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

发布于 2024-11-07 13:08:30 字数 501 浏览 0 评论 0原文

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

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

当添加 1000 个根(每个根有 1000 个子节点)时,单独的数据结构使用了大约 208 MB,而 PVirtualNode 仅使用了大约 160 MB,而且速度也快了一点。

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

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

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

So I have been messing with creating my own separate data structure. I finally got it working, but then I discovered that the memory usage was ridiculously high compared to the old method.

To test this, I created the same test app, but I would store the data in my PVirtualNodes.

When adding 1000 roots, with 1000 children each, the separate data structure was using around 208 MB, while the PVirtualNode one only used about 160 MB, and it was a wee bit faster as well.

I thought using a separate data structure was supposed to use less memory, and be faster, but I suppose that is the price instead?

Here is the source for the "Store data in PVirtualNode": http://pastebin.com/j6L2eHJt

Here is the source for the "Store data in Seperate Datastructure": http://pastebin.com/iSwR0hW1

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

丶情人眼里出诗心の 2024-11-14 13:08:30

我在单独数据结构代码中看到的第一个问题是,它错误地使类别拥有用户。这意味着,如果同一个人属于两个类别,那么它将在您的数据结构中显示为好像有两个完全独立的人。您将在项目的整个生命周期中对这个决定感到后悔。

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

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;

请注意此代码没有提及树控件。联系人列表不拥有树控件。如果确实如此,那么您就会回到几个月前开始的地方,在那里您遇到了如何在多个树中显示用户的问题控件。另请注意,一个用户可以出现在多个类别中,但没有一个类别拥有该用户。相反,联系人列表拥有用户,并授予类别引用他们的权限,反之亦然。

当您开始此项目时,您遇到的第一个问题是如何检测树控件中的重复元素。现在这个问题就容易多了,因为根本没有树控件。您只需在平面用户列表中查找重复项即可。如果您一开始就不向该列表添加重复项,那么您就不必再担心在更复杂的 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;

仅此而已。

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

The first problem I see with your separate-data-structure code is that it incorrectly makes the category own the users. That means that if the same physical person is in two categories, it will appear in your data structure as though there are two completely separate people. You'll spend the lifetime of the project regretting that decision.

You need a list of users, and you need a list of categories. A user can contain a list of categories that it belongs to, or a category can contain a list of users that belong to it. Maybe both.

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;

Notice how this code makes no mention of the tree control. A contact list does not own a tree control. If it did, then you'd be right back where you started months ago, where you had the problem of how to display users in multiple tree controls. And also notice that a user can appear in more than category, but no single category owns that user. Instead, the contact list owns the user, and grants categories permission to refer to them, and vice versa.

One of your first problems when you started this project was how to detect duplicate elements in the tree control. Now that problem is much easier because there is no tree control at all. You just have to find duplicates in the flat user list. If you don't add duplicates to that list in the first place, then you don't have to worry about finding duplicates in a more complicated GUI control anymore.

Notice that the data structure is not a tree. It's two lists, and each item in the list can refer to any number of items from the opposite list. In that sense, it's actually a graph. You merely display the data as a tree because otherwise it's hard to visualize it.

So, now that you have a data structure that's independent of the tree controls, how do you link the tree to the data it's supposed to display? Each node should hold a reference to the TUser or TCategory that it represents. The node's data record could be defined like this:

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

You can use it to implement the tree's OnGetText event like this:

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;

That is, each node will contain either a user or a category. The first element of the array will contain a value that's one of those types, but since you don't know in advance which it will be, you have a third type that's safe to use as either one, TObject. It's important to have the required data in the first field of the record because that's the field you're allowed to set when you call AddNewNode, even before the node has been completely initialized.

One of the secrets to avoiding long node-creation times is to avoid creating the nodes you don't need. If a top-level node is collapsed, then you don't actually need to create any of its children. Just set the flag on the top node indicating that is has children, and it will correctly paint itself with the "+" button. If the user later clicks the button to expand the node, then the tree control will ask you how many children it has, at which point it will create them. And even then, it will only initialize the nodes that need to be painted immediately. Delay the work until it's necessary. Somebody with a million contacts will probably never want to see all of them at once, so there's no need to create a million items in your GUI control.

When your program starts, all you need to do, at first, is load the user and category lists, and then set the tree's category count:

Tree.RootNodeCount := ContactList.Categories.Count;

That's all.

The tree's event will take care of the rest of the initialization phase. If you want some of the category nodes to be expanded from the start, then all you have to do is expand them. The tree's events will ask you how many children each node has, and you can implement the event to answer. Once the tree has created nodes for them, it will ask how to initialize them, and you can assign the corresponding user or category object to the node at that time. The tree will tell you when it needs more information. You don't have to give it any more than it asks for.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文