views:

532

answers:

7

Hello!!!

I have a problem adding strings to a TStringList. I've searched other posts but couldn't find an answer to this.

What I'm trying to do is to add a big amount of strings to a TStringList (more than 14000) but somewhere in the process I get an EAccessViolation. Here's the code I'm using:

procedure TForm1.FormCreate(Sender: TObject);
begin
    List := TStringList.Create;
    List.Duplicates := dupAccept;
end;

procedure TForm1.ButtonStartClick(Sender: TObject);
begin
    List.Clear;
    List.Add('125-AMPLE');
    List.Add('TCUMSON');
    List.Add('ATLV 4300');
    List.Add('150T-15');
    List.Add('TDL-08ZE');
    List.Add('RT20L');
    List.Add('SIN LINEA');
    List.Add('TIARA');
    List.Add('FL200ZK1');
    List.Add('FL250ZK1');
    List.Add('SIN LINEA');
    List.Add('CENTAURO-70 S.P.');
    List.Add('CORSADO');

{ This list continues to about 14000 strings...}

    List.Add('VOSJOD 2');
    List.Add('Z 125');
    List.Add('ZUMY');
    List.Add('NEW AGE 125');
    List.Add('SIN LINEA');
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
    FreeAndNil(List);
end;

¿What's wrong with this code? The list contains duplicate strings so I set the Duplicates property to dupAccept. I was able to load the list using LoadFromFile, but I don't want to have a text file outside my application.

I hope you can help me!!! Please tell me if you need any further information.

Thank you very much. I really appreciate your help.

+2  A: 

What Delphi version are you using? Some older versions had a bug in the memory manager that can cause an access violation when trying to reallocate an array to a size that's too large.

Try adding FastMM4 to your project to replace the old memory manager and see if that helps.

Also, you're probably better off keeping the list in an external file. Yes, it's another file, but it also means that you can change the list without having to recompile the entire program. This also makes creating (and distributing!) updates easier.

Mason Wheeler
The thing is, 14,000 elements — 54.6 KB — isn't really all that large an array. And furthermore, TStrings uses the same allocation strategy in LoadFromFile as would be used by a bunch of calls to Add. There's something else going on.
Rob Kennedy
@Mason: It's Delphi 5.
TheSandman
@Rob, this is a 14,000+ lines procedure... Quite big and unusual for a single procedure. In the end, the generated code can be way different than with LoadFromfile.
François
François, Mason did not suggest that the length of the *code* would be an issue. He suggested the size of the *array* would give problems. But, assuming the 14,000-line procedure runs correctly, the array will be allocated exactly the same way in either case. LoadFromFile calls LoadFromStream, and that calls SetTextStr, which will just call Add 14,000 times in a loop. Same number of calls to Add, and thus the same array-resizing. Therefore I conclude that the size of the array is irrelevant.
Rob Kennedy
+2  A: 

Mason is probably right for the cause of the AV; this is quite a large array to grow.
On a side note, when doing such a long processing on a StringList, it's recommended to surround it by BeginUpdate/EndUpdate to avoid firing any update event.
Even if you don't have any now, they might be added later and you'll get problems.

François
François: I used the same code above including the BeginUpdate and EndUpdate statements and got the same results...
TheSandman
TheSandman: I don't think François was in any way suggesting Begin/End Update would solve your current problem. He was merely advising it as a good general practice, because if a particular instance of a `TStringList` has code handling the `OnChange` or `OnChanging` events, then you don't want these events firing on each individual change, as it will negatively affect performance.
Craig Young
+1  A: 

Set list.capacity to the number of items you plan to add, immediately after you create the list. Alternatively, place the list in an RC file (named other than with the name of your project) and add it to your project. This gets compiled into your application, but does not involve executable code to create the list.

frogb
A: 

You may also want to try THashedStringList, could see a speed boost (although not in this function), although I'm not sure if the add method is a whole lot different.

Peter Turner
He may have duplicates as indicated from code. Hash will not help here.
Runner
In principle that's true (although the speed penalty is mitigated by the frequency of duplication), but a THashedStringList will accept duplicates.
Peter Turner
The values can be duplicated yes, but the keys cannot. It is by nature how hash works. Only one same key can exists in a hash table. Two different keys however can hash to a same value internally.If THashedStringList is accepting duplicate keys / names that it is broken. Anyway hash would only help if he is searching for items later. Simple adding items to hash is actually slowe than to string list because each key must be hashed.
Runner
Although, it may solve the issue he's having regardless of whether or not it was a good idea.
Peter Turner
+18  A: 

The suggestions for using an external file are on the mark here. However, your post indicates your desire for not having an external file. I would then suggest you link the file to the executable as a resource. You can easily do this following these steps:

Place all the strings into a text file called stringdata.txt (or whatever name you choose). Then create a .rc file of whatever name you choose and put the following into it (STRING_DATA can be any identifier you choose):

STRING_DATA RCDATA "stringdata.txt"

Create a .res file from the .rc:

BRCC32 <name of rc>.rc

Now reference this file from the source code. Place the following someplace in the unit:

{$R <name of res>.res}

Instead of loading from a file stream, load from a resource stream:

StringData := TResourceStream.Create(HInstance, 'STRING_DATA', RT_RCDATA);
try
  List.LoadFromStream(StringData);
finally
  StringData.Free;
end;

If you do command-line automated builds, I would suggest you keep the .rc file under source control and build the .res during the build process. This way you can also keep the stringdata.txt file under source control and any edits are automatically caught on the next build without having to explicitly build the .res file each time the .txt file changes.

Allen Bauer
Thanks for posting this, I was trying to figure out how to do this for embedding a dictionary file in a DLL but I couldn't figure it out from web or helpfiles.
Peter Turner
Allen: I'll try your advise. I'll let you all know if it worked.
TheSandman
Allen: It worked great! A very elegant and easy solution. Thank you very much for all your feedback!
TheSandman
+1  A: 

I would also worry about compiler integrity with a 14,000 line procedure. People have found other cases where going beyond anything reasonable breaks the compiler in various ways.

Loren Pechtel
A: 

try using the following instead of your code to add the strings to the StringList

var
Str: string;
begin
Str := '125-AMPLE' + #13#10;
Str := Str + 'TCUMSON' + #13#10;
Str := Str + 'ATLV 4300' + #13#10;
Str := Str + '150T-15' + #13#10;
................

List.Text := Str;

end;

This will be very slow because of string concatenation. I would not recomend this code.
Runner