Я имею дело с большими текстовыми файлами (больше 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;
Есть ли более быстрый способ получить итоговые значения?
Размер более 100 МБ на самом деле не говорит о многом. То, как вы подойдете к этому, будет сильно зависеть от того, насколько больше 100 МБ вы говорите. То, как вы управляете файлом размером 1 ГБ, сильно отличается от файла размером 100 ГБ.
Допустим, 10 ГБ — это максимум. Забавно то, что диспетчер задач отображает высокую загрузку ЦП, но низкую загрузку диска, когда выполняется приведенный выше код.
Да, потому что вы выделяете память для копирования каждой строки файла в строку во время подсчета.
пересчет CR/LF: можно ли предположить, что каждое из этих вхождений является концом строки? Или они могут быть частью какой-то последовательности Unicode? (Я не знаю, поэтому я и спрашиваю.) Если последнее, ему действительно нужно декодировать данные в строки, чтобы посчитать строки.
@dummzeuch: OP использует UTF-8, поэтому, если вы найдете байт 10, вы знаете, что это LF. Аналогично для КР. Смотрите таблицу на en.wikipedia.org/wiki/UTF-8.
@dummzeuch Да, это может быть частью «некоторой последовательности Unicode» - UTF-16 и UTF-32. Но не UTF-8.
Ответ прост: нет. Ваш алгоритм самый быстрый, но реализация — нет. Вы должны прочитать весь файл и посчитать строки. По крайней мере, если строки не фиксированного размера.
То, как вы читаете файл, может повлиять на общую производительность. Прочитайте файл блок за блоком в двоичном буфере (массиве байтов) как можно большего размера. Затем подсчитайте строки в буфере и выполните цикл с блоком в том же буфере.
Ответ на самом деле да. Это может быть быстрее, чем этот код, потому что потоковое чтение медленное.
Я бы просто выделил фиксированный буфер или сопоставил файл с памятью и просканировал необработанные данные файла по частям, считая байты LF, и даже не беспокоился о декодировании UTF-8.
@RemyLebeau на самом деле LF далеко не достаточно, см. Разделители строк Unicode . Даже без контекста UTF-8 я также ожидаю старой строки новой строки Mac только CR.
@AmigoJack OP использует TStreamReader.ReadLine()
, который поддерживает только разрывы строк без CR, без LF и CRLF. Не так сложно справиться со всеми тремя при сканировании разрывов строк вручную. В наши дни в реальных данных редко используются другие разрывы строк Unicode. Но если вам нужно справиться с ними всеми, это действительно не так сложно.
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, который работает немного лучше. В любом случае, ваш ответ правильный и открыл мне горизонты.
Это код всей программы. Кажется, работает просто отлично и довольно быстро:
Вы ДОЛЖНЫ использовать блок
try..finally
для защиты вашего объекта. Но чтобы ответить на ваш вопрос: вероятно, самый быстрый способ - прочитать его как двоичный файл, а затем перебрать его байты и подсчитать количество найденных последовательностей CRLF. Ваш код выше медленный, потому что вы не только считаете строки, но и извлекаете их как строки.