开发者

FileOpen, FileRead, FileWrite

How to properly work with FileRead, FileWrite, buffers (or with TFileStream).

I need to read a whole text file t开发者_开发问答o a String, then to write back a String to this file (replace it with a new string).


TStringList is what you want if you need to deal with the file on a per-line basis.

If you just want to treat it as a single string blob then there is TStringStream.

Stream := TStringStream.Create('', TEncoding.UTF8);
Try
  Stream.LoadFromFile('c:\desktop\in.txt');
  ShowMessage(Stream.DataString);

  Stream.Clear;
  Stream.WriteString('Greetings earthlings!');
  Stream.SaveToFile('c:\desktop\out.txt');
Finally
  Stream.Free;
End;


The simplest, most fool-proof way to read a file into a string is to use a TStringList like so:

sl := TStringList.Create;
try
    sl.LoadFromFile('C:\myfile.txt');

    //String can be read and modified using the Text property:
    oldString := sl.Text;
    sl.Text := 'foo bar';

    //Text can be written back to the file using:
    sl.WriteToFile('C:\myfile.txt');
finally
    sl.Free;
end;

As has been noted in the comments below your question, this could end up transforming line breaks within your string - so depending on what you're trying to read / do, you will need to look out for this.


Some of my utility routines (you can download the code in full from my web site)...

//------------------------------------------------------------------------------
// CsiStrToBytes
//
// Convert pInStr to an array of bytes using the string encoding
// pStringEncoding (one of automatic, Ansi, UTF-16, or UTF-8) and optionally
// include the byte order mark according to the pIncludeBom flag
//------------------------------------------------------------------------------
function CsiStrToBytes(const pInStr: string;
                       pStringEncoding: TECsiStringEncoding;
                       pIncludeBom: Boolean): TByteDynArray;
var
{$IFDEF UNICODE}
  lStringEncoding: TECsiStringEncoding;
  lStringStream: TStringStream;
  lPreambleBytes: TBytes;
  lStringBytes: TBytes;
  lPreambleLen: Integer;
  lStringLen: Integer;
{$ENDIF}
  lLen: Integer;
{$IFDEF UNICODE}
  lIndex: Integer;
{$ENDIF}
begin
  if pInStr <> '' then begin
{$IFDEF UNICODE}
    if pStringEncoding = seAuto then
      lStringEncoding := CsiGetPreferredEncoding(pInStr)
    else
      lStringEncoding := pStringEncoding;

    // UTF-8 and UTF-16 encoding can be handled by the TStringStream class
    if (lStringEncoding = seUtf8) or (lStringEncoding = seUtf16) then begin
      if lStringEncoding = seUtf8 then
        lStringStream := TStringStream.Create(pInStr, TEncoding.Utf8)
      else
        lStringStream := TStringStream.Create(pInStr, TEncoding.Unicode);
      try
        // add the UTF-8 or UTF-16 byte order mark to the start of the array of
        // bytes if required
        if pIncludeBom then
          lPreambleBytes := lStringStream.Encoding.GetPreamble
        else
          SetLength(lPreambleBytes, 0);
        lStringBytes := lStringStream.Bytes;
        lPreambleLen := Length(lPreambleBytes);
        lStringLen := Length(lStringBytes);
        SetLength(Result, lPreambleLen + lStringLen);
        if lPreambleLen > 0 then
          Move(lPreambleBytes[0], Result[0], lPreambleLen);
        if lStringLen > 0 then
          Move(lStringBytes[0], Result[lPreambleLen], lStringLen);
      finally
        lStringStream.Free;
      end;

    end else begin
{$ENDIF}
      // Ansi encoding must be handled manually
      lLen := Length(pInStr);
      SetLength(Result, lLen);
{$IFDEF UNICODE}
      for lIndex := 1 to lLen do
        Result[lIndex - 1] := Ord(pInStr[lIndex]) and $00ff;
{$ELSE}
      Move(pInStr[1], Result[0], lLen);
{$ENDIF}
{$IFDEF UNICODE}
    end;
{$ENDIF}

  end else
    SetLength(Result, 0);
end;

//------------------------------------------------------------------------------
// CsiSaveToFile
//
// Saves pData, an array of bytes, to pFileName
//------------------------------------------------------------------------------
procedure CsiSaveToFile(const pData: TByteDynArray; const pFileName: string);
var
  lFileStream: TFileStream;
  lLen: Integer;
begin
  lFileStream := TFileStream.Create(pFileName, fmCreate);
  try
    lLen := Length(pData);
    if lLen > 0 then
      lFileStream.WriteBuffer(pData[0], lLen);
  finally
    lFileStream.Free;
  end;
end;

//------------------------------------------------------------------------------
// CsiSaveToFile
//
// Saves pText to pFileName using the string encoding pStringEncoding, which is
// one of automatic, Ansi, UTF-16, or UTF-8
//------------------------------------------------------------------------------
procedure CsiSaveToFile(const pText: string; const pFileName: string;
                        pStringEncoding: TECsiStringEncoding);
begin
  CsiSaveToFile(CsiStrToBytes(pText, pStringEncoding), pFileName);
end;


Here are two functions doing what you want:

function StringFromFile(const FileName: TFileName): RawByteString;
var F: THandle;
    Size: integer;
begin
  result := '';
  if FileName='' then
    exit;
  F := FileOpen(FileName,fmOpenRead or fmShareDenyNone);
  if PtrInt(F)>=0 then begin
{$ifdef LINUX}
    Size := FileSeek(F,0,soFromEnd);
    FileSeek(F,0,soFromBeginning);
{$else}
    Size := GetFileSize(F,nil);
{$endif}
    SetLength(result,Size);
    if FileRead(F,pointer(Result)^,Size)<>Size then
      result := '';
    FileClose(F);
  end;
end;

function FileFromString(const Content: RawByteString; const FileName: TFileName;
  FlushOnDisk: boolean=false): boolean;
var F: THandle;
    L: integer;
begin
  result := false;
  F := FileCreate(FileName);
  if PtrInt(F)<0 then
    exit;
  if pointer(Content)<>nil then
    L := FileWrite(F,pointer(Content)^,length(Content)) else
    L := 0;
  result := (L=length(Content));
{$ifdef MSWINDOWS}
  if FlushOnDisk then
    FlushFileBuffers(F);
{$endif}
  FileClose(F);
end;

They use low-level FileOpen/FIleSeek/FileRead/FileWrite functions.

And you can specify any fmShare* option you need.

It uses a RawByteString type, so it expects the text to be handled in a bytes-oriented way. It won't work with Unicode text file, but with Ansi Text. You'll have to set the appropriate code page if you want to interact with it using the string type since Delphi 2009.

Before Delphi 2009, just define:

type
  RawByteString = AnsiString;


Added requested example of working with FileXXX function family too. Note on using RawByteString and file I/O - it is perfectly valid. Another note: for brevity in the low-level code i omitted some error-checking assertions for less likely occurring errors. Caveat emptor!

const
  FileName = 'Unit14.pas';  // this program is a stuntmaster,
                            // reads and writes its own source

procedure TForm14.FormClick(Sender: TObject);
var
  Stream: TFileStream;
  Buffer: RawByteString;
begin
  Stream := TFileStream.Create(FileName, fmOpenReadWrite or fmShareExclusive);
  // read entire file into string buffer
  SetLength(Buffer, Stream.Size);
  Stream.ReadBuffer(Buffer[1], Stream.Size);

  // do something with string
  OutputDebugString(PChar(Format('Buffer = "%s"', [Buffer])));

  // prepare to write
  Stream.Position := 0;   // rewind file pointer
  Stream.Size := 0;       // truncate the file

  // write entire string into the file
  Stream.WriteBuffer(Buffer[1], Length(Buffer));

  Stream.Free;
end;

// on right click - do exactly the same but using low-level FileXXX calls
procedure TForm14.FormContextPopup(Sender: TObject; MousePos: TPoint; var
    Handled: Boolean);
var
  Handle: Integer;
  Size: Cardinal;
  Buffer: RawByteString;
  Transferred: Integer;
begin
  Handle := FileOpen(FileName, fmOpenReadWrite or fmShareExclusive);
  Assert(Handle >= 0);

  // read entire file into string buffer
  Size := GetFileSize(Handle, nil);
  Assert(Size <> INVALID_FILE_SIZE);
  SetLength(Buffer, Size);
  Transferred := FileRead(Handle, Buffer[1], Size);
  Assert(not (Transferred < Size));

  // do something with string
  OutputDebugString(PChar(Format('Buffer = "%s"', [Buffer])));

  // prepare to write
  FileSeek(Handle, 0, 0); // rewind file pointer
  SetEndOfFile(Handle);   // truncate the file

  // write entire string into the file
  Transferred := FileWrite(Handle, Buffer[1], Length(Buffer));
  Assert(not (Transferred < Length(Buffer)));

  FileClose(Handle);
end;
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜