![]() |
| ||
|
|
Новости |
Поиск |
FAQ |
Статьи |
Книги |
Новости VCL
| | Форумы ЧАТ |
Орешник |
|
| |||
| |||
« предыдущая ветвь | форум | следующая ветвь »
Распарсить большой текстовый файл [D7, NT4, Win2k, WinXP]
Brenagwynn © (2005-02-04 13:56:00)Есть текстовый файл приличных размеров (минимум порядка 50000 строк), представляющий собой таблицу (с известными разделителями и известным кол-вом столбцов). Может коллективный разум подкинет какой-нить алгоритм, или хотя бы идею, как его побыстрее распарсить? Понятно, что можно тупо построчно парсить (загрузив в стринглист, например), но может вдруг найдется что-нить получше...
(2005-02-04 18:26:00) [1]Удалено модератором
Digitman (2005-02-04 17:57:00) [2]
> Игорь Шевченко © (04.02.05 17:33) [34]
прапорщику и задача-то иная ставилась, так ведь ?
TStringList вовсе не для таких объемов данных планировался ..
для оперативной и быстрой обработки ..
ну штука строк, ну другая .. ну уж никак не сотни, сотни тысяч штук строк ..
мозжечком шевелить таки надо ..
Игорь Шевченко (2005-02-04 17:33:00) [3]Digitman © (04.02.05 17:24) [33]
> вынужден он, понимаешь ли, при каждом add-вызове перераспределять
> память
Написано, кстати, действительно неоптимально. Для небольших объемов еще незаметно, а вот для более/менее солидных уже проявляются побочные эффекты.
Я удивляюсь современным программистам - скоро для удаления пробелов в строке будут WordApplication использовать. И непременно через супер-пупер компонент.
С уважением,
Digitman (2005-02-04 17:24:00) [4]
> Игорь Шевченко © (04.02.05 16:35) [31]
> Странно. У тебя наверное .. StringList...военный
точно, Игорь.
не иначе как прапорщик СА по заказу Борланда писал этот класс.
> TUser © (04.02.05 15:42) [21]
а куда ему, прапорщику, деваться ?
вынужден он, понимаешь ли, при каждом add-вызове перераспределять память ..
Brenagwynn (2005-02-04 16:36:00) [5]
> Alex_Bredin © (04.02.05 16:33) [30]
> Parsestring можно тоже сделать одной строкой - Stringlist.commatext
> := строка и готово
Тогда уж DelimitedText и Delimiter (у меня поля отделяются табуляцией). Думаю, что это будет все-тки медленнее (надо будет тоже попробовать).
Игорь Шевченко (2005-02-04 16:35:00) [6]TUser © (04.02.05 15:42) [21]
> Стринглист работает в чертово количество раз быстрее, чем
> readln
Странно. У тебя наверное импортный StringList. Или военный.
type
TForm1 = class(TForm)
Label1: TLabel;
Label2: TLabel;
lbReadln: TLabel;
lbStringList: TLabel;
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject);
var
F: TextFile;
Start: DWORD;
Buffer: string;
begin
Start := GetTickCount;
AssignFile(F, 'test.txt');
Reset(F);
try
while not Eof(F) do
readln(F, buffer);
finally
CloseFile(F);
end;
lbReadln.Caption := IntToStr(GetTickCount - Start);
end;
procedure TForm1.Button2Click(Sender: TObject);
var
List: TStringList;
Start: DWORD;
begin
Start := GetTickCount;
List := TStringList.Create;
List.LoadFromFile('test.txt');
lbStringList.Caption := IntToStr(GetTickCount - Start);
end;
end.
Результаты - readln - 390-410 тиков, stringlist - 700-1500 тиков.
Файл test.txt имеет 614 тысяч строк, размер 30 мегабайт.
Alex_Bredin (2005-02-04 16:33:00) [7]Parsestring можно тоже сделать одной строкой - Stringlist.commatext := строка и готово
Brenagwynn (2005-02-04 16:21:00) [8]
> Александр Иванов © (04.02.05 16:09) [28]
> И потом еще список может уступать, правда на совсем немного,
> в скорости доступа к строкам.
> Значит в этом я ошибся.
Как раз нет. При использовании ОДНОЙ И ТОЙ ЖЕ процедуры парсинга получаются примерно одни результаты. А вот если отказаться от нее и написать так:
colCount := 1;
rowCount := 1;
P := Pointer(S);
if P <> nil then
while P^ <> #0 do
begin
Start := P;
while not (P^ in [ #0, #9, #10, #13]) do Inc(P);
SetString(sub, Start, P - Start);
if P^ = #9 then begin
Inc(P);
Inc(colCount); // новый столбец
end;
if P^ = #13 then Inc(P);
if P^ = #10 then
begin
Inc(P);
colCount := 1;
Inc(rowCount); // новая строка
end;
end;
получается гаааараздо быстрее. У меня еще и ParseString был весьма тормозной (через Copy - Delete).
Александр Иванов (2005-02-04 16:09:00) [9]Александр Иванов © (04.02.05 15:23) [17]
И потом еще список может уступать, правда на совсем немного, в скорости доступа к строкам.
Значит в этом я ошибся.
Brenagwynn (2005-02-04 16:06:00) [10]
> Александр Иванов © (04.02.05 15:49) [26]
> Ты не понял, в твоей процедуре ParseString, необходимо будет
> только изменить условия с =, на in и внешнего цикла по строкам
> StringLista не будет.
Спасибо, помогло, да еще и как: 5800 мсек вместо 73145 мсек!
Александр Иванов (2005-02-04 15:49:00) [11]Brenagwynn (04.02.05 15:46) [24]
Ты не понял, в твоей процедуре ParseString, необходимо будет только изменить условия с =, на in и внешнего цикла по строкам StringLista не будет.
Alx2 (2005-02-04 15:48:00) [12]> Brenagwynn (04.02.05 15:39) [20]
Увеличить скорость чтения из файла "с помощью readln" поможет
SetTextBuf (см Help).
Если разделители стандартные, то можно непосредственно читать значение переменной с помощью read/readln
Brenagwynn (2005-02-04 15:46:00) [13]
> Александр Иванов © (04.02.05 15:43) [22]
> А для одной строки, пользуясь почти твоими терминами (Str
> - исходная строка):
> ParseString(Str,[ #9,#10,#13]);
То есть весь вышеприведенный код затолкать в одну процедуру. Можно, конечно... Ну да и фиг с ним.
Александр Иванов (2005-02-04 15:44:00) [14]TUser © (04.02.05 15:42) [21]
Я советовал ReadBuffer
Александр Иванов (2005-02-04 15:43:00) [15]А для одной строки, пользуясь почти твоими терминами (Str - исходная строка):
ParseString(Str,[ #9,#10,#13]);
TUser (2005-02-04 15:42:00) [16]Стринглист работает в чертово количество раз быстрее, чем readln
Brenagwynn (2005-02-04 15:39:00) [17]Через стринглист:
with TStringList.Create do
try
LoadFromFile('d:\apacs.txt');
for i := 0 to Count-1 do
ParseString(Strings[i],#9); // собственно парсим строку
finally
Free;
end;
Без стринглиста:
with TFileStream.Create('d:\ing.txt', fmOpenRead or fmShareDenyWrite) do
try
StrSize := Size - Position;
SetString(S, nil, StrSize);
Read(Pointer(S)^, StrSize);
P := Pointer(S);
if P <> nil then
while P^ <> #0 do
begin
Start := P;
while not (P^ in [ #0, #10, #13]) do Inc(P);
SetString(sub, Start, P - Start);
ParseString(sub,#9);
if P^ = #13 then Inc(P);
if P^ = #10 then Inc(P);
end;
finally
Free;
end;
Имхо, первое как-то компактнее выглядит.
Александр Иванов (2005-02-04 15:33:00) [18]Brenagwynn (04.02.05 15:26) [18]
А в чем удобстве? Так просто добавится еще один разделитель - #10#13.
Brenagwynn (2005-02-04 15:26:00) [19]
> Александр Иванов © (04.02.05 15:23) [17]
> И потом еще список может
> уступать, правда на совсем немного, в скорости доступа к
> строкам.
Ну, вот так оно и есть. Немного. А 700 мсек я готов пожертвовать в пользу удобства. Если бы была разница секунд в 5 - тогда другое дело.
Александр Иванов (2005-02-04 15:23:00) [20]Brenagwynn (04.02.05 15:21) [16]
Ты спрашивал, самый быстрый способ. И потом еще список может уступать, правда на совсем немного, в скорости доступа к строкам.