1

我有一个 TPersistent 对象的集合。我想通过它们的内存位置(作为主索引)和它们的属性(使用 rtti)来索引它们。一个集合可能有几个基于属性的索引(基于不同的属性)。

解决这个问题的策略和数据结构是什么,所以在处理基于属性的索引时,我可以从集合中删除无效对象(那些已经被销毁的对象)而不会发生访问冲突?

编辑:为了澄清事情,我希望实现这种接口(类和方法):

type
  TMyItem=class(TPersistent)
  public
    property HasProp[Name: string]: Boolean read GetHasProp;
    property PropValue[Name: string]: Variant read GetPropValue write SetPropValue;
  end;

  TMyCollection=class(TPersistent)
  private
    FMainList: TSortedList;
    FIndexes : TIndexes;
  public
    function Add(AItem: TMyItem): Integer;
    procedure Extract(AItem: TMyItem);
    procedure Delete(AItem: TMyItem);

    // and new sorted list based on the given property name to the FIndexes
    procedure AddIndex(const APropName: string);
    // remove sorted list correspoding to the given property name from FIndexes
    procedure RemoveIndex(const APropName: string);

    // just to see if the item is in the collection
    function Find(AItem: TMyItem): Boolean;
    // try to find first item which property specified by APropName has value specified by APropValue
    function Find(const APropName: string; const APropValue: Variant; var AItem: TMyItem): Boolean;
  end;

FMainList 包含指向 TMyItem 实例的指针列表。通过将指针转换为 NativeInt 进行排序。因此,如果我找到无效的对象,那将没有问题。但是在基于属性的索引中,我根据 TMyItems 的属性值对它们进行排序。因此,如果我试图找到无效 TMyItem 的条目(已经被销毁的条目),将引发 EAccessViolation,因为我需要获取属性值。

我目前解决这个问题的想法是将每个基于属性的索引中 TMyItem 的位置存储到存储在 FMainList 中的主记录中。但是这种方法还需要在每次添加或删除新项目时更新所有位置。这是我想要避免的。那么还有其他更好的机制吗?

4

1 回答 1

2

这类问题通常会导致在保存或计算索引之间进行选择,这与速度和内存之间的选择是同义词。它也取决于使用情况:这些Find例程是否经常调用?

就像您已经说过的那样:将每个索引保存在单独的数组中会带来各种同步问题,以及对内存的大量额外需求。

就个人而言,我会根据要求计算/获取每个指数。当然,迭代所有项目需要一些时间,但是当计数保持在 100K 以下甚至更高的数字时,我相信它不会受到任何延迟的影响。而且,当您基于TCollection类似 menjaraz 评论的设计时,您不必担心已删除的项目:不会有。

更新:

因为您想在需要使用 RTTI 的任意名称的属性中搜索值,所以这个迭代任务可能会慢一些。为了消除这种情况,我为您编写了此优化。它基于排除搜索操作中不具有该属性的项目。为此,我将集合包含的属性名称以及它们所属的类存储在其项目中。唯一的限制是不能有重复的属性名称,但我怀疑无论如何你都会将具有相同属性名称的类组合在一个共同的祖先中。(但是,可以添加具有相同属性名称的类,只要第二个是从第一个继承的。)

unit MyCollection;

interface

uses
  Classes, TypInfo;

type
  TMyItem = class(TCollectionItem)
  end;

  TMyCollection = class(TOwnedCollection)
  private
    FPrevItemClass: TClass;
    FPropList: TStringList;
    function GetItem(Index: Integer): TMyItem;
    procedure RegisterItemClass(AClass: TClass);
    procedure SetItem(Index: Integer; Value: TMyItem);
  protected
    procedure Notify(Item: TCollectionItem;
      Action: TCollectionNotification); override;
  public
    constructor Create(AOwner: TPersistent); virtual;
    destructor Destroy; override;
    function Find(AItem: TMyItem): Boolean; overload;
    function Find(const APropName: String; AValue: Variant;
      var AItem: TMyItem): Boolean; overload;
    property Items[Index: Integer]: TMyItem read GetItem write SetItem;
      default;
  end;

implementation

resourcestring
  SDupPropName = 'Duplicate property name. Only classes with unique ' +
                 'property names are supposed to be added to this collection.';

{ TMyCollection }

constructor TMyCollection.Create(AOwner: TPersistent);
begin
  inherited Create(AOwner, TMyItem);
  FPropList := TStringList.Create;
  RegisterItemClass(TMyItem);
end;

destructor TMyCollection.Destroy;
begin
  FPropList.Free;
  inherited Destroy;
end;

function TMyCollection.Find(AItem: TMyItem): Boolean;
var
  I: Integer;
begin
  for I := 0 to Count - 1 do
    if Items[I] = AItem then
    begin
      Result := True;
      Exit;
    end;
  Result := False;
end;

function TMyCollection.Find(const APropName: String; AValue: Variant;
  var AItem: TMyItem): Boolean;
var
  I: Integer;
  ItemClass: TClass;
begin
  Result := False;
  if FPropList.Find(APropName, I) then
  begin
    ItemClass := TClass(FPropList.Objects[I]);
    for I := 0 to Count - 1 do
      if Items[I] is ItemClass then
        if GetPropValue(Items[I], APropName, False) = AValue then
        begin
          AItem := Items[I];
          Result := True;
        end;
  end;
end;

function TMyCollection.GetItem(Index: Integer): TMyItem;
begin
  Result := TMyItem(inherited GetItem(Index));
end;

procedure TMyCollection.Notify(Item: TCollectionItem;
  Action: TCollectionNotification);
begin
  inherited Notify(Item, Action);
  if Action = cnAdded then
    if Item.ClassType <> FPrevItemClass then
      if FPropList.IndexOfObject(TObject(Item.ClassType)) = -1 then
        RegisterItemClass(Item.ClassType)
end;

procedure TMyCollection.RegisterItemClass(AClass: TClass);
var
  PropCount: Integer;
  PropList: PPropList;
  I: Integer;
  J: Integer;
  PropName: String;
begin
  PropCount := GetTypeData(AClass.ClassInfo)^.PropCount;
  if PropCount > 0 then
  try
    GetPropList(AClass.ClassInfo, PropList);
    for I := 0 to PropCount - 1 do
    begin
      PropName := PropList[I].Name;
      if not FPropList.Find(PropName, J) then
      begin
        FPropList.AddObject(PropName, TObject(AClass));
      end
      else
        if not AClass.InheritsFrom(TClass(FPropList.Objects[J])) then
          raise EInvalidOperation.Create(SDupPropName);
    end;
    FPrevItemClass := AClass;
  finally
    FreeMem(PropList);
  end;
end;

procedure TMyCollection.SetItem(Index: Integer; Value: TMyItem);
begin
  inherited SetItem(Index, Value);
end;

end.
于 2012-01-07T12:01:37.920 回答