Reduce lines of code in function to read files in Delphi [closed] - delphi

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
This question does not appear to be about programming within the scope defined in the help center.
Closed 8 years ago.
Improve this question
I'm trying to cut the lines of this code and I do not see how the code I show is the minimum of lines that could achieve:
function read_file(FileName: String): AnsiString;
var
F: File;
Buffer: AnsiString;
Size: Integer;
ReadBytes: Integer;
DefaultFileMode: Byte;
begin
Result := '';
DefaultFileMode := FileMode;
FileMode := 0;
AssignFile(F, FileName);
Reset(F, 1);
if (IOResult = 0) then
begin
Size := FileSize(F);
while (Size > 1024) do
begin
SetLength(Buffer, 1024);
BlockRead(F, Buffer[1], 1024, ReadBytes);
Result := Result + Buffer;
Dec(Size, ReadBytes);
end;
SetLength(Buffer, Size);
BlockRead(F, Buffer[1], Size);
Result := Result + Buffer;
CloseFile(F);
end;
FileMode := DefaultFileMode;
end;
is there any way to reduce more lines?

Like this:
function read_file(const FileName: String): AnsiString;
var
Stream: TFileStream;
begin
Stream := TFileStream.Create(FileName, fmOpenRead);
try
SetLength(Result, Stream.Size);
Stream.ReadBuffer(Pointer(Result)^, Stream.Size);
finally
Stream.Free;
end;
end;
In modern Delphi the TFile class has static methods that can do this as a one liner. Although not directly into an AnsiString.
As well as being shorter I perceive the following additional benefits:
Avoiding Pascal I/O in favour of modern streams.
Error handling by exceptions, taken care of by the stream class.
A single allocation of the string variable as opposed to repeated inefficient re-allocations.
If you must do this with Pascal I0 use a single allocation.
SetLength(Buffer, FileSize(F));
BlockRead(F, Pointer(Result)^, Length(Result), ReadBytes);

If you insist on using old-style I/O, the following function is probably the smallest one you can do and still handle errors appropriately (if appropriately handling errors is to return an empty string).
function read_file(const FileName: String): AnsiString;
var
F: File;
DefaultFileMode: Byte;
begin
DefaultFileMode := FileMode;
try
FileMode := 0;
AssignFile(F, FileName);
{$I-}
Reset(F, 1);
{$I+}
if IoResult=0 then
try
SetLength(Result,FileSize(F));
if Length(Result)>0 then begin
{$I-}
BlockRead(F,Result[1],LENGTH(Result));
{$I+}
if IoResult<>0 then Result:='';
end;
finally
CloseFile(F);
end;
finally
FileMode := DefaultFileMode;
end;
end;

Related

When I examine more than 9000 files with this Delphi code , I am having Error :stream Read Error [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 4 years ago.
Improve this question
When I examine more than 9000 files with the following Delphi code, I get this error:
Please help me fix this error.
I use Delphi 10.2 Tokyo, and a MacBook with 8 gb RAM and an i5 CPU.
Freeandnill tested
Stream Free;
MemStr Free;
It looks like your post is mostly code; please add some more details I have not more info
Tested
// detect image type
var
Form1: TForm1;
JPG_HEADER: array[0..2] of byte = ($FF, $D8, $FF);
GIF_HEADER: array[0..2] of byte = ($47, $49, $46);
BMP_HEADER: array[0..1] of byte = ($42, $4D);
PNG_HEADER: array[0..3] of byte = ($89, $50, $4E, $47);
TIF_HEADER: array[0..2] of byte = ($49, $49, $2A);
TIF_HEADER2: array[0..2] of byte = (77, 77, 00);
type
TImageType = (ifUnknown, ifJPG, ifGIF, ifBMP, ifPNG, ifTIF);
implementation
{$R *.dfm}
function TypeToStr(ImageType: TImageType): String;
begin
case ImageType of
ifJPG: Result := 'Image/JPEG';
ifGIF: Result := 'Image/GIF';
ifPNG: Result := 'Image/PNG';
ifBMP: Result := 'Image/BMP';
ifTIF: Result := 'Image/TIFF';
else
Result := 'Unknown Type';
end;
end;
function GetImageType(FileName: String): TImageType;
var
Stream: TFileStream;
MemStr: TMemoryStream;
buf: integer;
tmp: string;
begin
Result := ifUnknown;
Stream := TFileStream.Create(FileName, fmOpenRead);
MemStr := TMemoryStream.Create;
try
MemStr.CopyFrom(Stream, 5);
if MemStr.Size > 4 then
begin
// uncomment these lines to detect "unknown types"
// MemStr.Position:=0;
// MemStr.Read(buf,1);
// showmessage(inttostr(ord(buf)));
// MemStr.Read(buf,1);
// showmessage(inttostr(ord(buf)));
// MemStr.Read(buf,1);
// showmessage(inttostr(ord(buf)));
if CompareMem(MemStr.Memory, #JPG_HEADER, SizeOf(JPG_HEADER)) then
Result := ifJPG
else if CompareMem(MemStr.Memory, #GIF_HEADER, SizeOf(GIF_HEADER)) then
Result := ifGIF
else if CompareMem(MemStr.Memory, #PNG_HEADER, SizeOf(PNG_HEADER)) then
Result := ifPNG
else if CompareMem(MemStr.Memory, #BMP_HEADER, SizeOf(BMP_HEADER)) then
Result := ifBMP
else if CompareMem(MemStr.Memory, #TIF_HEADER, SizeOf(TIF_HEADER)) then
Result := ifTIF
else if CompareMem(MemStr.Memory, #TIF_HEADER2, SizeOf(TIF_HEADER2)) then
Result := ifTIF;
end;
finally
Stream.Free;
MemStr.Free;
end;
end;
//Run Cod
procedure TForm1.Button1Click(Sender: TObject);
var
FileName: String;
it: TImageType;
begin
if OpenDialog1.Execute then
begin
FileName := OpenDialog1.FileName;
it := GetImageType(FileName);
Label1.Caption := TypeToStr(it);
end;
end;
You are asking the TStream.CopyFrom() method to read exactly 5 bytes. Internally, it uses the TStream.ReadBuffer() method, which raises a stream error if the exact number of bytes requested is not read. For instance, if you try to read from a file that is less than 5 bytes in size.
In comments, you show that you have a loop that calls GetFileSize() before calling GetImageType(). But that loop is checking the file size for <> 0 when it should be checking for >= 5 instead. Your TForm1.Button1Click() method is not checking GetFileSize() at all before calling GetImageType().
That being said, in GetImageType(), you don't need the TMemoryStream at all. Use a local byte[] array instead, and call the TFileStream.Read() method (not ReadBuffer()!) to populate it. The return value tells you the actual number of bytes read. Use that size when checking your image signatures. You don't need GetFileSize() at all (which BTW, is easier to implement using SysUtils.FindFirst() instead of actually opening the file and querying its size). GetImageType() should simply return ifUnknown if the requested file cannot be accessed (wrap the TFileStream.Create in a try/except) or is too small.
Try this:
type
TImageType = (ifUnknown, ifJPG, ifGIF, ifBMP, ifPNG, ifTIF);
...
function TypeToStr(ImageType: TImageType): String;
begin
case ImageType of
ifJPG: Result := 'Image/JPEG';
ifGIF: Result := 'Image/GIF';
ifPNG: Result := 'Image/PNG';
ifBMP: Result := 'Image/BMP';
ifTIF: Result := 'Image/TIFF';
else
Result := 'Unknown Type';
end;
end;
function GetImageType(FileName: String): TImageType;
const
JPG_HEADER: array[0..2] of byte = ($FF, $D8, $FF);
GIF_HEADER: array[0..2] of byte = ($47, $49, $46);
BMP_HEADER: array[0..1] of byte = ($42, $4D);
PNG_HEADER: array[0..3] of byte = ($89, $50, $4E, $47);
TIF_HEADER: array[0..2] of byte = ($49, $49, $2A);
TIF_HEADER2: array[0..2] of byte = ($4D, $4D, $00);
var
Stream: TFileStream;
buf: array[0..3] of Byte;
bufsize: Integer;
function MatchesSignature(const signature; signatureSize: Integer): Boolean;
begin
Result := (bufsize >= signatureSize) and CompareMem(#buf, #signature, signatureSize);
end;
begin
Result := ifUnknown;
try
Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
bufsize := Stream.Read(buf, SizeOf(buf));
if bufsize < 2 then Exit;
finally
Stream.Free;
end;
except
Exit;
end;
// uncomment these lines to detect "unknown types"
// ShowMessage(IntToStr(buf[0]));
// Showmessage(IntToStr(buf[1]));
// Showmessage(IntToStr(buf[2]));
if MatchesSignature(JPG_HEADER, SizeOf(JPG_HEADER)) then
Result := ifJPG
else if MatchesSignature(GIF_HEADER, SizeOf(GIF_HEADER)) then
Result := ifGIF
else if MatchesSignature(PNG_HEADER, SizeOf(PNG_HEADER)) then
Result := ifPNG
else if MatchesSignature(BMP_HEADER, SizeOf(BMP_HEADER)) then
Result := ifBMP
else if MatchesSignature(TIF_HEADER, SizeOf(TIF_HEADER)) then
Result := ifTIF
else if MatchesSignature(TIF_HEADER2, SizeOf(TIF_HEADER2)) then
Result := ifTIF;
end;
You perform MemStr.CopyFrom(Stream, 5); without checking whether stream size is large enough.
So opening of empty or too short file gives that error.
Stream error arises due to reading beyond the end of file.
Try to check Stream.Size before copying

Find and Replace Text in a Large TextFile (Delphi XE5)

I am trying to find and replace text in a text file. I have been able to do this in the past with methods like:
procedure SmallFileFindAndReplace(FileName, Find, ReplaceWith: string);
begin
with TStringList.Create do
begin
LoadFromFile(FileName);
Text := StringReplace(Text, Find, ReplaceWith, [rfReplaceAll, rfIgnoreCase]);
SaveToFile(FileName);
Free;
end;
end;
The above works fine when a file is relatively small, however; when the the file size is something like 170 Mb the above code will cause the following error:
EOutOfMemory with message 'Out of memory'
I have tried the following with success, however it takes a long time to run:
procedure Tfrm_Main.button_MakeReplacementClick(Sender: TObject);
var
fs : TFileStream;
s : AnsiString;
//s : string;
begin
fs := TFileStream.Create(edit_SQLFile.Text, fmOpenread or fmShareDenyNone);
try
SetLength(S, fs.Size);
fs.ReadBuffer(S[1], fs.Size);
finally
fs.Free;
end;
s := StringReplace(s, edit_Find.Text, edit_Replace.Text, [rfReplaceAll, rfIgnoreCase]);
fs := TFileStream.Create(edit_SQLFile.Text, fmCreate);
try
fs.WriteBuffer(S[1], Length(S));
finally
fs.Free;
end;
end;
I am new to "Streams" and working with buffers.
Is there a better way to do this?
Thank You.
You have two mistakes in first code example and three - in second example:
Do not load whole large file in memory, especially in 32bit application. If file size more than ~1 Gb, you always get "Out of memory"
StringReplace slows with large strings, because of repeated memory reallocation
In second code you don`t use text encoding in file, so (for Windows) your code "think" that file has UCS2 encoding (two bytes per character). But what you get, if file encoding is Ansi (one byte per character) or UTF8 (variable size of char)?
Thus, for correct find&replace you must use file encoding and read/write parts of file, as LU RD said:
interface
uses
System.Classes,
System.SysUtils;
type
TFileSearchReplace = class(TObject)
private
FSourceFile: TFileStream;
FtmpFile: TFileStream;
FEncoding: TEncoding;
public
constructor Create(const AFileName: string);
destructor Destroy; override;
procedure Replace(const AFrom, ATo: string; ReplaceFlags: TReplaceFlags);
end;
implementation
uses
System.IOUtils,
System.StrUtils;
function Max(const A, B: Integer): Integer;
begin
if A > B then
Result := A
else
Result := B;
end;
{ TFileSearchReplace }
constructor TFileSearchReplace.Create(const AFileName: string);
begin
inherited Create;
FSourceFile := TFileStream.Create(AFileName, fmOpenReadWrite);
FtmpFile := TFileStream.Create(ChangeFileExt(AFileName, '.tmp'), fmCreate);
end;
destructor TFileSearchReplace.Destroy;
var
tmpFileName: string;
begin
if Assigned(FtmpFile) then
tmpFileName := FtmpFile.FileName;
FreeAndNil(FtmpFile);
FreeAndNil(FSourceFile);
TFile.Delete(tmpFileName);
inherited;
end;
procedure TFileSearchReplace.Replace(const AFrom, ATo: string;
ReplaceFlags: TReplaceFlags);
procedure CopyPreamble;
var
PreambleSize: Integer;
PreambleBuf: TBytes;
begin
// Copy Encoding preamble
SetLength(PreambleBuf, 100);
FSourceFile.Read(PreambleBuf, Length(PreambleBuf));
FSourceFile.Seek(0, soBeginning);
PreambleSize := TEncoding.GetBufferEncoding(PreambleBuf, FEncoding);
if PreambleSize <> 0 then
FtmpFile.CopyFrom(FSourceFile, PreambleSize);
end;
function GetLastIndex(const Str, SubStr: string): Integer;
var
i: Integer;
tmpSubStr, tmpStr: string;
begin
if not(rfIgnoreCase in ReplaceFlags) then
begin
i := Pos(SubStr, Str);
Result := i;
while i > 0 do
begin
i := PosEx(SubStr, Str, i + 1);
if i > 0 then
Result := i;
end;
if Result > 0 then
Inc(Result, Length(SubStr) - 1);
end
else
begin
tmpStr := UpperCase(Str);
tmpSubStr := UpperCase(SubStr);
i := Pos(tmpSubStr, tmpStr);
Result := i;
while i > 0 do
begin
i := PosEx(tmpSubStr, tmpStr, i + 1);
if i > 0 then
Result := i;
end;
if Result > 0 then
Inc(Result, Length(tmpSubStr) - 1);
end;
end;
var
SourceSize: int64;
procedure ParseBuffer(Buf: TBytes; var IsReplaced: Boolean);
var
i: Integer;
ReadedBufLen: Integer;
BufStr: string;
DestBytes: TBytes;
LastIndex: Integer;
begin
if IsReplaced and (not(rfReplaceAll in ReplaceFlags)) then
begin
FtmpFile.Write(Buf, Length(Buf));
Exit;
end;
// 1. Get chars from buffer
ReadedBufLen := 0;
for i := Length(Buf) downto 0 do
if FEncoding.GetCharCount(Buf, 0, i) <> 0 then
begin
ReadedBufLen := i;
Break;
end;
if ReadedBufLen = 0 then
raise EEncodingError.Create('Cant convert bytes to str');
FSourceFile.Seek(ReadedBufLen - Length(Buf), soCurrent);
BufStr := FEncoding.GetString(Buf, 0, ReadedBufLen);
if rfIgnoreCase in ReplaceFlags then
IsReplaced := ContainsText(BufStr, AFrom)
else
IsReplaced := ContainsStr(BufStr, AFrom);
if IsReplaced then
begin
LastIndex := GetLastIndex(BufStr, AFrom);
LastIndex := Max(LastIndex, Length(BufStr) - Length(AFrom) + 1);
end
else
LastIndex := Length(BufStr);
SetLength(BufStr, LastIndex);
FSourceFile.Seek(FEncoding.GetByteCount(BufStr) - ReadedBufLen, soCurrent);
BufStr := StringReplace(BufStr, AFrom, ATo, ReplaceFlags);
DestBytes := FEncoding.GetBytes(BufStr);
FtmpFile.Write(DestBytes, Length(DestBytes));
end;
var
Buf: TBytes;
BufLen: Integer;
bReplaced: Boolean;
begin
FSourceFile.Seek(0, soBeginning);
FtmpFile.Size := 0;
CopyPreamble;
SourceSize := FSourceFile.Size;
BufLen := Max(FEncoding.GetByteCount(AFrom) * 5, 2048);
BufLen := Max(FEncoding.GetByteCount(ATo) * 5, BufLen);
SetLength(Buf, BufLen);
bReplaced := False;
while FSourceFile.Position < SourceSize do
begin
BufLen := FSourceFile.Read(Buf, Length(Buf));
SetLength(Buf, BufLen);
ParseBuffer(Buf, bReplaced);
end;
FSourceFile.Size := 0;
FSourceFile.CopyFrom(FtmpFile, 0);
end;
how to use:
procedure TForm2.btn1Click(Sender: TObject);
var
Replacer: TFileSearchReplace;
StartTime: TDateTime;
begin
StartTime:=Now;
Replacer:=TFileSearchReplace.Create('c:\Temp\123.txt');
try
Replacer.Replace('some текст', 'some', [rfReplaceAll, rfIgnoreCase]);
finally
Replacer.Free;
end;
Caption:=FormatDateTime('nn:ss.zzz', Now - StartTime);
end;
Your first try creates several copies of the file in memory:
it loads the whole file into memory (TStringList)
it creates a copy of this memory when accessing the .Text property
it creates yet another copy of this memory when passing that string to StringReplace (The copy is the result which is built in StringReplace.)
You could try to solve the out of memory problem by getting rid of one or more of these copies:
e.g. read the file into a simple string variable rather than a TStringList
or keep the string list but run the StringReplace on each line separately and write the result to the file line by line.
That would increase the maximum file size your code can handle, but you will still run out of memory for huge files. If you want to handle files of any size, your second approach is the way to go.
No - I don't think there's a faster way that the 2nd option (if you want a completely generic search'n'replace function for any file of any size). It may be possible to make a faster version if you code it specifically according to your requirements, but as a general-purpose search'n'replace function, I don't believe you can go faster...
For instance, are you sure you need case-insensitive replacement? I would expect that this would be a large part of the time spent in the replace function. Try (just for kicks) to remove that requirement and see if it doesn't speed up the execution quite a bit on large files (this depends on how the internal coding of the StringReplace function is made - if it has a specific optimization for case-sensitive searches)
I believe refinement of Kami's code is needed to account for the string not being found, but the start of a new instance of the string might occur at the end of the buffer. The else clause is different:
if IsReplaced then begin
LastIndex := GetLastIndex(BufStr, AFrom);
LastIndex := Max(LastIndex, Length(BufStr) - Length(AFrom) + 1);
end else
LastIndex :=Length(BufStr) - Length(AFrom) + 1;
Correct fix is this one:
if IsReplaced then
begin
LastIndex := GetLastIndex(BufStr, AFrom);
LastIndex := Max(LastIndex, Length(BufStr) - Length(AFrom) + 1);
end
else
if FSourceFile.Position < SourceSize then
LastIndex := Length(BufStr) - Length(AFrom) + 1
else
LastIndex := Length(BufStr);

Why does writing to a file fail when it's opened with FILE_FLAG_NO_BUFFERING?

I'm trying to measure the effects of FILE_FLAG_WRITE_THROUGH and FILE_FLAG_NO_BUFFERING on a sequence of writes in a file, as request in another question. But I've found that I can't write a file with FILE_FLAG_NO_BUFFERING set.
When I use it, Delphi returns EWriteError with message stream read error.
The code used is below:
procedure TForm1.btn1Click(Sender: TObject);
var
fsFSArquivoAAC: TFileStream;
L, lastErr: Cardinal;
R: WideString;
hn: THandle;
begin
hn := Windows.CreateFile( PChar('TesteAAC.AAC2'),
GENERIC_READ or GENERIC_WRITE,
FILE_SHARE_READ or FILE_SHARE_WRITE, nil, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL or FILE_FLAG_WRITE_THROUGH or FILE_FLAG_NO_BUFFERING, 0);
lastErr := GetLastError();
if (lastErr <> ERROR_SUCCESS) then
begin
if (lastErr <> ERROR_ALREADY_EXISTS ) then
begin
MessageDlg('Whoops, something went wrong with CreateFile!',
mtError, [mbOK], 0);
end
else
begin
SetLastError(ERROR_SUCCESS);
end;
end;
fsFSArquivoAAC := TFileStream.Create( hn );
try
R := 'BatatinhaquandoNasceEspalharamapelochao';
// write WideString
L := Length(R);
fsFSArquivoAAC.WriteBuffer(L, SizeOf(integer));
if L > 0 then
fsFSArquivoAAC.WriteBuffer(R[1], L * SizeOf(WideChar));
finally
fsFSArquivoAAC.Free;
end;
If you comment FILE_FLAG_NO_BUFFERING the code works. Why?
If you use FILE_FLAG_WRITE_THROUGH and FILE_FLAG_NO_BUFFERING there are various requirements for aligning your buffers in memory, aligning your writes with disk sectors and (I think) writing in multiples of sector sizes. You don't seem to be doing any of these things.

Is it possible to delete bytes from the beginning of a file?

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?
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;
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;

Why do I get access violations using Mike Heydon's TStringBuilder class?

I am using a TStringBuilder class ported from .Net to Delphi 7.
And here is my code snippet:
procedure TForm1.btn1Click(Sender: TObject);
const
FILE_NAME = 'PATH TO A TEXT FILE';
var
sBuilder: TStringBuilder;
I: Integer;
fil: TStringList;
sResult: string;
randInt: Integer;
begin
randomize;
sResult := '';
for I := 1 to 100 do
begin
fil := TStringList.Create;
try
fil.LoadFromFile(FILE_NAME);
randInt := Random(1024);
sBuilder := TStringBuilder.Create(randInt);
try
sBuilder.Append(fil.Text);
sResult := sBuilder.AsString;
finally
sBuilder.free;
end;
mmo1.Text := sResult;
finally
FreeAndNil(fil);
end;
end;
showmessage ('DOne');
end;
I am experiencing AV errors. I can alleviate the problem when I create memory with the size multiple by 1024, however sometimes it still occurs.
Am I doing something wrong?
Your code is fine. The TStringBuilder code you're using is faulty. Consider this method:
procedure TStringBuilder.Append(const AString : string);
var iLen : integer;
begin
iLen := length(AString);
if iLen + FIndex > FBuffMax then _ExpandBuffer;
move(AString[1],FBuffer[FIndex],iLen);
inc(FIndex,iLen);
end;
If the future length is too long for the current buffer size, the buffer is expanded. _ExpandBuffer doubles the size of the buffer, but once that's done, it never checks whether the new buffer size is sufficient. If the original buffer size is 1024, and the file you're loading is 3 KB, then doubling the buffer size to 2048 will still leave the buffer too small in Append, and you'll end up overwriting 1024 bytes beyond the end of the buffer.
Change the if to a while in Append.

Resources