我想逐行处理文本文件。在过去,我将文件加载到StringList
:
slFile := TStringList.Create();
slFile.LoadFromFile(filename);
for i := 0 to slFile.Count-1 do
begin
oneLine := slFile.Strings[i];
//process the line
end;
问题是,一旦文件达到几百兆,我必须分配一个巨大的内存块;当我真的只需要足够的内存来保持一行一行时。 (另外,当系统在步骤1中锁定加载文件时,您无法真正指示进度。)
我尝试使用Delphi提供的本机和推荐的文件I / O例程:
var
f: TextFile;
begin
Reset(f, filename);
while ReadLn(f, oneLine) do
begin
//process the line
end;
Assign
的问题在于没有选项可以在没有锁定的情况下读取文件(即fmShareDenyNone
)。前stringlist
示例也不支持禁止锁定,除非您将其更改为LoadFromStream
:
slFile := TStringList.Create;
stream := TFileStream.Create(filename, fmOpenRead or fmShareDenyNone);
slFile.LoadFromStream(stream);
stream.Free;
for i := 0 to slFile.Count-1 do
begin
oneLine := slFile.Strings[i];
//process the line
end;
所以现在即使我没有获得锁定,我又回来将整个文件加载到内存中。
是否有Assign
/ ReadLn
的替代方案,我可以逐行读取文件,而无需进行共享锁定?
我宁愿不直接进入Win32 CreateFile
/ ReadFile
,也不得不处理分配缓冲区并检测CR
,LF
,CRLF
的。
我考虑过内存映射文件,但如果整个文件不适合(映射)到虚拟内存中,并且必须一次映射文件的视图(片段),则存在困难。开始变得难看。
我只想Reset
与fmShareDenyNone
一起使用!
答案 0 :(得分:15)
使用最新的Delphi版本,您可以使用TStreamReader
。使用您的文件流构建它,然后调用its ReadLine
method(继承自TTextReader
)。
所有Delphi版本的选项是使用Peter Below's StreamIO unit,它会为您提供AssignStream
。它的工作方式与AssignFile
类似,但对于流而不是文件名。一旦您使用该函数将流与TextFile
变量相关联,您就可以像调用任何其他文件一样调用ReadLn
和其他I / O函数。
答案 1 :(得分:3)
如果您需要在较早的Delphis中支持ansi和Unicode,则可以使用我的GpTextFile或GpTextStream。
答案 2 :(得分:3)
您可以使用以下示例代码:
TTextStream = class(TObject)
private
FHost: TStream;
FOffset,FSize: Integer;
FBuffer: array[0..1023] of Char;
FEOF: Boolean;
function FillBuffer: Boolean;
protected
property Host: TStream read FHost;
public
constructor Create(AHost: TStream);
destructor Destroy; override;
function ReadLn: string; overload;
function ReadLn(out Data: string): Boolean; overload;
property EOF: Boolean read FEOF;
property HostStream: TStream read FHost;
property Offset: Integer read FOffset write FOffset;
end;
{ TTextStream }
constructor TTextStream.Create(AHost: TStream);
begin
FHost := AHost;
FillBuffer;
end;
destructor TTextStream.Destroy;
begin
FHost.Free;
inherited Destroy;
end;
function TTextStream.FillBuffer: Boolean;
begin
FOffset := 0;
FSize := FHost.Read(FBuffer,SizeOf(FBuffer));
Result := FSize > 0;
FEOF := Result;
end;
function TTextStream.ReadLn(out Data: string): Boolean;
var
Len, Start: Integer;
EOLChar: Char;
begin
Data:='';
Result:=False;
repeat
if FOffset>=FSize then
if not FillBuffer then
Exit; // no more data to read from stream -> exit
Result:=True;
Start:=FOffset;
while (FOffset<FSize) and (not (FBuffer[FOffset] in [#13,#10])) do
Inc(FOffset);
Len:=FOffset-Start;
if Len>0 then begin
SetLength(Data,Length(Data)+Len);
Move(FBuffer[Start],Data[Succ(Length(Data)-Len)],Len);
end else
Data:='';
until FOffset<>FSize; // EOL char found
EOLChar:=FBuffer[FOffset];
Inc(FOffset);
if (FOffset=FSize) then
if not FillBuffer then
Exit;
if FBuffer[FOffset] in ([#13,#10]-[EOLChar]) then begin
Inc(FOffset);
if (FOffset=FSize) then
FillBuffer;
end;
end;
function TTextStream.ReadLn: string;
begin
ReadLn(Result);
end;
用法:
procedure ReadFileByLine(Filename: string);
var
sLine: string;
tsFile: TTextStream;
begin
tsFile := TTextStream.Create(TFileStream.Create(Filename, fmOpenRead or fmShareDenyWrite));
try
while tsFile.ReadLn(sLine) do
begin
//sLine is your line
end;
finally
tsFile.Free;
end;
end;
答案 3 :(得分:2)
我所做的是使用TFileStream,但我将输入缓冲到相当大的块(例如每个几兆字节),并一次读取和处理一个块。这样我就不必一次加载整个文件。
这种方式非常快,即使是大文件也是如此。
我确实有进度指示器。当我加载每个块时,我将它增加了另外加载的文件的分数。
一次只读一行,无需缓冲,对大文件来说太慢了。
答案 4 :(得分:2)
因为似乎 FileMode 变量对Textfiles无效,但我的测试显示从文件中多次读取没有问题。你没有在你的问题中提到它,但如果你在阅读时不打算写文本文件,你应该很好。
答案 5 :(得分:0)
为什么不直接从TFileStream本身直接读取文件的行?
即。 (伪代码):
readline:
while NOT EOF and (readchar <> EOL) do
appendchar to result
while NOT EOF do
begin
s := readline
process s
end;
你可能会发现一个问题是iirc TFileStream没有被缓冲,因此对大文件的性能将是次优的。但是,对于非缓冲流including this one的问题,有很多解决方案,如果这种方法可以解决您的初始问题,您可能希望进行调查。
答案 6 :(得分:0)
几年前我遇到了同样的问题,尤其是锁定文件的问题。我所做的是使用shellapi的低级读取文件。我知道问题已经过去了,因为我的回答(2年),但也许我的贡献可以帮助将来的某个人。
const
BUFF_SIZE = $8000;
var
dwread:LongWord;
hFile: THandle;
datafile : array [0..BUFF_SIZE-1] of char;
hFile := createfile(PChar(filename)), GENERIC_READ, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, 0);
SetFilePointer(hFile, 0, nil, FILE_BEGIN);
myEOF := false;
try
Readfile(hFile, datafile, BUFF_SIZE, dwread, nil);
while (dwread > 0) and (not myEOF) do
begin
if dwread = BUFF_SIZE then
begin
apos := LastDelimiter(#10#13, datafile);
if apos = BUFF_SIZE then inc(apos);
SetFilePointer(hFile, aPos-BUFF_SIZE, nil, FILE_CURRENT);
end
else myEOF := true;
Readfile(hFile, datafile, BUFF_SIZE, dwread, nil);
end;
finally
closehandle(hFile);
end;
对我而言,速度提升似乎很重要。