Delphi:使用Reset/ReadLn读取文本文件的替代方法
我想逐行处理一个文本文件。以前,我将文件加载到Delphi:使用Reset/ReadLn读取文本文件的替代方法,delphi,text-files,readline,Delphi,Text Files,Readline,我想逐行处理一个文本文件。以前,我将文件加载到StringList: slFile := TStringList.Create(); slFile.LoadFromFile(filename); for i := 0 to slFile.Count-1 do begin oneLine := slFile.Strings[i]; //process the line end; 问题是一旦文件达到几百兆字节,我就必须分配一大块内存;实际上,我一次只需要足够的内存来容纳一行。(另外,
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
的问题是,没有锁定就无法读取文件(即fmsharedynone
)。前一个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
,我可以逐行读取文件,而无需使用共享锁,还有其他方法吗
我不想直接进入Win32CreateFile
/ReadFile
,而必须处理分配缓冲区和检测CR
,LF
,CRLF
我考虑过内存映射文件,但是如果整个文件不适合(映射)到虚拟内存中,并且必须一次映射文件的视图(片段),则会有困难。开始变丑了
我只想用
fmsharedynone
重置 为什么不直接从TFileStream本身一次读取一行文件
i、 e.(伪代码):
readline:
而不是EOF和(readchar EOL)do
追加字符到结果
而不是EOF做什么
开始
s:=读线
过程s
结束;
您可能会发现这样做的一个问题是iirc TFileStream没有缓冲,因此大文件上的性能将是次优的。但是,对于非缓冲流的问题,有许多解决方案,您可能希望研究这种方法是否解决了初始问题。我所做的是使用TFileStream,但我将输入缓冲到相当大的块(例如,每个块数兆字节)中,并一次读取和处理一个块。这样我就不必一次加载整个文件
这样工作速度相当快,即使对于大文件也是如此
我有一个进度指标。当我加载每个块时,我会将其增加额外加载的文件部分
一次读取一行,而不进行缓冲,对于大文件来说太慢了。对于最新的Delphi版本,您可以使用。用您的文件流构造它,然后调用(继承自TTextReader
)
所有Delphi版本都可以使用一个选项,它为您提供AssignStream
。它的工作原理类似于AssignFile
,但用于流而不是文件名。使用该函数将流与TextFile
变量关联后,您可以像调用任何其他文件一样调用ReadLn
及其上的其他I/O函数。因为FileMode变量似乎对Textfiles无效,但我的测试表明,从该文件多次读取没有问题。您在问题中没有提到它,但是如果您不打算在读取文本文件时写入文本文件,您应该会很好。如果您需要在旧的Delphis中支持ansi和Unicode,您可以使用我的或 您可以使用以下示例代码:
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;
几年前我也遇到过同样的问题,尤其是锁定文件的问题。我所做的是使用来自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;
对我来说,速度的提高似乎意义重大。-1。即使对于非文本文件,调用Reset
时,除了FileMode
的下两位之外,所有的都会被屏蔽,因此共享标志也会被忽略。你真的尝试过吗?我制作了一个简单的应用程序,用fmOpenRead+fmShareDenyWrite打开一个文本文件,每次点击按钮都读取一行,然后将其添加到TMemo中。我可以执行两次应用程序,同时读取文件。此外,禁止写入文件。如果有人感兴趣,我可以编辑我的答案,包括相关的源代码。顺便说一句,用D2010测试。我刚刚做了另一个测试:即使没有FmSharedyWrite,它也能工作。到目前为止,我遇到的唯一缺点是,在文件打开读取时(即使使用fmsharedynone),似乎不可能写入文件,但从多个进程读取似乎没有问题。我不想这样做的原因是,不容易做到正确。例如,您的伪代码有3个细微的bug。因此,与其重新发明一个有缺陷的轮子,我更愿意使用封装的、经过测试的代码。它怎么能包含缺陷呢?这是用来说明想法的伪代码,不是真正的代码!!如何实现真正的代码将决定它是否包含bug。您希望在从光盘读取if时处理文件,而不是在读取整个内容后处理文件,然后在流媒体传输时处理文件,这正是您所需要的(您会注意到,所有其他答案都是此主题的变体!)。如果你已经知道你想听到什么样的答案,为什么还要问这个问题呢?伪代码用于显示算法,而不需要处理特定的语言。在这种情况下,算法是有缺陷的。我很抱歉,但是如果你需要在这样的论坛上用填鸭式输入完整的工作代码,即使是伪代码的形式,那么你应该找另一份工作。软件开发显然不是你的专长(如果你能很好地发现细微的逻辑缺陷,那么你就能很好地编写没有这些缺陷的真正代码)。FFS这是我能在t上发现虫子的唯一原因
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;
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;