10

I know that I can efficiently truncate a file and remove bytes from the end of the file.

Is there a corresponding efficient way to truncate files by deleting content from the beginning of the file to a point in the middle of the file?

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
TheDude
  • 3,045
  • 4
  • 46
  • 95

2 Answers2

14

As I read the question you are asking to remove content from a file starting from the beginning of the file. In other words you wish to delete content at the start of the file and shift the remaining content down.

This is not possible. You can only truncate a file from the end, not from the beginning. You will need to copy the remaining content into a new file, or copy it down yourself within the same file.

However you do it there is no shortcut efficient way to do this. You have to copy the data, for example as @kobik describes.

Raymond Chen wrote a nice article on this topic: How do I delete bytes from the beginning of a file?


Just for fun, here's a simple implementation of a stream based method to delete content from anywhere in the file. You could use this with a read/write file stream. I've not tested the code, I'll leave that to you!

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;
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Actually, reference to sparse file is quite misleading. It is not about **deleting** N bytes at offset 0, but about **setting** region of 0..N-1 bytes to zeroes (or whatever pre-set filler byte value) – OnTheFly Mar 07 '12 at 12:59
  • 2
    @user Sorry, what is misleading? – David Heffernan Mar 07 '12 at 13:00
8

A very simple solution would be to shift (move) blocks of data from the "target position offset" towards BOF, and then trim (truncate) the leftovers:

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


--------------------------
|xxxxxx|yyyyyy|zzz|******|
--------------------------
                  ^ EOF

Since @David posted a code based on TStream, here is some code based on "low level" I/O pascal style:

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
  • 21,001
  • 4
  • 61
  • 121
  • 2
    **Very** clever idea Kobik, but can I do that in practice? (in delphi I mean) – TheDude Mar 07 '12 at 13:47
  • 1
    Sure you can: `BlockRead`/`Seek`/`BlockWrite`... or use `TFileStream` directly. – kobik Mar 07 '12 at 14:02
  • @Gdhami This is all you can do. You have to copy the remaining contents of the file. The point is that there is no trivial efficient way to truncate at the beginning of the file, unlike truncation at the end of the file which is effectively free. – David Heffernan Mar 07 '12 at 14:40
  • 3
    +1 for the picture. This should eliminate the uncertainties that have existed regarding what the OP wants exactly. – Uli Gerhardt Mar 07 '12 at 16:59
  • 1
    Thank you Kobik, I'm not sure how to accept both answers (I'm new to SO), but your version is great too! – TheDude Mar 07 '12 at 19:27
  • @kobik, how i can use your code to delete last character on file? –  Dec 05 '16 at 05:16
  • 1
    You cant. My code deletes from bof. You need to simply set stream size to size-1 – kobik Dec 05 '16 at 11:04