Самый быстрый способ найти количество строк в тексте (C++)

Мне нужно прочитать количество строк в файле перед выполнением некоторых операций над этим файлом. Когда я пытаюсь прочитать файл и увеличить переменную line_count на каждой итерации, пока не достигну eof. В моем случае это было не так быстро. Я использовал ifstream и fgets . Они оба были медлительны . Есть ли хакерский способ сделать это, который также используется, например, BSD, Linux kernel или berkeley db.(может быть с помощью побитовых операций).

Как я уже говорил, есть миллионы строк в этот файл и он продолжает увеличиваться, каждая строка имеет около 40 или 50 символов. Я использую Linux.

Примечание.: Я уверен, что будут люди, которые могут сказать, что используют идиота DB. Но кратко в моем случае я не могу использовать db.

8 ответов


единственный способ найти счетчик строк-прочитать весь файл и подсчитать количество символов конца строки. Самый быстрый способ сделать это, вероятно, прочитать весь файл в большой буфер с одной операцией чтения, а затем пройти через буфер, подсчитывая символы "\n".

поскольку ваш текущий размер файла составляет около 60 мб, это не является привлекательным вариантом. Вы можете получить некоторую скорость, не читая весь файл, но читая его кусками., скажем, размер 1Mb. Вы также скажите, что о базе данных не может быть и речи, но она действительно выглядит лучшим долгосрочным решением.

Edit: Я просто запустил небольшой тест на этом, и использование буферизованного подхода (размер буфера 1024K) кажется немного более чем в два раза быстрее, чем чтение строки за раз с getline (). Вот код - мои тесты были сделаны с g++, используя-O2 уровень оптимизации:

#include <iostream>
#include <fstream>
#include <vector>
#include <ctime>
using namespace std;

unsigned int FileRead( istream & is, vector <char> & buff ) {
    is.read( &buff[0], buff.size() );
    return is.gcount();
}

unsigned int CountLines( const vector <char> & buff, int sz ) {
    int newlines = 0;
    const char * p = &buff[0];
    for ( int i = 0; i < sz; i++ ) {
        if ( p[i] == '\n' ) {
            newlines++;
        }
    }
    return newlines;
}

int main( int argc, char * argv[] ) {
    time_t now = time(0);
    if ( argc == 1  ) {
        cout << "lines\n";
        ifstream ifs( "lines.dat" );
        int n = 0;
        string s;
        while( getline( ifs, s ) ) {
            n++;
        }
        cout << n << endl;
    }
    else {
        cout << "buffer\n";
        const int SZ = 1024 * 1024;
        std::vector <char> buff( SZ );
        ifstream ifs( "lines.dat" );
        int n = 0;
        while( int cc = FileRead( ifs, buff ) ) {
            n += CountLines( buff, cc );
        }
        cout << n << endl;
    }
    cout << time(0) - now << endl;
}

Не используйте строки stl C++ и getline (или fgets C), просто необработанные указатели стиля C и либо блок чтения в кусках размера страницы, либо mmap файл.

затем сканируйте блок в собственном размере слова вашей системы (т. е. либо uint32_t или uint64_t), используя один из магические алгоритмы ' SIMD в рамках операций регистра (SWAR)' для тестирования байтов в word. Пример здесь; цикл с 0x0a0a0a0a0a0a0a0aLL в нем сканирует на разрывы строк. ( этот код получает около 5 циклов на входной байт, соответствующий регулярному выражению в каждой строке файла)

если файл составляет всего несколько десятков или СТО или около того мегабайт, и он продолжает расти (т. е. что-то продолжает писать ему), то есть хорошая вероятность того, что linux кэширует его в памяти, поэтому он не будет ограничен дисковым вводом, но ограничена пропускная способность памяти.

если файл только когда-либо добавляется, вы также можете запомнить количество строк и предыдущая длина, и начало от там.


было указано, что вы можете использовать mmap с алгоритмами stl C++ и создать функтор для передачи в std::foreach. Я предложил вам не делать этого не потому, что вы не можете сделать это таким образом, но нет никакой выгоды в написании дополнительного кода для этого. Или вы можете использовать mmapped итератор boost, который обрабатывает все это для вас; но для проблемы код, с которым я связан, был написан для этого намного медленнее, и вопрос был о скорости, а не стиле.


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

разбор до конца файла. Запомните количество строк и смещение EOF. Когда файл растет fseek к смещению, проанализируйте EOF и обновите количество строк и смещение.


существует разница между подсчетом строк и разделителями подсчета строк. Некоторые общие gotchas, чтобы следить за, если получение точного подсчета строк важно:

  1. какова кодировка файла? Байтовые решения будут работать для ASCII и UTF-8, но следите, если у вас есть UTF-16 или какая-то многобайтовая кодировка, которая не гарантирует, что байт со значением линейного канала обязательно кодирует линейный канал.

  2. много текста файлы не имеют разделителя строк в конце последней строки. Поэтому, если ваш файл говорит "Hello, World!", вы можете получить счет 0 вместо 1. Вместо того, чтобы просто подсчитывать разделители линий, вам понадобится простая государственная машина для отслеживания.

  3. некоторые очень неясные файлы используют Unicode U+2028 LINE SEPARATOR (или даже U+2029 PARAGRAPH SEPARATOR) как разделители линии вместо более общего возвращения экипажа и/или линии питания. Вы также можете следить за U+0085 NEXT LINE (NEL).

  4. вам придется рассмотреть, хотите ли вы считать некоторые другие управляющие символы в качестве прерывателей строк. Например, если U+000C FORM FEED или U+000B LINE TABULATION (a.к. a. вертикальная вкладка) считается переходом на новую строку?

  5. текстовые файлы из более старых версий Mac OS (до OS X) используют возврат каретки (U+000D), а не перевода строки (U+000A) в отдельных строках. Если Вы читаете необработанные байты в буфер (например, с вашим потоком в двоичный режим) и сканирование их, вы придумаете количество 0 на этих файлах. Вы не можете подсчитать как возврат каретки, так и каналы строк, потому что файлы ПК обычно заканчивают строку с обоими. Опять же, вам понадобится простая государственная машина. (Кроме того, вы можете читать файл в текстовом режиме, а не в двоичном режиме. Текстовые интерфейсы нормализуют разделители строк до '\n' для файлов, которые соответствуют конвенции, используемой на вашей платформе. Если Вы читаете файлы с других платформ, вы вернетесь к двоичный режим с государственной машиной.)

  6. если у вас когда-либо была супер длинная строка в файле,getline() подход может вызвать исключение, вызывающее сбой простого счетчика строк на небольшом количестве файлов. (Это особенно верно, если Вы читаете старый файл Mac на платформе, отличной от Mac, вызывая getline() посмотреть весь файл как одну гигантскую строку.) Читая куски в буфер фиксированного размера и используя государственную машину, вы можете сделать его пулей доказательство.

код в принятом ответе страдает от большинства из этих ловушек. Сделайте это прямо перед тем, как сделать это быстро.


помните, что все fstreams буферизованы. Таким образом, они фактически читают кусками, поэтому вам не нужно воссоздавать эту функциональность. Все, что вам нужно сделать, это просканировать буфер. Не используйте getline (), хотя это заставит вас размер строки. Поэтому я бы просто использовал итераторы STL std::count и stream.

#include <iostream>
#include <fstream>
#include <iterator>
#include <algorithm>


struct TestEOL
{
    bool operator()(char c)
    {
        last    = c;
        return last == '\n';
    }
    char    last;
};

int main()
{
    std::fstream  file("Plop.txt");

    TestEOL       test;
    std::size_t   count   = std::count_if(std::istreambuf_iterator<char>(file),
                                          std::istreambuf_iterator<char>(),
                                          test);

    if (test.last != '\n')  // If the last character checked is not '\n'
    {                       // then the last line in the file has not been 
        ++count;            // counted. So increement the count so we count
    }                       // the last line even if it is not '\n' terminated.
}

Это не медленно из-за вашего алгоритма , это медленно, потому что операции ввода-вывода медленные. Я полагаю, вы используете простой алгоритм O(n), который просто последовательно просматривает файл. В таком случае, есть нет быстрый алгоритм, который может оптимизировать ваши программы.

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

сопоставленные с памятью файлы не позволят вам реализовать алгоритм лучше, чем O (n), но это мая уменьшит время доступа IO.


вы можете получить окончательный ответ, только сканируя весь файл в поисках символов новой строки. Ничего не поделаешь.

тем не менее, есть несколько возможностей, которые вы можете рассмотреть.

1 / Если вы используете упрощенный цикл, читая по одному символу за раз, проверяя наличие новых строк, не делайте этого. Несмотря на то, что ввод-вывод может быть буферизован, сами вызовы функций являются дорогостоящими, с учетом времени.

лучшим вариантом является чтение больших кусков файл (скажем, 5M) в память с одной операцией ввода-вывода, а затем обработать это. Вам, вероятно, не нужно слишком беспокоиться о специальной инструкции по сборке, так как библиотека времени выполнения C будет оптимизирована в любом случае - простой strchr() должны сделать это.

2 / Если вы говорите, что общая длина строки составляет около 40-50 символов, и вам не нужно точно количество строк, просто возьмите размер файла и разделите на 45 (или любое среднее значение, которое вы считаете нужным использовать).

3/ Если это что-то вроде файла журнала, и вы не есть чтобы сохранить его в одном файле (может потребоваться доработка на других частях системы), рассмотрите возможность периодического разделения файла.

например, когда он достигает 5 м, переместите его (например,x.log), чтобы от имени файла (например, x_20090101_1022.log) и выяснить, сколько строк есть в этой точке (сохранение его в x_20090101_1022.count, затем начните новый x.log файл журнала. Характеристики файлов журнала означают, что этот созданный раздел dated будет никогда не изменяйте, поэтому вам никогда не придется пересчитывать количество строк.

чтобы обработать журнал "файл", вы просто cat x_*.log через какую-то технологическую трубу, а не cat x.log. Чтобы получить количество строк "файла", сделайте wc -l на текущем x.войти (относительно быстро) и добавить его к сумме всех значений в x_*.count файлы.


вещь, которая занимает время, загружает 40 + MB в память. Самый быстрый способ сделать это-либо запомнить его, либо загрузить его за один раз в большой буфер. Как только у вас есть это в памяти, так или иначе, цикл, пересекающий данные, ищущие \n символы практически мгновенно, независимо от того, как это реализовано.

Итак, на самом деле, самый важный трюк-загрузить файл в память как можно быстрее. И самый быстрый способ сделать это-сделать это как один операция.

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

или, возможно, вы можете поддерживать отдельный индексный файл, показывающий местоположение известных символов "\n", поэтому эти части файла можно пропустить.

чтение большой объем данных с жесткого диска происходит медленно. Ничего не поделаешь.