【问题标题】:Is it possible to delete bytes from the beginning of a file?是否可以从文件开头删除字节?
【发布时间】:2012-03-24 18:39:31
【问题描述】:

我知道我可以有效地截断文件并从文件末尾删除字节。

有没有相应的高效截断文件的方法,从文件开头删除内容到文件中间的某个点?

【问题讨论】:

    标签: delphi file truncate


    【解决方案1】:

    当我阅读您要求从文件开头删除内容的问题时。换句话说,您希望删除文件开头的内容并将剩余内容下移。

    这是不可能的。您只能从末尾截断文件,而不能从开头截断。您需要将剩余的内容复制到一个新文件中,或者您自己将其复制到同一个文件中。

    无论你怎么做,都没有捷径有效的方法可以做到这一点。您必须复制数据,例如@kobik 所描述的。

    Raymond Chen 就这个话题写了一篇不错的文章:How do I delete bytes from the beginning of a file?


    只是为了好玩,这里有一个基于流的方法的简单实现,用于从文件中的任何位置删除内容。您可以将其与读/写文件流一起使用。我没有测试代码,我会留给你的!

    procedure DeleteFromStream(Stream: TStream; Start, Length: Int64);
    var
      Buffer: Pointer;
      BufferSize: Integer;
      BytesToRead: Int64;
      BytesRemaining: Int64;
      SourcePos, DestPos: Int64;
    begin
      SourcePos := Start+Length;
      DestPos := Start;
      BytesRemaining := Stream.Size-SourcePos;
      BufferSize := Min(BytesRemaining, 1024*1024*16);//no bigger than 16MB
      GetMem(Buffer, BufferSize);
      try
        while BytesRemaining>0 do begin
          BytesToRead := Min(BufferSize, BytesRemaining);
          Stream.Position := SourcePos;
          Stream.ReadBuffer(Buffer^, BytesToRead);
          Stream.Position := DestPos;
          Stream.WriteBuffer(Buffer^, BytesToRead);
          inc(SourcePos, BytesToRead);
          inc(DestPos, BytesToRead);
          dec(BytesRemaining, BytesToRead);
        end;
        Stream.Size := DestPos;
      finally
        FreeMem(Buffer);
      end;
    end;
    

    【讨论】:

    • 实际上,对稀疏文件的引用是相当具有误导性的。这不是关于删除偏移0处的N个字节,而是关于设置 0..N-1字节的区域为零(或任何预设的填充字节值)
    • @user 对不起,有什么误导?
    • 你太棒了!谢谢大卫,做到了!
    【解决方案2】:

    一个非常简单的解决方案是从“目标位置偏移量”移动(移动)数据块 朝向 BOF,然后修剪(截断)剩菜:

    --------------------------
    |******|xxxxxx|yyyyyy|zzz|
    --------------------------
    BOF  <-^ (target position offset)
    
    
    --------------------------
    |xxxxxx|yyyyyy|zzz|******|
    --------------------------
                      ^ EOF
    

    由于@David 发布了基于TStream 的代码,以下是一些基于“低级”I/O 帕斯卡风格的代码:

    function FileDeleteFromBOF(const FileName: string; const Offset: Cardinal): Boolean;
    var
      Buf: Pointer;
      BufSize, FSize,
      NumRead, NumWrite,
      OffsetFrom, OffsetTo: Cardinal;
      F: file;
    begin
      {$IOCHECKS OFF}
      Result := False;
      AssignFile(F, FileName);
      try
        FileMode := 2; // Read/Write
        Reset(F, 1); // Record size = 1
        FSize := FileSize(F);
        if (IOResult <> 0) or (Offset >= FSize) then Exit;
        BufSize := Min(Offset, 1024 * 64); // Max 64k - This value could be optimized
        GetMem(Buf, BufSize);
        try
          OffsetFrom := Offset;
          OffsetTo := 0;
          repeat
            Seek(F, OffsetFrom);
            BlockRead(F, Buf^, BufSize, NumRead);
            if NumRead = 0 then Break;
            Seek(F, OffsetTo);
            BlockWrite(F, Buf^, NumRead, NumWrite);
            Inc(OffsetFrom, NumWrite);
            Inc(OffsetTo, NumWrite);
          until (NumRead = 0) or (NumWrite <> NumRead) or (OffsetFrom >= FSize);
          // Truncate and set to EOF
          Seek(F, FSize - Offset);
          Truncate(F);
          Result := IOResult = 0;
        finally
          FreeMem(Buf);
        end;
      finally
        CloseFile(F);
      end;
    end;
    

    【讨论】:

    • 非常聪明的想法 Kobik,但我可以在实践中这样做吗? (我的意思是德尔福)
    • 当然可以:BlockRead/Seek/BlockWrite... 或直接使用TFileStream
    • 图片+1。这应该消除关于 OP 到底想要什么的不确定性。
    • 谢谢 Kobik,我不确定如何接受这两个答案(我是 SO 新手),但你的版本也很棒!
    • 你不能。我的代码从 bof 中删除。您只需将流大小设置为 size-1