Author: Jonas Bilinkevicius
I'm trying to write a function that, given a FileName and a line number, returns
the entire line in a string.
Answer:
This technique is useful for high-speed processing. Save the sample program file
with a .pas or .dpr file name and compile it with dcc32:
1 {$APPTYPE CONSOLE}
2 uses
3 SysUtils, Classes;
4
5 function GrabLine(const AFileName: string; ALine: Integer): string;
6 var
7 fs: TFileStream;
8 buf: packed array[0..4095] of Char;
9 bufRead: Integer;
10 bufPos: PChar;
11 lineStart: PChar;
12 tmp: string;
13 begin
14 fs := TFileStream.Create(AFileName, fmOpenRead);
15 try
16 Dec(ALine);
17 bufRead := 0;
18 bufPos := nil;
19 { read the first line specially }
20 if ALine = 0 then
21 begin
22 bufRead := fs.read(buf, SizeOf(buf));
23 if bufRead = 0 then
24 raise Exception.Create('Line not found');
25 bufPos := buf;
26 end
27 else
28 while ALine > 0 do
29 begin
30 { read in a buffer }
31 bufRead := fs.read(buf, SizeOf(buf));
32 if bufRead = 0 then
33 raise Exception.Create('Line not found');
34 bufPos := buf;
35 while (bufRead > 0) and (ALine > 0) do
36 begin
37 if bufPos^ = #10 then
38 Dec(ALine);
39 Inc(bufPos);
40 Dec(bufRead);
41 end;
42 end;
43 { Found the beginning of the line at bufPos... scan for end.
44 Two cases:
45 1) we'll find it before the end of this buffer
46 2) it'll go beyond this buffer and into n more buffers }
47 lineStart := bufPos;
48 while (bufRead > 0) and (bufPos^ <> #10) do
49 begin
50 Inc(bufPos);
51 Dec(bufRead);
52 end;
53 { if bufRead is positive, we'll have found the end and we can leave. }
54 SetString(Result, lineStart, bufPos - lineStart);
55 { determine if there are more buffers to process }
56 while bufRead = 0 do
57 begin
58 bufRead := fs.read(buf, SizeOf(buf));
59 lineStart := buf;
60 bufPos := buf;
61 while (bufRead > 0) and (bufPos^ <> #10) do
62 begin
63 Inc(bufPos);
64 Dec(bufRead);
65 end;
66 SetString(tmp, lineStart, bufPos - lineStart);
67 Result := Result + tmp;
68 end;
69 finally
70 fs.Free;
71 end;
72 end;
73
74 function GrabLine2(const s: string; ALine: Integer): string;
75 var
76 sl: TStringList;
77 begin
78 sl := TStringList.Create;
79 try
80 sl.LoadFromFile(s);
81 Result := sl[ALine - 1]; { index off by one }
82 finally
83 sl.Free;
84 end;
85 end;
86
87 begin
88 Writeln(GrabLine(ParamStr(1), StrToInt(ParamStr(2))));
89 Writeln(GrabLine2(ParamStr(1), StrToInt(ParamStr(2))));
90 end.
Call it like 'getline testfile.txt 20000', depending on what you call the .pas (or .dpr) file. For large (i.e. tens of megabytes) files, the (rather complex) scanning function easily beats the memory expensive StringList version.
|