Быстрый способ получить общее количество строк большого файла

Я имею дело с большими текстовыми файлами (больше 100 МБ). Мне нужно общее количество строк как можно быстрее. В настоящее время я использую код ниже (обновление: добавлено try-finally):

var
  SR: TStreamReader;
  totallines: int64;
  str: string;
begin
  SR:=TStreamReader.Create(myfilename, TEncoding.UTF8);
  try
    totallines:=0;
    while not SR.EndOfStream do
    begin
      str:=SR.ReadLine;
      inc(totallines);
    end;
  finally
    SR.Free;
  end;
end;

Есть ли более быстрый способ получить итоговые значения?

Вы ДОЛЖНЫ использовать блок try..finally для защиты вашего объекта. Но чтобы ответить на ваш вопрос: вероятно, самый быстрый способ - прочитать его как двоичный файл, а затем перебрать его байты и подсчитать количество найденных последовательностей CRLF. Ваш код выше медленный, потому что вы не только считаете строки, но и извлекаете их как строки.

Andreas Rejbrand 14.12.2020 11:06

Размер более 100 МБ на самом деле не говорит о многом. То, как вы подойдете к этому, будет сильно зависеть от того, насколько больше 100 МБ вы говорите. То, как вы управляете файлом размером 1 ГБ, сильно отличается от файла размером 100 ГБ.

J... 14.12.2020 12:09

Допустим, 10 ГБ — это максимум. Забавно то, что диспетчер задач отображает высокую загрузку ЦП, но низкую загрузку диска, когда выполняется приведенный выше код.

Xel Naga 14.12.2020 12:16

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

J... 14.12.2020 12:31

пересчет CR/LF: можно ли предположить, что каждое из этих вхождений является концом строки? Или они могут быть частью какой-то последовательности Unicode? (Я не знаю, поэтому я и спрашиваю.) Если последнее, ему действительно нужно декодировать данные в строки, чтобы посчитать строки.

dummzeuch 14.12.2020 14:27

@dummzeuch: OP использует UTF-8, поэтому, если вы найдете байт 10, вы знаете, что это LF. Аналогично для КР. Смотрите таблицу на en.wikipedia.org/wiki/UTF-8.

Andreas Rejbrand 14.12.2020 14:31

@dummzeuch Да, это может быть частью «некоторой последовательности Unicode» - UTF-16 и UTF-32. Но не UTF-8.

AmigoJack 14.12.2020 19:48
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
7
1 770
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ прост: нет. Ваш алгоритм самый быстрый, но реализация — нет. Вы должны прочитать весь файл и посчитать строки. По крайней мере, если строки не фиксированного размера.

То, как вы читаете файл, может повлиять на общую производительность. Прочитайте файл блок за блоком в двоичном буфере (массиве байтов) как можно большего размера. Затем подсчитайте строки в буфере и выполните цикл с блоком в том же буфере.

Ответ на самом деле да. Это может быть быстрее, чем этот код, потому что потоковое чтение медленное.

David Heffernan 14.12.2020 14:24

Я бы просто выделил фиксированный буфер или сопоставил файл с памятью и просканировал необработанные данные файла по частям, считая байты LF, и даже не беспокоился о декодировании UTF-8.

Remy Lebeau 14.12.2020 19:32

@AmigoJack OP использует TStreamReader.ReadLine(), который поддерживает только разрывы строк без CR, без LF и CRLF. Не так сложно справиться со всеми тремя при сканировании разрывов строк вручную. В наши дни в реальных данных редко используются другие разрывы строк Unicode. Но если вам нужно справиться с ними всеми, это действительно не так сложно.

Remy Lebeau 14.12.2020 20:04
Ответ принят как подходящий
Program LineCount;

{$APPTYPE CONSOLE}
{$WEAKLINKRTTI ON}
{$RTTI EXPLICIT METHODS([]) PROPERTIES([]) FIELDS([])}
{$SetPEFlags 1}

{ Compile with XE8 or above... }

USES
  SysUtils,
  BufferedFileStream;

VAR
  LineCnt: Int64;
  Ch: Char;
  BFS: TReadOnlyCachedFileStream;

function Komma(const S: string; const C: Char = ','): string;
{ About 4 times faster than Comma... }
var
  I: Integer; // loops through separator position
begin
  Result := S;
  I := Length(S) - 2;
  while I > 1 do
  begin
    Insert(C, Result, I);
    I := I - 3;
  end;
end; {Komma}

BEGIN
  writeln('LineCount - Copyright (C) 2020 by Walter L. Chester.');
  writeln('Counts lines in the given textfile.');
  if ParamCount <> 1 then
    begin
      writeln('USAGE:  LineCount <filename>');
      writeln;
      writeln('No file size limit!  Counts lines: takes 4 minutes on a 16GB file.');
      Halt;
    end;
  if not FileExists(ParamStr(1)) then
    begin
      writeln('File not found!');
      halt;
    end;
  writeln('Counting lines in file...');
  BFS := TReadOnlyCachedFileStream.Create(ParamStr(1), fmOpenRead);
  try
    LineCnt := 0;
    while BFS.Read(ch,1) = 1 do
      begin
        if ch = #13 then
          Inc(LineCnt);
        if (LineCnt mod 1000000) = 0 then
          write('.');
      end;
    writeln;
    writeln('Total Lines: ' + Komma(LineCnt.ToString));
  finally
    BFS.Free;
  end;
END.

Большое спасибо! Интересно, что Int64 не работал. Поэтому я изменил его на Integer. Кроме того, я обнаружил, что в Delphi есть встроенный класс TBufferedFileStream, который работает немного лучше. В любом случае, ваш ответ правильный и открыл мне горизонты.

Xel Naga 16.12.2020 20:22

Это код всей программы. Кажется, работает просто отлично и довольно быстро:

Walterc 19.12.2020 16:47

Другие вопросы по теме