这样划算吗?
Is this economical?
只是想看看是否有更好的方法来执行以下操作(总是有更好的方法)因为它确实会在加载时由于数据量而延迟应用程序。
我想用存储在 csv 文件中的数据填充一个记录数组,我目前的数组长度是固定的,但稍后会使其成为动态的,以便我可以添加到 csv 文件中。
type
TStarCoords = Packed record
szSystem: String[40];
fCoordX: Single;
fCoordY: Single;
fCoordZ: Single;
end;
SystemCoords: Array [0 .. 22379] of TStarCoords;
Const
SYSTEMS = 'Data\Systems.csv';
然后我在 oncreate 事件上填充数组
procedure TForm1.FormCreate(Sender: TObject);
var
szFile, sRecord: string;
Row, Index, i: Integer;
slList: TStringList;
begin
szFile := ExtractFilePath(ParamStr(0)) + SYSTEMS;
if FileExists(szFile) then
try
slList := TStringList.Create;
slList.LoadFromFile(szFile);
for Row := 0 to slList.Count - 1 do
begin
sRecord := slList[Row];
index := Pos(',', sRecord);
if index > 0 then
begin
SystemCoords[Row].szSystem := Copy(sRecord, 1, index - 1);
Delete(sRecord, 1, index);
end;
index := Pos(',', sRecord);
if index > 0 then
begin
SystemCoords[Row].fCoordX := StrToFloat(Copy(sRecord, 1, index - 1));
Delete(sRecord, 1, index);
end;
index := Pos(',', sRecord);
if index > 0 then
begin
SystemCoords[Row].fCoordY := StrToFloat(Copy(sRecord, 1, index - 1));
Delete(sRecord, 1, index);
end;
SystemCoords[Row].fCoordZ := StrToFloat(sRecord);
end;
finally
slList.Free;
end;
for i := Low(SystemCoords) to High(SystemCoords) do
begin
cbSystem.Items.Add(SystemCoords[i].szSystem);
end;
end;
如您所见,我正在使用 "Pos" 函数来解析 csv 文件,并在末尾循环数组以将 Star 名称添加到组合框,是否有更经济的方法来执行此操作?
欢迎提出任何建议
您可以使用TStringlist
来解析行。在下文中,我假设您有用逗号分隔的元素。
由于您将记录的字符串表示形式放入组合框中,我向您保证稍后在您的程序中需要采用另一种方式:从字符串中查找 TStarCoords。鉴于我 woyls recoment 你把你的元素放在 TDictionary
instread og 数组中。
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, Generics.Collections, StdCtrls;
type
TStarCoords = packed record
szSystem: string[40];
fCoordX: Single;
fCoordY: Single;
fCoordZ: Single;
end;
const
SYSTEMS = 'Data\Systems.csv';
type
TForm1 = class(TForm)
ComboBox1: TComboBox;
procedure FormCreate(Sender: TObject);
procedure ComboBox1Change(Sender: TObject);
private
SystemCoords: TDictionary<string, TStarCoords>;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.ComboBox1Change(Sender: TObject);
var
StarCoord: TStarCoords;
begin
if not SystemCoords.TryGetValue(ComboBox1.Text, StarCoord) then
exit; //todo : Make some error handling
Caption := FloatToStr(StarCoord.fCoordX);
end;
procedure TForm1.FormCreate(Sender: TObject);
var
Lines, Elements: TStringlist;
Line: string;
SystemCoord: TPair<string, TStarCoords>;
begin
if not FileExists(ExtractFilePath(ParamStr(0)) + SYSTEMS) then
exit; //todo: Some error handling
SystemCoords := TDictionary<string, TStarCoords > .Create;
Lines := TStringlist.Create;
Elements := TStringlist.Create;
Elements.LineBreak := ',';
try
for Line in Lines do
begin
Elements.Text := Line;
SystemCoord.Key := Elements[0];
with SystemCoord.Value do
begin
szSystem := string(Elements[0]);
fCoordX := StrToFloat(Elements[1]);
fCoordY := StrToFloat(Elements[2]);
fCoordZ := StrToFloat(Elements[3]);
end;
SystemCoords.Add(SystemCoord.Key, SystemCoord.Value);
end;
finally
Lines.Free;
Elements.Free;
end;
try
ComboBox1.Items.BeginUpdate;
for SystemCoord in SystemCoords do
ComboBox1.Items.Add(SystemCoord.Key);
finally
ComboBox1.Items.EndUpdate;
end;
end;
end.
看起来效率不高。
- 分配固定长度的全局数组看起来很糟糕。使用在运行时确定长度的动态数组。
- 不推荐使用短字符串。不要在现代编程中使用它们。它们是遗留的,不处理 Unicode。
- 不要打包记录。这会导致数据错位。
- 似乎需要更多的堆分配。如果可以,请避免
Delete
。
- 加载到字符串列表中效率不高。使用基于行 reader 的方法来提高速度。 Delphi 内置于 class 中,虽然是垃圾。如果您想要速度和有效使用内存,请自己动手。
- 可能大部分时间都花在了组合上!将 22380 个项目添加到组合中将花费很长时间。不要那样做。如果数据集较小,则只添加与数据中一样多的项目。否则,请在 UI 控件中使用虚拟范例。
你的下一步是找出瓶颈所在。我们只能猜测,因为我们缺少太多信息。我们不知道数据是否是静态的,有多大等等。
正如其他人所说,可能大部分时间都花在了组合上。
我认为,在处理 TStrings
的大更新时,BeginUpdate
/ EndUpdate
technique proposed by the 是一种有效的方法。
作为一个小问题,如果您的应用程序是唯一写入和读取数据的,并且机器和人类都不关心 CSV 格式,您可以考虑采用不同的文件格式存储记录,使用 BlockRead
and BlockWrite
函数。
type
TStarCoords = record
szSystem: string[40];
fCoordX,
fCoordY,
fCoordZ: Single;
end;
。 . .
const
CFILENAME = '<your path to some file .dat>';
正在读取数据:
procedure TForm1.FormCreate(Sender: TObject);
var
lstStarCoords: TList<TStarCoords>;
f: File;
starCoords: TStarCoords;
begin
lstStarCoords := TList<TStarCoords>.Create;
try
AssignFile(f, CFILENAME);
Reset(f, SizeOf(TStarCoords));
try
while not Eof(f) do begin
BlockRead(f, starCoords, 1);
lstStarCoords.Add(starCoords);
end;
finally
CloseFile(f);
end;
cbSystem.Items.BeginUpdate;
for starCoords in lstStarCoords do
cbSystem.Items.Add(starCoords.szSystem);
cbSystem.Items.EndUpdate;
finally
lstStarCoords.Free;
end;
end;
写入数据:
procedure TForm1.WriteStarCoords;
var
lstStarCoords: TList<TStarCoords>;
f: File;
starCoords: TStarCoords;
i: Integer;
begin
lstStarCoords := TList<TStarCoords>.Create;
try
//let's insert 5k new items
for i:=1 to 5000 do begin
with starCoords do begin
szSystem := 'HYEL YE';
fCoordX := 122;
fCoordY := 12.375;
fCoordZ := 45.75;
end;
lstStarCoords.Add(starCoords);
end;
AssignFile(f, CFILENAME);
Rewrite(f, SizeOf(TStarCoords));
try
for starCoords in lstStarCoords do
BlockWrite(f, starCoords, 1);
finally
CloseFile(f);
end;
finally
lstStarCoords.Free;
end;
end;
EDIT: 使用指针直接在cbSystem
组件中存储记录信息的例子
这种方法稍微 "dangerous" 因为它分配的内存必须手动释放,但允许避免使用 TDictionary
将 TStarCoords.szSystem
与相应的配对记录。
声明一个指向 TStarCoords
记录的新类型:
type
PStarCoords = ^TStarCoords;
正在读取数据:
procedure TForm1.FormCreate(Sender: TObject);
var
lstStarCoords: TStringList;
f: File;
starCoords: PStarCoords;
begin
ClearCbSystem;
lstStarCoords := TStringList.Create(False);
{another minor enhancement:
since lstStarCoords does not own any TObject which needs to be freed
the OwnsObjects property of the TStringList can be set to False
in order to avoid some code to be execute in some method like Clear and Delete}
try
lstStarCoords.BeginUpdate;
AssignFile(f, CFILENAME);
Reset(f, SizeOf(TStarCoords));
try
while not Eof(f) do begin
New(starCoords);
BlockRead(f, starCoords^, 1);
lstStarCoords.AddObject(starCoords^.szSystem, TObject(starCoords));
end;
finally
CloseFile(f);
end;
lstStarCoords.EndUpdate;
cbSystem.Items.Assign(lstStarCoords);
finally
lstStarCoords.Free;
end;
end;
使用cbSystem.Clear
清除列表不会自动处理必须手动释放的底层指针。每次必须清除 cbSystem
列表时使用 ClearCbSystem
过程:
procedure TForm1.ClearCbSystem;
var
i: Integer;
begin
cbSystem.Items.BeginUpdate;
for i := cbSystem.Items.Count-1 downto 0 do
Dispose(PStarCoords(cbSystem.Items.Objects[i]));
cbSystem.Clear;
cbSystem.Items.EndUpdate;
end;
当窗体被销毁时,对 ClearCbSystem
过程的调用确保指针在 cbSystem
组件被应用程序本身释放之前被释放:
procedure TForm1.FormDestroy(Sender: TObject);
begin
ClearCbSystem;
end;
只是想看看是否有更好的方法来执行以下操作(总是有更好的方法)因为它确实会在加载时由于数据量而延迟应用程序。
我想用存储在 csv 文件中的数据填充一个记录数组,我目前的数组长度是固定的,但稍后会使其成为动态的,以便我可以添加到 csv 文件中。
type
TStarCoords = Packed record
szSystem: String[40];
fCoordX: Single;
fCoordY: Single;
fCoordZ: Single;
end;
SystemCoords: Array [0 .. 22379] of TStarCoords;
Const
SYSTEMS = 'Data\Systems.csv';
然后我在 oncreate 事件上填充数组
procedure TForm1.FormCreate(Sender: TObject);
var
szFile, sRecord: string;
Row, Index, i: Integer;
slList: TStringList;
begin
szFile := ExtractFilePath(ParamStr(0)) + SYSTEMS;
if FileExists(szFile) then
try
slList := TStringList.Create;
slList.LoadFromFile(szFile);
for Row := 0 to slList.Count - 1 do
begin
sRecord := slList[Row];
index := Pos(',', sRecord);
if index > 0 then
begin
SystemCoords[Row].szSystem := Copy(sRecord, 1, index - 1);
Delete(sRecord, 1, index);
end;
index := Pos(',', sRecord);
if index > 0 then
begin
SystemCoords[Row].fCoordX := StrToFloat(Copy(sRecord, 1, index - 1));
Delete(sRecord, 1, index);
end;
index := Pos(',', sRecord);
if index > 0 then
begin
SystemCoords[Row].fCoordY := StrToFloat(Copy(sRecord, 1, index - 1));
Delete(sRecord, 1, index);
end;
SystemCoords[Row].fCoordZ := StrToFloat(sRecord);
end;
finally
slList.Free;
end;
for i := Low(SystemCoords) to High(SystemCoords) do
begin
cbSystem.Items.Add(SystemCoords[i].szSystem);
end;
end;
如您所见,我正在使用 "Pos" 函数来解析 csv 文件,并在末尾循环数组以将 Star 名称添加到组合框,是否有更经济的方法来执行此操作?
欢迎提出任何建议
您可以使用TStringlist
来解析行。在下文中,我假设您有用逗号分隔的元素。
由于您将记录的字符串表示形式放入组合框中,我向您保证稍后在您的程序中需要采用另一种方式:从字符串中查找 TStarCoords。鉴于我 woyls recoment 你把你的元素放在 TDictionary
instread og 数组中。
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, Generics.Collections, StdCtrls;
type
TStarCoords = packed record
szSystem: string[40];
fCoordX: Single;
fCoordY: Single;
fCoordZ: Single;
end;
const
SYSTEMS = 'Data\Systems.csv';
type
TForm1 = class(TForm)
ComboBox1: TComboBox;
procedure FormCreate(Sender: TObject);
procedure ComboBox1Change(Sender: TObject);
private
SystemCoords: TDictionary<string, TStarCoords>;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.ComboBox1Change(Sender: TObject);
var
StarCoord: TStarCoords;
begin
if not SystemCoords.TryGetValue(ComboBox1.Text, StarCoord) then
exit; //todo : Make some error handling
Caption := FloatToStr(StarCoord.fCoordX);
end;
procedure TForm1.FormCreate(Sender: TObject);
var
Lines, Elements: TStringlist;
Line: string;
SystemCoord: TPair<string, TStarCoords>;
begin
if not FileExists(ExtractFilePath(ParamStr(0)) + SYSTEMS) then
exit; //todo: Some error handling
SystemCoords := TDictionary<string, TStarCoords > .Create;
Lines := TStringlist.Create;
Elements := TStringlist.Create;
Elements.LineBreak := ',';
try
for Line in Lines do
begin
Elements.Text := Line;
SystemCoord.Key := Elements[0];
with SystemCoord.Value do
begin
szSystem := string(Elements[0]);
fCoordX := StrToFloat(Elements[1]);
fCoordY := StrToFloat(Elements[2]);
fCoordZ := StrToFloat(Elements[3]);
end;
SystemCoords.Add(SystemCoord.Key, SystemCoord.Value);
end;
finally
Lines.Free;
Elements.Free;
end;
try
ComboBox1.Items.BeginUpdate;
for SystemCoord in SystemCoords do
ComboBox1.Items.Add(SystemCoord.Key);
finally
ComboBox1.Items.EndUpdate;
end;
end;
end.
看起来效率不高。
- 分配固定长度的全局数组看起来很糟糕。使用在运行时确定长度的动态数组。
- 不推荐使用短字符串。不要在现代编程中使用它们。它们是遗留的,不处理 Unicode。
- 不要打包记录。这会导致数据错位。
- 似乎需要更多的堆分配。如果可以,请避免
Delete
。 - 加载到字符串列表中效率不高。使用基于行 reader 的方法来提高速度。 Delphi 内置于 class 中,虽然是垃圾。如果您想要速度和有效使用内存,请自己动手。
- 可能大部分时间都花在了组合上!将 22380 个项目添加到组合中将花费很长时间。不要那样做。如果数据集较小,则只添加与数据中一样多的项目。否则,请在 UI 控件中使用虚拟范例。
你的下一步是找出瓶颈所在。我们只能猜测,因为我们缺少太多信息。我们不知道数据是否是静态的,有多大等等。
正如其他人所说,可能大部分时间都花在了组合上。
我认为,在处理 TStrings
的大更新时,BeginUpdate
/ EndUpdate
technique proposed by the
作为一个小问题,如果您的应用程序是唯一写入和读取数据的,并且机器和人类都不关心 CSV 格式,您可以考虑采用不同的文件格式存储记录,使用 BlockRead
and BlockWrite
函数。
type
TStarCoords = record
szSystem: string[40];
fCoordX,
fCoordY,
fCoordZ: Single;
end;
。 . .
const
CFILENAME = '<your path to some file .dat>';
正在读取数据:
procedure TForm1.FormCreate(Sender: TObject);
var
lstStarCoords: TList<TStarCoords>;
f: File;
starCoords: TStarCoords;
begin
lstStarCoords := TList<TStarCoords>.Create;
try
AssignFile(f, CFILENAME);
Reset(f, SizeOf(TStarCoords));
try
while not Eof(f) do begin
BlockRead(f, starCoords, 1);
lstStarCoords.Add(starCoords);
end;
finally
CloseFile(f);
end;
cbSystem.Items.BeginUpdate;
for starCoords in lstStarCoords do
cbSystem.Items.Add(starCoords.szSystem);
cbSystem.Items.EndUpdate;
finally
lstStarCoords.Free;
end;
end;
写入数据:
procedure TForm1.WriteStarCoords;
var
lstStarCoords: TList<TStarCoords>;
f: File;
starCoords: TStarCoords;
i: Integer;
begin
lstStarCoords := TList<TStarCoords>.Create;
try
//let's insert 5k new items
for i:=1 to 5000 do begin
with starCoords do begin
szSystem := 'HYEL YE';
fCoordX := 122;
fCoordY := 12.375;
fCoordZ := 45.75;
end;
lstStarCoords.Add(starCoords);
end;
AssignFile(f, CFILENAME);
Rewrite(f, SizeOf(TStarCoords));
try
for starCoords in lstStarCoords do
BlockWrite(f, starCoords, 1);
finally
CloseFile(f);
end;
finally
lstStarCoords.Free;
end;
end;
EDIT: 使用指针直接在cbSystem
组件中存储记录信息的例子
这种方法稍微 "dangerous" 因为它分配的内存必须手动释放,但允许避免使用 TDictionary
将 TStarCoords.szSystem
与相应的配对记录。
声明一个指向 TStarCoords
记录的新类型:
type
PStarCoords = ^TStarCoords;
正在读取数据:
procedure TForm1.FormCreate(Sender: TObject);
var
lstStarCoords: TStringList;
f: File;
starCoords: PStarCoords;
begin
ClearCbSystem;
lstStarCoords := TStringList.Create(False);
{another minor enhancement:
since lstStarCoords does not own any TObject which needs to be freed
the OwnsObjects property of the TStringList can be set to False
in order to avoid some code to be execute in some method like Clear and Delete}
try
lstStarCoords.BeginUpdate;
AssignFile(f, CFILENAME);
Reset(f, SizeOf(TStarCoords));
try
while not Eof(f) do begin
New(starCoords);
BlockRead(f, starCoords^, 1);
lstStarCoords.AddObject(starCoords^.szSystem, TObject(starCoords));
end;
finally
CloseFile(f);
end;
lstStarCoords.EndUpdate;
cbSystem.Items.Assign(lstStarCoords);
finally
lstStarCoords.Free;
end;
end;
使用cbSystem.Clear
清除列表不会自动处理必须手动释放的底层指针。每次必须清除 cbSystem
列表时使用 ClearCbSystem
过程:
procedure TForm1.ClearCbSystem;
var
i: Integer;
begin
cbSystem.Items.BeginUpdate;
for i := cbSystem.Items.Count-1 downto 0 do
Dispose(PStarCoords(cbSystem.Items.Objects[i]));
cbSystem.Clear;
cbSystem.Items.EndUpdate;
end;
当窗体被销毁时,对 ClearCbSystem
过程的调用确保指针在 cbSystem
组件被应用程序本身释放之前被释放:
procedure TForm1.FormDestroy(Sender: TObject);
begin
ClearCbSystem;
end;