Эффективное копирование одного стандартного потока в другой

Хорошо, вот код, который описывает, что я пытаюсь сделать.

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/fcntl.h>

#include <iostream>
#include <sstream>

int main( int c, char *v[] )
{
    int fd = open( "data.out", O_RDONLY | O_NONBLOCK );
    std::cout << "fd = " << fd << std::endl;

    char buffer[ 1024000 ];
    ssize_t nread;

    std::stringstream ss;

    while( true )
    {
        if ( (nread = read( fd, buffer, sizeof( buffer ) - 1 )) < 0 )
            break;

        ss.write( buffer, nread );

        while( true )
        {
            std::stringstream s2;

            std::cout << "pre-get  : " <<
                (((ss.rdstate() & std::ios::badbit) == std::ios::badbit) ? "bad" : "") << " " <<
                (((ss.rdstate() & std::ios::eofbit) == std::ios::eofbit) ? "eof" : "") << " " <<
                (((ss.rdstate() & std::ios::failbit) == std::ios::failbit) ? "fail" : "" ) << " " <<
                std::endl;

            ss.get( *s2.rdbuf() );

            std::cout << "post-get : " <<
                (((ss.rdstate() & std::ios::badbit) == std::ios::badbit) ? "bad" : "") << " " <<
                (((ss.rdstate() & std::ios::eofbit) == std::ios::eofbit) ? "eof" : "") << " " <<
                (((ss.rdstate() & std::ios::failbit) == std::ios::failbit) ? "fail" : "" ) << " " <<
                std::endl;

            unsigned int linelen = ss.gcount() - 1;

            if ( ss.eof() )
            {
                ss.str( s2.str() );
                break;
            }
            else if ( ss.fail() )
            {
                ss.str( "" );
                break;
            }
            else
            {
                std::cout << s2.str() << std::endl;
            }
        }
    }
}

Сначала он считывает большие куски данных в буфер данных. Я знаю, что есть лучшие способы выполнения этой части на С++, но в моем реальном приложении мне вручают буфер char[] и длину.

Затем я записываю буфер в объект std::stringstream, чтобы я мог удалять из него строку за раз.

Я подумал, что буду использовать метод get(streambuf &) в потоке строк, чтобы записать одну строку в другой поток строк, где я смогу затем вывести ее.

Игнорируя тот факт, что это может быть не лучший способ извлечь строку за раз из буфера, который я читал (хотя я хотел бы, чтобы кто-нибудь предложил лучшую альтернативу той, которую я публикую здесь), как только первый ss.get( *s2.rdbuf() ) называется, ss находится в состоянии сбоя, и я не могу понять, почему. Во входном файле много данных, поэтому ss определенно должно содержать более одной строки ввода.

Есть идеи?


person ScaryAardvark    schedule 18.01.2010    source источник


Ответы (2)


Мне кажется, что первый (и, возможно, самый важный) шаг к получению достойной эффективности — минимизировать копирование данных. Так как вы получаете данные в char[] с длиной, моей первой тенденцией было бы начать с создания strstream с использованием этого буфера. Затем вместо того, чтобы копировать строку за раз в другой поток strstream (или stringstream), я бы копировал строки по одной в поток, который вы будете использовать для их записи на выходе.

Если вам разрешено изменять содержимое буфера, другой возможностью будет разбить буфер на строки, просто заменив каждый '\n' на '\0'. Если вы собираетесь это сделать, вам обычно потребуется создать вектор (дек и т. д.) указателей на начало каждой строки (т. е. найти первый '\r' или '\n' и замените его на «\ 0». Затем следующая вещь, отличная от «\ r» или «\ n», является началом следующей строки, поэтому ее адрес в вашем векторе).

Я бы также хорошо подумал о том, можете ли вы избежать построчного вывода. Чтение большого буфера для поиска новой строки происходит относительно медленно. Если вы все равно собираетесь писать одну строку за другой, вы можете избежать всего этого, просто записав весь буфер в поток вывода и закончив с этим.

person Jerry Coffin    schedule 18.01.2010

Я тестировал это на Windows, так что вы можете проверить это;

Если data.out начинается с новой строки, у меня возникает та же проблема, что и у вас, в противном случае ss.get( *s2.rdbuf() ) отлично работает для первого вызова.

При повторном вызове текущая позиция потока не продвинулась дальше EOL. Поэтому, когда вызывается во второй раз, get немедленно пытается прочитать EOL, и, поскольку никакие другие символы не были скопированы, он устанавливает бит ошибки.

Быстрое и, возможно, грязное исправление:

ss.get( *s2.rdbuf() );
// Get rid of EOL (may need an extra if file contains both \r and \n)
ss.get();
person Fredrik Jansson    schedule 18.01.2010