Delphi TList записей

31

Мне нужно сохранить временный список записей и думал, что TList будет хорошим способом сделать это? Однако я не уверен, как это сделать с помощью TList и задавался вопросом, было ли это лучше всего, а также если у кого-нибудь есть примеры того, как это сделать?

  • 16
    Никто явно не предложил Generics.Collections.TList<T> . Стоит рассмотреть, на мой взгляд.
  • 0
    @ Давид: Это определенно путь. Я обнаружил, что Generics.Collections.TList <T> намного быстрее, если это большой список записей только для чтения.
Показать ещё 4 комментария
Теги:
record
tlist

7 ответов

28
Лучший ответ

Самый простой способ - создать собственный потомок TList. Здесь представлено быстрое приложение консоли для примера:

program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils, Classes;

type
  PMyRec=^TMyRec;
  TMyRec=record
    Value: Integer;
    AByte: Byte;
  end;

  TMyRecList=class(TList)
  private
    function Get(Index: Integer): PMyRec;
  public
    destructor Destroy; override;
    function Add(Value: PMyRec): Integer;
    property Items[Index: Integer]: PMyRec read Get; default;
  end;

{ TMyRecList }

function TMyRecList.Add(Value: PMyRec): Integer;
begin
  Result := inherited Add(Value);
end;

destructor TMyRecList.Destroy;
var
  i: Integer;
begin
  for i := 0 to Count - 1 do
    FreeMem(Items[i]);
  inherited;
end;

function TMyRecList.Get(Index: Integer): PMyRec;
begin
  Result := PMyRec(inherited Get(Index));
end;

var
  MyRecList: TMyRecList;
  MyRec: PMyRec;
  tmp: Integer;
begin
  MyRecList := TMyRecList.Create;
  for tmp := 0 to 9 do
  begin
    GetMem(MyRec, SizeOf(TMyRec));
    MyRec.Value := tmp;
    MyRec.AByte := Byte(tmp);
    MyRecList.Add(MyRec);
  end;

  for tmp := 0 to MyRecList.Count - 1 do
    Writeln('Value: ', MyRecList[tmp].Value, ' AByte: ', MyRecList[tmp].AByte);
  WriteLn('  Press Enter to free the list');
  ReadLn;
  MyRecList.Free;
end.

Это устраняет несколько вещей:

  • Он обрабатывает освобождение памяти.
  • Вам не нужно вводить все, чтобы использовать его.

Как сказал Реми и Уоррен, это немного больше, потому что вам нужно выделять память при добавлении новых записей.

  • 2
    +1 хороший код Кен
  • 0
    +1 Кен за создание TList потомков TList
Показать ещё 7 комментариев
18

Во-первых, если вы хотите совместить классический TList с записями, вам нужно будет:

  • Выделите свои записи в куче, а не в стеке. Используйте GetMem как Remy.
  • Возьмите адрес записи и добавьте ее в TList.
  • При удалении элемента из списка и его использовании разыщите его:
  • Не забудьте освободить и очистить, потом.

Сочетание списков с записями требует так много "указателей и кучи-управления", что такая техника будет только в пределах возможностей эксперта.

Альтернативы тому, что вы просили, чтобы все еще использовать что-то, называемое TList, включают использование TList стиля generics.collections с типами записей, которые будут иметь все преимущества TList, но потребуют от вас в основном сделать много целых копий для получения данных.

Самые идиоматические способы Delphi делать то, что вы просите, либо:

  • используйте TList или TObjectList с типами классов вместо записи. Обычно вы заканчиваете подклассификацию TList или TObjectList в этом случае.

  • Используйте динамический массив типов записей, но имейте в виду, что сложнее сортировать тип массива и расширять тип массива во время выполнения не так быстро, как с TList.

  • Используйте generics.Collections TList с вашими классами. Это позволяет избежать подкласса TList или TObjectList каждый раз, когда вы хотите использовать список с другим классом.

Пример кода, показывающий динамические массивы:

 TMyRec = record
    ///
 end;

 TMyRecArray = array of TMyRec;

 procedure Demo;
 var
    myRecArray:TMyRecArray;
 begin
    SetLength(myRecArray,10);
 end;

Теперь для некоторой справочной информации о том, почему TList нелегко использовать с типами записей:

TList лучше подходит для использования с типами классов, потому что переменная типа "TMyClass", где "type TMyclass= class... end;" может быть легко "упомянут" как значение указателя, которое принадлежит TList.

Переменные типа Record - это значения-типы в Delphi, тогда как значения класса неявно являются значениями ссылки. Вы можете думать о значениях ссылки в качестве указателей стелса. Вам не нужно разыгрывать их, чтобы получить их содержимое, но когда вы добавляете его в TList, вы фактически добавляете указатель на TList, не создаете копию или не выделяете какую-либо новую память.

Ответ Remy показывает вам буквально, как сделать точно, что вы хотите, и я пишу свой ответ только потому, что хочу предупредить вас о деталях вашего запроса и предложить что вы тоже рассматриваете альтернативы.

  • 1
    @Warren: проблема с массивами в том, что они не так легко сортируются. Я согласен с рекомендацией типа класса, хотя; Вы можете легче управлять ими с помощью TObjectList. (Кстати, ^ теперь почти полностью необязателен; компилятор обрабатывает большую часть этого для вас, и вам это не нужно.)
  • 0
    @Warren: Динамические массивы также не так эффективны в использовании памяти, как TList, когда дело доходит до добавления и удаления элементов в / из списка. TList растет в геометрической прогрессии и эффективно удаляется. Массивы нет. Вы должны будете реализовать эти операции вручную при использовании массивов.
Показать ещё 10 комментариев
11

Вы можете ознакомиться с нашей оболочкой TDynArray. Он определен в модуле с открытым исходным кодом, работающем от Delphi 6 до XE.

С помощью TDynArray вы можете получить доступ к любому динамическому массиву (например, TIntegerDynArray = array of integer или TRecordDynArray = array of TMyRecord) с помощью свойств и методов типа TList, например. Count, Add, Insert, Delete, Clear, IndexOf, Find, Sort и некоторые новые методы, такие как LoadFromStream, SaveToStream, LoadFrom и SaveTo, которые позволяют быстро выполнять двоичную сериализацию любого динамического массива, даже содержащие строки или записи - метод CreateOrderedIndex также доступен для создания отдельного индекса в соответствии с содержимым динамического массива. Вы также можете сериализовать содержимое массива в JSON, если хотите. Slice, Reverse или Copy также доступны.

Он будет обрабатывать динамический массив записей и даже записи в записях со строками или другими динамическими массивами внутри.

При использовании внешней переменной Count вы можете значительно ускорить добавление элементов в указанном динамическом массиве.

type
  TPerson = packed record
    sCountry: string;
    sFullName: string;
    sAddress: string;
    sCity: string;
    sEmployer: string;
  end;
  TPersons = array of TPerson;
var
  MyPeople: TPersons;

(...)
procedure SavePeopleToStream(Stream: TMemoryStream);
var aPeople: TPerson;
    aDynArray: TDynArray;
begin
  aDynArray.Init(TypeInfo(TPersons),MyPeople);
  aPeople.sCountry := 'France';
  aPeople.sEmployer := 'Republique';
  aDynArray.Add(aPeople);
  aDynArray.SaveToStream(Stream);
end; // no try..finally Free needed here

Существует также класс TDynArrayHashed, который позволяет внутреннее хеширование содержимого динамического массива. Он очень быстр и способен хэшировать любые данные (есть стандартные хешеры для строк, но вы можете предоставить свои собственные - даже хэш-функция может быть настроена).

Обратите внимание, что TDynArray и TDynArrayHashed являются только оболочками вокруг существующей переменной динамического массива. Поэтому вы можете инициализировать оболочку TDynArray при необходимости, чтобы более эффективно обращаться к любому родному динамическому массиву Delphi.

  • 0
    @ Кен Уайт (предвкушением :)) Да, это не настоящая реализация TList , я знаю. Но это определенно TList подобная реализация, более быстрая, чем использование TList (поскольку записи распределяются по чанкам). TList НЕ предназначен для хранения записей, но указателей. А в нашей оболочке есть методы, которых у вас нет в TList , такие как хеширование, внутреннее сохранение или загрузка в память или поток, сортировка с помощью внешнего целочисленного поискового индекса, методов Slice и тому подобное. Мы используем это, например, в нашей платформе, например, для хранения кэша скомпилированных операторов SQL в несколько строк кода.
  • 0
    Можно ли загрузить только эту обертку без целого фреймворка?
Показать ещё 1 комментарий
4

Вы можете использовать TList для этого, например:

type
  pRec = ^sRec;
  sRec = record
    Value: Integer;
    ...
  end;

var
  List: TList;
  Rec: pRec;
  I: Integer;
begin
  List := TList.Create;
  try
    for I := 1 to 5 do begin
      GetMem(Rec);
      try
        Rec^.Value := ...;
        ...
        List.Add(Rec);
      except
        FreeMem(Rec);
        raise;
      end;
    end;
    ...
    for I := 0 to List.Count-1 do
    begin
      Rec := pRec(List[I]);
      ...
    end;
    ...
    for I := 0 to List.Count-1 do
      FreeMem(pRec(List[I]));
    List.Clear;
  finally
    List.Free;
  end;
end;
  • 2
    Обратите внимание, что в этом случае Реми решает семантический пробел по значению и по ссылке, объявляя Rec как pRec, который является типом указателя. Я бы сказал, что он должен вызвать эту переменную PointerToARecord или что-то в этом роде. Большинство программистов на Delphi предпочитают избегать прямого использования указателей, где это возможно, поэтому более новые TList <Generic> и классические функции динамических массивов Delphi обычно предпочтительнее.
  • 5
    -1 указатель магии и новички не смешиваются.
Показать ещё 3 комментария
1

Мы столкнулись с аналогичной проблемой здесь с общим списком записей. Надеюсь, что следующий код psuedo поможет.

type
  PPat = ^TPat;
  TPat = record
    data: integer;
  end;

...
var
    AList: TList<PPat>;

...
procedure TForm1.Button1Click(Sender: TObject);
var
  obj: PPat;
begin
  obj := AList[0];
  obj.data := 1;
  Assert(obj.data = AList[0].data);  // correct
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  obj: PPat;
begin
  AList := TList<PPat>.Create;
  GetMem(obj, SizeOf(TPat));  // not shown but need to FreeMem when items are removed from the list
  obj.data := 2;
  AList.Add(obj);
end;
0

Если вы используете более старую версию Delphi, где нет универсальных шаблонов, рассмотрите возможность наследования от TList и переопределите метод Notify. При добавлении элемента выделите память, скопируйте содержимое памяти указателя и переопределите содержимое в списке. При удалении просто освобождаю память.

  TOwnedList = class(TList)
  private
    FPtrSize: integer;
  protected
    procedure Notify(Ptr: Pointer; Action: TListNotification); override;
  public
    constructor Create(const APtrSize: integer);
  end;

  constructor TOwnedList.Create(const APtrSize: integer);
  begin
    inherited Create();
    FPtrSize := APtrSize;
  end;

  procedure TOwnedList.Notify(Ptr: Pointer; Action: TListNotification);
  var
    LPtr: Pointer;
  begin
    inherited;
    if (Action = lnAdded) then begin
      GetMem(LPtr, FPtrSize);
      CopyMemory(LPtr, Ptr, FPtrSize); //May use another copy kind
      List^[IndexOf(Ptr)] := LPtr;
    end else if (Action = lnDeleted) then begin
      FreeMem(Ptr, FPtrSize);
    end;
  end;

Использование:

...
LList := TOwnedList.Create(SizeOf(*YOUR RECORD TYPE HERE*));
...
  • Обратите внимание, что там, где я использовал CopyMemory (LPtr, Ptr, FPtrSize), вы можете использовать другой способ копирования. Мой список предназначен для хранения записи со ссылками указателя, поэтому он не управляет полями памяти.
0

Все зависит от типа данных, которые вы хотите сохранить.

Вы можете использовать TCollection и TCollectionItem.

Здесь (отредактирован) код из рабочего блока, в котором я использовал TCollection для чтения списка определений отчетов из папки. Каждый отчет состоял из своего рода шаблона и оператора SQL, который должен храниться вместе с именем файла.

Поскольку он отредактирован и использует некоторые из моих собственных единиц (TedlFolderRtns читает файлы во внутренний список, чтобы назвать только один), этот пример достаточно прост, чтобы быть полезным. С некоторыми замените все, вы можете адаптироваться к любой вашей потребности.

Посмотрите в TCollection в справке, вы можете много сделать с ней. И это держит вашу обработку кода красиво сгруппированной в классовой структуре.

  unit cReports;
  interface
  uses
     SysUtils, Classes, XMLDoc, XMLIntf, Variants,
     // dlib - Edelcom
     eIntList, eProgSettings,eFolder ;
  type

     TReportDefItem = class(TCollectionItem)
     private
        fSql: string;
        fSkeleton: string;
        fFileName: string;
        procedure Load;
        procedure SetFileName(const Value: string);
     public
        constructor Create(Collection:TCollection); override;
        destructor Destroy ; override;

        property FileName: string read fFileName write SetFileName;
        property Sql : string read fSql write fSql;
        property Skeleton : string read fSkeleton write fSkeleton;
     end;

     TReportDefList = class(TCollection)
     private
        function OsReportFolder: string;
        function GetAction(const Index: integer): TReportDefItem;
     public
        constructor Create(ItemClass: TCollectionItemClass);
        destructor Destroy; override;

        procedure LoadList;

        function Add : TReportDefItem;
        property Action [ const Index:integer ]: TReportDefItem read GetAction;
     end;

  implementation

  { TReportDefList }

  constructor TReportDefList.Create(ItemClass: TCollectionItemClass);
  begin
     inherited;
  end;

  destructor TReportDefList.Destroy;
  begin
     inherited;
  end;
  function TReportDefList.Add: TReportDefItem;
  begin
     Result := TReportDefItem( Add() );
  end;

  function TReportDefList.GetAction(const Index: integer): TReportDefItem;
  begin
     if (Index >= 0) and (Index < Count)
     then Result := TReportDefItem( Items[Index] )
     else Result := Nil;
  end;

  procedure TReportDefList.LoadList;
  var Folder : TedlFolderRtns;
      i : integer;
      Itm : TReportDefItem;
  begin
     Folder := TedlFolderRtns.Create;
     try
        Folder.FileList( OsReportFolder,'*.sw.xml', False);
        for i := 0 to Folder.ResultListCount -1 do
        begin
          Itm := Add();
          Itm.FileName := Folder.ResultList[i];
        end;
     finally
        FreeAndNil(Folder);
     end;
  end;

  function TReportDefList.OsReportFolder: string;
  begin
     Result := Application.ExeName + '_RprtDef';
  end;

  { TReportDefItem }

  constructor TReportDefItem.Create(Collection: TCollection);
  begin
     inherited;
     fSql := '';
     fSkeleton := '';
  end;

  destructor TReportDefItem.Destroy;
  begin
    inherited;
  end;

  procedure TReportDefItem.Load;
  var XMLDoc : IXMLDocument;
      TopNode : IXMLNode;
      FileNode : IXmlNode;
      iWebIndex, iRemoteIndex : integer;
      sWebVersion, sRemoteVersion: string;
      sWebFileName: string;
  begin
     if not FileExists(fFileName ) then Exit;

     XMLDoc := TXMLDocument.Create(nil);
     try
        XMLDoc.LoadFromFile( fFileName );
        XMLDoc.Active := True;

        TopNode := XMLDoc.ChildNodes.FindNode('sw-report-def');
        if not Assigned(TopNode) then Exit;

        FileNode := TopNode.ChildNodes.First;
        while Assigned(FileNode) do
        begin
           fSql := VarToStr( FileNode.Attributes['sql'] );
           fSkeleton := VarToStr(  FileNode.Attributes['skeleton'] );
           FileNode := FileNode.NextSibling;
        end;
        XMLDoc.Active := False;
     finally
        XMLDoc := Nil;
     end;
  end;

  procedure TReportDefItem.SetFileName(const Value: string);
  begin
     if fFileName <> Value
     then begin
        fFileName := Value;
        Load;
     end;
  end;
  end.

Использовать как:

fReports := TReportDefList.Create( TReportDefItem );
fReports.LoadList();

Ещё вопросы

Сообщество Overcoder
Наверх
Меню