这样划算吗?

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" 因为它分配的内存必须手动释放,但允许避免使用 TDictionaryTStarCoords.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;