7

そのため、独自の別のデータ構造を作成することをいじっています。ようやく動作するようになりましたが、古い方法に比べてメモリ使用量がとてつもなく高いことに気付きました。

これをテストするために、同じテスト アプリを作成しましたが、データは PVirtualNodes に保存します。

それぞれ 1000 個の子を持つ 1000 個のルートを追加すると、個別のデータ構造は約 208 MB を使用していましたが、PVirtualNode は約 160 MB しか使用していませんでした。また、わずかに高速でした。

別のデータ構造を使用すると、メモリの使用量が減り、高速になるはずだと思っていましたが、代わりにそれが代償だと思いますか?

「PVirtualNode にデータを保存する」のソースは次のとおりです: http://pastebin.com/j6L2eHJt

「別のデータ構造にデータを保存する」のソースは次のとおりです: http://pastebin.com/iSwR0hW1

4

1 に答える 1

21

個別のデータ構造のコードで最初に目にする問題は、カテゴリがusersを所有するという誤った設定になっていることです。つまり、同じ物理的な人物が 2 つのカテゴリに属している場合、データ構造では、2 人の完全に別々の人物であるかのように表示されます。その決定を後悔しながら、プロジェクトの生涯を過ごすことになります。

ユーザーのリストと、カテゴリのリストが必要です。ユーザーには、それが属するカテゴリのリストを含めることができます。または、カテゴリには、それに属するユーザーのリストを含めることができます。多分両方。

type
  TUser = class;
  TCategory = class;
  TContactList = class
  private
    FUsers: TObjectList<TUser>;
    FCategories: TObjectList<TCategory>;
  end;

  TUser = class
  private
    FCategories: TObjectList<TCategory>;
  public
    constructor Create(const DisplayName: string; SkypeID: Integer);
    property DisplayName: string;
    property SkypeID: Integer;
    property Categories: TObjectList<TCategory> read FCategories;
  end;

  TCategory = class
  private
    FUsers: TObjectList<TUser>;
  public
    constructor Create(const Name: string; ID: Integer);
    property Name: string;
    property ID: Integer;
    property Users: TObjectList<TUser> read FUsers;
  end;

constructor TContactList.Create;
begin
  // The contact list is the single master list of all contacts; it
  // owns the user objects, so set OwnsObjects = True
  FUsers := TObjectList<TUser>.Create(True);
  FCategories := TObjectList<TCategory>.Create(True);
end;

constructor TUser.Create;
begin
  // A user does not own its categories; set OwnsObjects = False
  FCategories := TObjectList<TCategory>.Create(False);
end;

constructor TCategory.Create;
begin
  // A category does not own its members; set OwnsObjects = False
  FUsers := TObjectList<TUser>.Create(False);
end;

このコードがツリー コントロールについて言及していないことに注目してください。連絡先リストはツリー コントロールを所有していません。もしそうなら、あなたは数ヶ月前に始めたところ、つまり複数のツリー コントロールでユーザーを表示する方法に問題があったところに戻っていることになります。また、ユーザーは複数のカテゴリに表示される可能性がありますが、そのユーザーを所有する単一のカテゴリはありません。代わりに、連絡先リストがユーザーを所有し、それらを参照するためのアクセス許可をカテゴリに付与し、その逆も同様です。

このプロジェクトを開始したときの最初の問題の 1 つは、ツリー コントロール内の重複要素を検出する方法でした。ツリー コントロールがまったくないため、この問題ははるかに簡単になります。フラットなユーザー リストで重複を見つけるだけです。最初にそのリストに重複を追加しないと、より複雑な GUI コントロールで重複を見つけることを心配する必要がなくなります。

データ構造がツリーではないことに注意してください。これは 2 つのリストであり、リスト内の各項目は反対側のリストから任意の数の項目を参照できます。その意味では、それは実際にはグラフです。データをツリーとして表示するだけです。そうしないと視覚化するのが難しいからです。

ツリー コントロールから独立したデータ構造ができたので、ツリーを表示するデータにどのようにリンクさせるのでしょうか。各ノードは、それが表すTUserまたはへの参照を保持する必要があります。TCategoryノードのデータ レコードは次のように定義できます。

type
  PNodeData = ^TNodeData;
  TNodeData = record
  case Integer of
    0: Obj: TObject;
    1: User: TUser;
    2: Category: TCategory;
  end;

OnGetTextこれを使用して、次のようにツリーのイベントを実装できます。

procedure TJeffForm.TreeGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
  Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
var
  Data: PNodeData;
begin
  if TextType = ttStatic then
    Exit;
  Data := Sender.GetNodeData(Node);
  Assert(Assigned(Data), 'Node wasn''t initialized properly');
  Assert(Assigned(Data.Obj), 'Node has no object');

  if Data.Obj is TUser then
    CellText := Data.User.DisplayName
  else if Data.Obj is TCategory then
    CellText := Data.Category.Name
  else
    CellText := Format('Unknown node type %s', [Data.Obj.ClassName]);
end;

つまり、各ノードにはユーザーまたはカテゴリが含まれます。配列の最初の要素には、これらの型の 1 つである値が含まれますが、どちらになるかは事前にわからないため、いずれかとして安全に使用できる 3 番目の型がありますTObjectAddNewNodeノードが完全に初期化される前であっても、を呼び出すときに設定できるフィールドであるため、レコードの最初のフィールドに必要なデータを含めることが重要です。

長いノード作成時間を回避する秘訣の 1 つは、不要なノードを作成しないようにすることです。最上位ノードが折りたたまれている場合、実際にその子を作成する必要はありません。最上位ノードに子があることを示すフラグを設定するだけで、「+」ボタンで正しくペイントされます。ユーザーが後でノードを展開するためにボタンをクリックすると、ツリー コントロールはいくつの子を持つを尋ね、その時点でそれらを作成します。その場合でも、すぐにペイントする必要があるノードのみを初期化します。必要になるまで作業を遅らせます。100 万の連絡先を持っている人は、一度にすべての連絡先を見たいとは思わないでしょう。そのため、GUI コントロールに何百万もの項目を作成する必要はありません。

プログラムが起動したら、最初にユーザー リストとカテゴリ リストをロードし、ツリーのカテゴリ カウントを設定するだけです。

Tree.RootNodeCount := ContactList.Categories.Count;

それで全部です。

ツリーのイベントは、初期化フェーズの残りを処理します。カテゴリ ノードの一部を最初から展開したい場合は、それらを展開するだけです。ツリーのイベントは、各ノードが持つ子の数を尋ねます。そのイベントを実装して答えることができます。ツリーがそれらのノードを作成すると、それらを初期化する方法を尋ねられ、その時点で対応するユーザーまたはカテゴリ オブジェクトをノードに割り当てることができます。さらに情報が必要な場合は、ツリーが通知します。求められる以上のものを与える必要はありません。

于 2011-05-13T14:59:38.453 に答える