我一直想要解决这个问题,所以我写了一个解决方案,它是JvCsvDataSet的一部分。我的问题如下:
- 我想读取可能具有CR、CR+LF或仅LF结尾的文件。
- 我想要像ReadLn一样的东西,但对于点#1非常灵活,并且不具有ReadLn的众所周知的问题。因此,古老的Pascal拥有Textfile类型和ReadLn过程。现代Class等效项是必需的。
- 我希望它成为类似流的对象,以便我可以逐行读取,并且不会将整个3.7 GB兆字节的文件加载到内存中。此外,我希望Position是Int64类型,并且我希望能够处理非常大的文件(> 2 GB)。
- 我希望它在Delphi 7中工作,也在Delphi XE2中工作,以及两者之间的所有内容。
- 我希望它非常非常非常快。因此,我花了一些时间优化块读取性能和解析。
因此,如果您想要做到这一点,您可以编写以下内容:
procedure TForm1.Button1Click(Sender: TObject);
var
ts:TTextStream;
s:String;
begin
ts := TTextStream.Create('c:\temp\test.txt', fm_OpenReadShared);
try
while not ts.Eof do begin
s := ts.ReadLine;
doSomethingWith(s);
end;
finally
ts.Free;
end;
end;
好的。看起来很容易,是吧?确实如此。而且它甚至还有一个文件模式标志(注意那里的read-shared选项?)。现在你所需要的就是TTextStream的代码,它们在这里:
unit textStreamUnit;
{$M+}
{$R-}
interface
uses
Windows,
Classes,
SysUtils;
const
cQuote = #34;
cLf = #10;
cCR = #13;
fm_APPEND_FLAG = $20000;
fm_REWRITE_FLAG = $10000;
fm_Append = fmOpenReadWrite or fm_APPEND_FLAG;
fm_OpenReadShared = fmOpenRead or fmShareDenyWrite;
fm_OpenRewrite = fmOpenReadWrite or fm_REWRITE_FLAG;
fm_Truncate = fmCreate or fm_REWRITE_FLAG;
fm_Rewrite = fmCreate or fm_REWRITE_FLAG;
TextStreamReadChunkSize = 8192;
resourcestring
RsECannotReadFile = 'Cannot read file %';
type
ETextStreamException = class(Exception);
{$ifndef UNICODE}
RawByteString=AnsiString;
{$endif}
TTextStream = class(TObject)
private
FStream: TFileStream;
FFilename: string;
FStreamBuffer: PAnsiChar;
FStreamIndex: Integer;
FStreamSize: Integer;
FLastReadFlag: Boolean;
procedure _StreamReadBufInit;
public
function ReadLine: RawByteString;
procedure Append;
procedure Rewrite;
procedure Write(const s: RawByteString);
procedure WriteLine(const s: RawByteString);
procedure WriteChar(c: AnsiChar);
procedure WriteCrLf;
function Eof: Boolean;
constructor Create(const FileName: string; Mode: DWORD = fm_OpenReadShared; Rights: Cardinal = 0); reintroduce; virtual;
destructor Destroy; override;
function Size: Int64;
property Filename: string read FFilename;
property Stream: TFileStream read FStream;
end;
implementation
function GetFileSizeEx(h: HFILE; FileSize: PULargeInteger): BOOL; stdcall; external Kernel32;
procedure TTextStream.Append;
begin
Stream.Seek(0, soFromEnd);
end;
constructor TTextStream.Create(const FileName: string; Mode: DWORD; Rights: Cardinal);
var
IsAppend: Boolean;
IsRewrite: Boolean;
begin
inherited Create;
FFilename := FileName;
FLastReadFlag := False;
IsAppend := (Mode and fm_APPEND_FLAG) <> 0;
IsRewrite := (Mode and fm_REWRITE_FLAG) <> 0;
FStream := TFileStream.Create(Filename, Word(Mode), Rights);
if IsAppend then
Self.Append
else
Stream.Position := 0;
if IsRewrite then
Rewrite;
_StreamReadBufInit;
end;
destructor TTextStream.Destroy;
begin
if Assigned(FStream) then
FStream.Position := 0;
FreeAndNil(FStream);
FreeMem(FStreamBuffer);
inherited Destroy;
end;
function TTextStream.Eof: Boolean;
begin
if not Assigned(FStream) then
Result := False
else
Result := FLastReadFlag and (FStreamIndex >= FStreamSize);
end;
function TTextStream.ReadLine: RawByteString;
var
Buf: array of AnsiChar;
n: Integer;
QuoteFlag: Boolean;
LStreamBuffer: PAnsiChar;
LStreamSize: Integer;
LStreamIndex: Integer;
procedure FillStreamBuffer;
begin
FStreamSize := Stream.Read(LStreamBuffer[0], TextStreamReadChunkSize);
LStreamSize := FStreamSize;
if LStreamSize = 0 then
begin
if FStream.Position >= FStream.Size then
FLastReadFlag := True
else
raise ETextStreamException.CreateResFmt(@RsECannotReadFile, [FFilename]);
end
else
if LStreamSize < TextStreamReadChunkSize then
FLastReadFlag := True;
FStreamIndex := 0;
LStreamIndex := 0;
end;
begin
SetLength(Buf, 150);
n := 0;
QuoteFlag := False;
LStreamBuffer := FStreamBuffer;
LStreamSize := FStreamSize;
LStreamIndex := FStreamIndex;
while True do
begin
if n >= Length(Buf) then
SetLength(Buf, n + 100);
if LStreamIndex >= LStreamSize then
FillStreamBuffer;
if LStreamIndex >= LStreamSize then
Break;
Buf[n] := LStreamBuffer[LStreamIndex];
Inc(LStreamIndex);
case Buf[n] of
cQuote:
QuoteFlag := not QuoteFlag;
cLf:
if not QuoteFlag then
Break;
cCR:
begin
if not QuoteFlag then
begin
if LStreamIndex >= LStreamSize then
FillStreamBuffer;
if LStreamBuffer[LStreamIndex] = cLf then
Inc(LStreamIndex);
Break;
end;
end
end;
Inc(n);
end;
FStreamIndex := LStreamIndex;
SetString(Result, PAnsiChar(@Buf[0]), n);
end;
procedure TTextStream.Rewrite;
begin
if Assigned(FStream) then
FStream.Size := 0;
end;
function TTextStream.Size: Int64;
begin
if Assigned(FStream) then
GetFileSizeEx(FStream.Handle, PULargeInteger(@Result))
else
Result := 0;
end;
procedure TTextStream.Write(const s: RawByteString);
begin
Stream.Write(s[1], Length(s));
end;
procedure TTextStream.WriteChar(c: AnsiChar);
begin
Stream.Write(c, SizeOf(AnsiChar));
end;
procedure TTextStream.WriteCrLf;
begin
WriteChar(#13);
WriteChar(#10);
end;
procedure TTextStream.WriteLine(const s: RawByteString);
begin
Write(s);
WriteCrLf;
end;
procedure TTextStream._StreamReadBufInit;
begin
if not Assigned(FStreamBuffer) then
begin
GetMem(FStreamBuffer, TextStreamReadChunkSize);
end;
end;
end.
当设置文本时,该值将被解析为子字符串,每当遇到回车符或换行符时就会分隔。(两者不需要成对出现)。
这至少从Delphi5开始就是一样的。 - LU RD