TStringList, динамический массив или связанный список в Delphi?

У меня есть выбор.

У меня есть несколько уже упорядоченных строк, которые мне нужно сохранить и получить доступ. Похоже, я могу выбрать между:

  1. TStringList
  2. динамический массив строк, и
  3. связанный список строк (однонаправленного)

    и Алан в своем комментарии предложил мне также добавить к выбору:

  4. TList<string>

в каких обстоятельствах каждый из этих лучше других?

что лучше всего подходит для небольших списков (менее 10 элементов)?

что лучше всего подходит для больших списков (более 1000 элементов)?

что лучше для огромных списков (более 1 000 000 элементов)?

что лучше для минимизации использования памяти?

что лучше всего минимизировать время загрузки, чтобы добавить дополнительные элементы в конце?

что лучше всего для минимизации времени доступа для доступа ко всему списку от первого до последнего?

на этом базис (или любые другие), какая структура данных была бы предпочтительнее?

для справки, я использую Delphi 2009.


Дмитрий в комментарии сказал:

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

ОК. У меня есть генеалогическая программа с большим количеством данных.

для каждого человека у меня есть ряд событий и атрибутов. Я сохраняю их как короткий текст строк, но их много для каждого человека, от 0 до нескольких сотен. И у меня тысячи людей. Мне не нужен случайный доступ к ним. Мне нужно только, чтобы они были связаны как несколько строк в известном порядке, прикрепленных к каждому человеку. Это мой случай с тысячами "маленьких списков". Они требуют времени для загрузки и использования памяти, а также времени для доступа, если они мне нужны (например, для экспорта всего сгенерированного отчета).

тогда у меня есть несколько больших списков, например, все имена разделы моего "виртуального" treeview, которые могут иметь сотни тысяч имен. Опять же, мне нужен только список, к которому я могу получить доступ по индексу. Они хранятся отдельно от TreeView для эффективности, и дерево получает их только по мере необходимости. Это занимает некоторое время для загрузки и очень дорого памяти для моей программы. Но мне не нужно беспокоиться о времени доступа, потому что одновременно доступны только несколько.

надеюсь, это дает вам представление о том, что я пытаюсь выполнять.

p.s. Я разместил много вопросов об оптимизации Delphi здесь, в StackOverflow. Моя программа считывает 25 МБ файлов со 100 000 человек и создает структуры данных и отчет и treeview для них за 8 секунд, но использует 175 МБ ОЗУ для этого. Я работаю над тем, чтобы уменьшить это, потому что я собираюсь загрузить файлы с несколькими миллионами людей в 32-битных окнах.


Я только что нашел отличные предложения по оптимизации TList в этом StackOverflow вопрос: есть ли более быстрая реализация TList?

7 ответов


Если у вас нет особых потребностей, a TStringList трудно победить, потому что он обеспечивает TStrings интерфейс, что многие компоненты могут использовать напрямую. С TStringList.Sorted := True, будет использоваться двоичный поиск, Что означает, что поиск будет очень быстрым. Вы также получаете отображение объектов бесплатно, каждый элемент также может быть связан с указателем, и вы получаете все существующие методы для маршалинга, интерфейсы потока, запятая-текст, разделенный текст и так далее.

С другой стороны, для специальных нужд цели, если вам нужно сделать много вставок и удалений, то что-то более приближающееся к связанному списку было бы лучше. Но затем поиск становится медленнее, и это действительно редкая коллекция строк, которая никогда не нуждается в поиске. В таких ситуациях часто используется некоторый тип хэша, где хэш создается, скажем, из первых 2 байтов строки( предварительно выделите массив длиной 65536, и первые 2 байта строки преобразуются непосредственно в хэш-индекс в этом диапазоне), а затем при этом расположение хэша, связанный список хранится с каждым ключом элемента, состоящим из оставшихся байтов в строках (для экономии места---индекс хэша уже содержит первые два байта). Затем начальный поиск хэша равен O (1), а последующие вставки и удаления связаны-список-быстро. Это компромисс, которым можно манипулировать, и рычаги должны быть ясными.


  1. TStringList. Плюсы: имеет расширенные функциональные возможности, позволяющие динамично развиваться, сортировка, сохранение, загрузка, поиск и т. д. Минусы: при большом объеме доступа к элементам по индексу строки[Index] вводят разумную потерянную производительность (несколько процентов), по сравнению с доступом к массиву, накладные расходы памяти для каждой ячейки элемента.

  2. динамический массив строк. Плюсы: сочетает в себе возможность динамично расти, как струны, с самым быстрым доступом по индексу, минимальное использование памяти от других. Минусы: ограниченная стандартная функциональность "string list".

  3. связанный список строк (однонаправленного). Плюсы: линейная скорость добавления элемента в конец списка. Минусы: медленный доступ по индексу и поиск, ограниченного стандарта "список строк" функции, памяти на следующий элемент указатель, статьей издержек для каждого элемента выделения памяти.

  4. TList. Как выше.

  5. TStringBuilder. У меня нет хорошей идеи, как использовать TStringBuilder в качестве хранилища для нескольких строк.

на самом деле, есть гораздо более подходов:

  • связанный список динамических массивов
  • хэш-таблицы
  • базы данных
  • бинарные деревья
  • etc

лучший подход будет зависеть от задач.

что лучше всего подходит для небольших списков (под 10 пунктов)?

любой, может быть даже статический массив с общей переменной количества элементов.

что лучше всего подходит для больших списков (более 1000 элементов)? Что лучше всего подходит для огромных списков (более 1 000 000 элементов)?

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

что лучше для минимизации использования памяти?

динамический массив съест меньше памяти. Но вопрос не в накладных расходах, а в том, на каком количестве элементов эти накладные расходы становятся разумными. А потом как правильно обращаться с таким количеством предметов.

что самое лучшее для того чтобы уменьшить время загрузки добавить дополнительные детали на конце?

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

что лучше всего для минимизации времени доступа для доступа ко всему списку от первого до последнего?

динамический массив.

На этой основе (или любые другие), какая структура данных была бы предпочтительнее?

для какой задачи ?


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

do the math-в настоящее время вы загружаете файл 25 MB с около 100000 человек в нем, что заставляет ваше приложение потреблять 175 МБ памяти. Если вы хотите загрузить файлы с несколькими миллионами человек, вы можете оценить это без радикальные изменения в вашей программе вам нужно будет умножить ваши потребности в памяти на n * 10 Как хорошо. Невозможно сделать это в 32-битном процессе, сохраняя все в памяти так, как вы это делаете в настоящее время.

у вас в основном есть два варианта:

  1. Не хранить все сразу в памяти, вместо этого используя базу данных или файловое решение, из которого вы загружаете данные, когда вам это нужно. Я помню, у вас уже были другие вопросы по этому поводу, и, вероятно, вы решили против этого, поэтому я оставлю это.

  2. храните все в памяти, но максимально эффективным способом. Пока нет 64-битного Delphi, это должно позволить несколько миллионов человек, в зависимости от того, сколько данных будет для каждого человека. Перекомпиляция этого для 64 бит также устранит этот предел.

Если вы идете на второй вариант, то вам нужно уменьшить потребление памяти гораздо больше агрессивно:

  • использовать интернировании строк. Каждый загруженный элемент данных в вашей программе, который содержит одни и те же данные, но содержится в разных строках, в основном является пустой памятью. Я понимаю, что ваша программа является зрителем, а не редактором, поэтому вы, вероятно, можете уйти только с добавлением строк в пул интернированных строк. Выполнение string interning с миллионами строк по-прежнему сложно,"оптимизация потребления памяти Струнные Бассейны" сообщения в блоге SmartInspect блог может дать вам некоторые хорошие идеи. Эти ребята регулярно имеют дело с огромными файлами данных и должны были заставить его работать с теми же ограничениями, с которыми вы сталкиваетесь.
    Это также должно связать этот ответ с вашим вопросом - если вы используете string interning, вам не нужно будет хранить списки строк в структурах данных, а списки индексов пула строк.
    Также может быть полезно использовать несколько пулов строк, например один для имен, но разные для таких мест, как города или страны. Это должно ускорить вставку в пулы.

  • используйте кодировку строк, которая дает наименьшее представление в памяти. Хранение всего как собственной строки Юникода Windows, вероятно, потребляет гораздо больше места, чем хранение строк в UTF-8, Если вы регулярно не имеете дело со строками, содержащими в основном символы, которые нуждаются в трех или более байтах в кодировке UTF-8.
    Из-за необходимого набора символов преобразование вашей программе потребуется больше циклов процессора для отображения строк, но с таким количеством данных это достойный компромисс, так как доступ к памяти будет узким местом, а меньший размер данных поможет уменьшить нагрузку на доступ к памяти.


один вопрос: Как вы запрашиваете: вы соответствуете строкам или запросу на ID или позиции в списке?

лучше всего для небольших # строк:

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

лучше всего для памяти (если это наибольшее ограничение) и время загрузки:

сохранить все строки в одном буфере памяти (или памяти сопоставленный файл) и только указатели на строки (или смещения). Всякий раз, когда вам нужна строка, вы можете вырезать строку с помощью двух указателей и вернуть ее как строку Delphi. Таким образом, вы избегаете накладных расходов самой Строковой структуры (refcount, length int, codepage int и структуры диспетчера памяти для каждого распределения строк.

Это работает только в том случае, если строки статичны и не изменяются.

TList, TList, массив строк и решение выше имеют накладные расходы "list" одного указателя на строку. Связанный список имеет накладные расходы не менее 2 указателей (один связанный список) или 3 указателей (двойной связанный список). Решение связанного списка не имеет быстрого произвольного доступа, но позволяет O(1) изменять размер, где другие параметры имеют O (lgN) (используя коэффициент для изменения размера) или O (N) с фиксированным изменением размера.

Что бы я сделал:

Если

все упомянутые структуры в вашем вопросе потерпят неудачу при использовании больших объемов данных 1M+ строк, которые должны быть динамически обработаны в коде. В то время я бы использовал двоичное дерево балансов или хэш-таблицу в зависимости от типа запросов, которые мне нужно сделать.


из вашего описания я не совсем уверен, что он может вписаться в ваш дизайн, но одним из способов улучшить использование памяти, не страдая от огромного штрафа за производительность, является использование trie.

преимущества по отношению к двоичному дереву поиска

следующие основные преимущества попыток над бинарными деревьями поиска ("Бсц"):

  • поиск ключей быстрее. Ищу ключ длина m принимает худший случай O (m) время. A BST выполняет O (log(n)) сравнения ключей, где n - количество элементов в дереве, потому что поиск зависит от глубины дерево, логарифмическое в количество ключей, если дерево сбалансированный. Следовательно, в худшем случае BST занимает O (M log n) времени. Более того, в худшем случае log (n) подойдет м. Кроме того, простые операции пытаются использование во время поиска, например array индексирование с использованием символа, быстро на реальных машины.

  • попытки могут потребовать меньше места, если они содержат большое количество коротких строки, потому что ключи не хранятся явно и узлы являются общими между ключами с общим инициалом подпоследовательности.

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

возможный вариант:

недавно я обнаружил SynBigTable (http://blog.synopse.info/post/2010/03/16/Synopse-Big-Table), который имеет класс TSynBigTableString для хранения больших объемов данных с использованием строкового индекса.

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

Как простой as:

aId: = UTF8String (Format ('%s.%s', [имя, фамилия]));

с BigTable.Добавить (данные, помощь)

и

с BigTable.Get (aId, data)

один улов, индексы должны быть уникальными, а стоимость обновления немного высока (сначала удалить, затем повторно вставить)


TStringList хранит массив указателей на записи (string, TObject).

TList хранит массив указателей.

TStringBuilder не удается сохранить коллекцию строк. Он похож на StringBuilder .NET и должен использоваться только для объединения (многих) строк.

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

Я бы использовал общий Delphi TList<string> во всех ваших сценариях. Он хранит массив строк (не string pointers). Он должен иметь более быстрый доступ во всех случаях из-за отсутствия (ООН)бокса.

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

Delphi продвигает свой TList и TList<>. Реализация внутреннего массива сильно оптимизирована, и я никогда не испытывал проблем с производительностью / памятью при его использовании. См.эффективность TList и TStringList