单独的数据结构与VirtualStringTree的PVirtualNodes存储数据?

时间:2011-05-13 14:00:46

标签: delphi data-structures memory-management virtualtreeview

所以我一直在搞乱创建自己独立的数据结构。我终于让它工作了,但后来我发现与旧方法相比,内存使用率高得离谱。

为了测试这个,我创建了相同的测试应用程序,但我会将数据存储在我的PVirtualNodes中。

当添加1000个根,每个1000个子节点时,单独的数据结构使用大约208 MB,而PVirtualNode只使用大约160 MB,并且它也快了一点。

我认为使用单独的数据结构应该使用更少的内存,并且更快,但我想这是代价呢?

以下是“在PVirtualNode中存储数据”的来源:http://pastebin.com/j6L2eHJt

以下是“在单独数据结构中存储数据”的来源:http://pastebin.com/iSwR0hW1

1 个答案:

答案 0 :(得分:21)

我在您的单独数据结构代码中看到的第一个问题是它错误地使类别拥有用户。这意味着如果同一个自然人分为两类,它将出现在您的数据结构中,就像有两个完全独立的人一样。你将在项目的整个生命周期中为这个决定感到后悔。

您需要一个用户列表,您需要一个类别列表。用户可以包含它所属的类别列表,或者类别可以包含属于它的用户列表。也许两者都有。

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;

注意这段代码没有提到树控件。联系人列表不拥有树控件。如果确实如此,那么你就会回到几个月前开始的地方,那里有the problem of how to display users in multiple tree controls。并且还注意到用户可以出现在多个类别中,但没有单个类别拥有该用户。相反,联系人列表拥有用户,并授予类别权限以引用它们,反之亦然。

启动此项目时,您遇到的第一个问题是树控件中的how to detect duplicate elements。现在这个问题变得更容易了,因为根本没有树控制。您只需在平面用户列表中找到重复项。如果您没有首先在该列表中添加重复项,那么您不必担心在更复杂的GUI控件中找到重复项。

请注意,数据结构不是树。它是两个列表,列表中的每个项目都可以引用相反列表中的任意数量的项目。从这个意义上说,它实际上是一个图形。您只需将数据显示作为树,否则很难将其显示出来。

那么,既然你有一个独立于树控件的数据结构,你如何将树链接到它应该显示的数据?每个节点都应该包含对其所代表的TUserTCategory的引用。节点的数据记录可以这样定义:

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;

也就是说,每个节点都包含用户或类别。数组的第一个元素将包含一个属于这些类型的值,但由于您事先并不知道它将是哪个,因此您可以使用第三种类型,可以安全地使用它们TObject。在记录的第一个字段中包含所需数据非常重要,因为这是您在调用AddNewNode时允许设置的字段,即使在节点完全初始化之前也是如此。

避免长节点创建时间的一个秘诀是避免创建不需要的节点。如果顶级节点已折叠,则实际上您不需要创建任何子节点。只需在顶部节点上设置标志,指​​示子项,并使用“+”按钮正确绘制自己。如果用户稍后单击按钮以展开节点,则树控件将询问它有多少个子项,此时它将创建它们。即使这样,它也只会初始化需要立即绘制的节点。延迟工作直到必要。拥有一百万个联系人的人可能永远不会想要同时看到所有这些联系人,因此不需要在GUI控件中创建一百万个项目。

当您的程序启动时,您首先需要做的就是加载用户和类别列表,然后设置树的类别计数:

Tree.RootNodeCount := ContactList.Categories.Count;

就是这样。

树的事件将处理初始化阶段的其余部分。如果您想从一开始就扩展某些类别节点,那么您所要做的就是展开它们。树的事件将询问每个节点有多少个孩子,并且您可以实现事件来回答。一旦树为它们创建了节点,它将询问如何初始化它们,并且您可以在那时将相应的用户或类别对象分配给节点。树将告诉您何时需要更多信息。你不必再提供它了。