2011-12-23 22 views
6

Tôi có một tệp văn bản lớn (khoảng 100MB) và mỗi dòng được phân cách bằng ký tự CR chứ không phải CRLF.Làm thế nào để khắc phục sự cố khi tải CR chỉ phân tách văn bản tệp trong Delphi 7?

Tôi đã cố gắng đọc tệp văn bản này, từng dòng bằng TStringList.LoadFromFile() hoặc ReadLn (F, ..), nhưng cả hai phương thức đều yêu cầu các dòng được phân tách bằng CRLF.

Bạn có phương pháp hiệu quả và nhanh nào để đọc loại tệp văn bản này không?

Cảm ơn.

PS: Tôi đang sử dụng Delphi 7.

Trả lời

8

Điều này sẽ thực hiện. Đọc tệp văn bản vào luồng bộ nhớ. Sau đó điền vào danh sách chuỗi với nội dung. textList.Text chấp nhận bất kỳ kết hợp nào của CR, LFCRLF để tạo thành một dòng.

function MemoryStreamToString(M : TMemoryStream) : string; 
begin 
    SetString(Result,PChar(M.Memory),M.Size div SizeOf(Char)); // Works in all Delphi versions 
end; 

var 
    memStream : TMemoryStream; 
    textList : TStringList; 
begin 
    textList := TStringList.Create; 
    try 
    memStream:= TMemoryStream.Create; 
    try 
     memStream.LoadFromFile('mytextfile.txt'); 
     textList.Text := MemoryStreamToString(memStream); // any combination of CR,LF,CRLF interprets as a line 
    finally 
     memStream.Free; 
    end; 
    // do something with textList 

    finally 
    textList.Free; 
    end; 

end; 
+0

+1 rất đẹp thực sự –

+0

Tôi đã nhận lỗi biên dịch "Loại không tương thích" trên SetString (Kết quả, M.Memory, M.Size); – ewlung

+0

Tôi đổi thành SetString (Result, PChar (M.Memory), Integer (M.Size)); và nó đã được biên soạn. Nhưng, nếu tôi kiểm tra textList.count, kết quả là 1. Vì vậy, việc chuyển đổi không chính xác. Các tệp văn bản chứa nhiều dòng, mỗi dòng kết thúc bằng CR. Hàm của bạn chuyển đổi toàn bộ dòng thành một chuỗi, điều này là sai. – ewlung

0

Nếu tôi không nhầm bạn cần phải thiết lập thuộc tính linebreak của stringlist trước khi đọc văn bản từ tập tin.

.... 
const 
    CR = #13; 
    LF = #10; 
    LFCR = #10#13; 
begin 
    MyStringList.LineBreak:= CR; 
    MyStringList.LoadFromFile(..... 

Xem: http://docwiki.embarcadero.com/VCL/XE2/en/Classes.TStrings.LineBreak

Không chắc chắn 100% Delphi 7 hỗ trợ này (chỉ cần kiểm tra, D2007 không vì vậy tôi nghi ngờ D7 sẽ quá).

+0

Không, Delphi 7 không có điều này, thật không may. – ewlung

0

Xem nếu điều này giúp:

https://stackoverflow.com/a/2957614/1046041

Tại cái nhìn đầu tiên, có vẻ như bạn có thể thay đổi trong mã nhân vật EOL đến cái gì khác sau đó # 13 # 10.

Nó cũng phân tích từng dòng (bạn có thể sử dụng bộ đệm này) thay vì tải toàn bộ tệp vào bộ nhớ (có thể là sự cố đối với tệp 100MB +).

+0

Đang tải nội dung TStrings từ tập tin giống như bộ nhớ tham lam khi tải thuộc tính Văn bản của nó. – OnTheFly

4

Tôi luôn muốn có giải pháp cho vấn đề này và vì vậy tôi đã viết một giải pháp, là một phần của JvCsvDataSet. Vấn đề của tôi là:

  1. Tôi muốn đọc tệp có thể có CR, CR + LF hoặc chỉ kết thúc LF.
  2. Tôi muốn một cái gì đó như ReadLn, nhưng thực sự linh hoạt về điểm số 1 và không có vấn đề nổi tiếng của ReadLn. Pascal cổ đại có kiểu Textfile và thủ tục ReadLn. Cần có một lớp tương đương hiện đại.
  3. Tôi muốn nó là một đối tượng giống như dòng để tôi có thể đọc từng dòng và không tải toàn bộ tệp dung lượng 3,7 GB của tôi vào bộ nhớ. Ngoài ra, tôi muốn Vị trí là loại Int64 và tôi muốn có thể xử lý các tệp rất lớn (> 2 gb).
  4. Tôi muốn điều này để làm việc trong Delphi 7, và cũng trong Delphi XE2, và tất cả mọi thứ ở giữa.
  5. Tôi muốn nó rất rất nhanh. Vì vậy, tôi đã dành thời gian tối ưu hóa hiệu suất đọc khối và phân tích cú pháp.

Vì vậy, đây là những gì bạn sẽ viết nếu bạn muốn làm điều này:

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; 

Được rồi.Điều đó có vẻ dễ dàng phải không? Nó là. Và nó thậm chí có một lá cờ chế độ tập tin (thông báo tùy chọn đọc chia sẻ ở đó?). Bây giờ tất cả những gì bạn cần là Teh Codez Đối với TTextStream, ở đây:

unit textStreamUnit; 
{$M+} 


{$R-} 

{ 
    textStreamUnit 

    This code is based on some of the content of the JvCsvDataSet written by Warren Postma, and others, 
    licensed under MOZILLA Public License. 
} 

interface 

uses 
    Windows, 
    Classes, 
    SysUtils; 


const 
    cQuote = #34; 
    cLf = #10; 
    cCR = #13; 

{ File stream mode flags used in TTextStream } 

    { Significant 16 bits are reserved for standard file stream mode bits. } 
    { Standard system values like fmOpenReadWrite are in SysUtils. } 
    fm_APPEND_FLAG = $20000; 
    fm_REWRITE_FLAG = $10000; 

    { combined Friendly mode flag values } 
    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; // 8k chunk reads. 

resourcestring 
    RsECannotReadFile = 'Cannot read file %'; 


type 
    ETextStreamException = class(Exception); 

{$ifndef UNICODE} 
    RawByteString=AnsiString; 
{$endif} 

    TTextStream = class(TObject) 
    private 
    FStream: TFileStream; // Tried TJclFileStream also but it was too slow! Do NOT use JCL streams here. -wpostma. 
    FFilename: string; 
    FStreamBuffer: PAnsiChar; 
    FStreamIndex: Integer; 
    FStreamSize: Integer; 
    FLastReadFlag: Boolean; 

    procedure _StreamReadBufInit; 
    public 
    function ReadLine: RawByteString; { read a string, one per line, wow. Text files. Cool eh?} 

    procedure Append; 
    procedure Rewrite; 

    procedure Write(const s: RawByteString);  {write a string. wow, eh? } 
    procedure WriteLine(const s: RawByteString); {write string followed by Cr+Lf } 

    procedure WriteChar(c: AnsiChar); 

    procedure WriteCrLf; 
    //procedure Write(const s: string); 

    function Eof: Boolean; {is at end of file? } 

    { MODE is typically a fm_xxx constant thatimplies a default set of stream mode bits plus some extended bit flags that are specific to this stream type.} 
    constructor Create(const FileName: string; Mode: DWORD = fm_OpenReadShared; Rights: Cardinal = 0); reintroduce; virtual; 
    destructor Destroy; override; 

    function Size: Int64; //override; // sanity 

    { read-only properties at runtime} 
    property Filename: string read FFilename; 
    property Stream: TFileStream read FStream; { Get at the underlying stream object} 
    end; 

implementation 





// 2 gigabyte file limit workaround: 
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, {16 lower bits only}Word(Mode), Rights); 

    //Stream := FStream; { this makes everything in the base class actually work if we inherited from Easy Stream} 

    if IsAppend then 
    Self.Append // seek to the end. 
    else 
    Stream.Position := 0; 

    if IsRewrite then 
    Rewrite; 

    _StreamReadBufInit; 
end; 

destructor TTextStream.Destroy; 
begin 
    if Assigned(FStream) then 
    FStream.Position := 0; // avoid nukage 
    FreeAndNil(FStream); 
    FreeMem(FStreamBuffer); // Buffered reads for speed. 
    inherited Destroy; 
end; 

function TTextStream.Eof: Boolean; 
begin 
    if not Assigned(FStream) then 
    Result := False 
    //Result := True 
    else 
    Result := FLastReadFlag and (FStreamIndex >= FStreamSize); 
    //Result := FStream.Position >= FStream.Size; 
end; 

{ TTextStream.ReadLine: 
    This reads a line of text, normally terminated by carriage return and/or linefeed 
    but it is a bit special, and adapted for CSV usage because CR/LF characters 
    inside quotes are read as a single line. 

    This is a VERY PERFORMANCE CRITICAL function. We loop tightly inside here. 
    So there should be as few procedure-calls inside the repeat loop as possible. 


} 
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 
    { Ignore linefeeds, read until carriage return, strip carriage return, and return it } 
    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: {34} // quote 
     QuoteFlag := not QuoteFlag; 
     cLf: {10} // linefeed 
     if not QuoteFlag then 
      Break; 
     cCR: {13} // carriage return 
     begin 
      if not QuoteFlag then 
      begin 
      { If it is a CRLF we must skip the LF. Otherwise the next call to ReadLine 
       would return an empty line. } 
      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;// truncate! 
end; 

function TTextStream.Size: Int64; { Get file size } 
begin 
    if Assigned(FStream) then 
    GetFileSizeEx(FStream.Handle, PULargeInteger(@Result)) {int64 Result} 
    else 
    Result := 0; 
end; 

{ Look at this. A stream that can handle a string parameter. What will they think of next? } 
procedure TTextStream.Write(const s: RawByteString); 
begin 
    Stream.Write(s[1], Length(s)); {The author of TStreams would like you not to be able to just write Stream.Write(s). Weird. } 
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 
    //FStreamBuffer := AllocMem(TextStreamReadChunkSize); 
    GetMem(FStreamBuffer, TextStreamReadChunkSize); 
    end; 
end; 

end. 
+0

Điều này làm việc hoàn hảo trong Delphi XE2, nơi các câu trả lời khác không thành công. Cảm ơn Warren. Vụ nổ từ quá khứ :) –

+0

Tuyệt vời. Tôi yêu lớp học tiện ích nhỏ này. –

Các vấn đề liên quan