Зачем использовать символы в качестве хеш-ключей в Ruby?

Часто люди используют символы в качестве ключей в хэше Ruby.

В чем преимущество использования строки?

Например:

hash[:name]

против

hash['name']

4 ответа

Решение

TL;DR:

Использование символов не только экономит время при выполнении сравнений, но и экономит память, поскольку они сохраняются только один раз.

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

Краткий (иш) ответ:

Использование символов не только экономит время при выполнении сравнений, но и экономит память, поскольку они сохраняются только один раз.

Символы в Ruby в основном являются "неизменяемыми строками".. это означает, что они не могут быть изменены, и это означает, что один и тот же символ при многократном обращении по всему исходному коду всегда сохраняется как одна и та же сущность, например, имеет один и тот же идентификатор объекта,

С другой стороны, строки изменчивы, их можно изменить в любое время. Это означает, что Ruby необходимо хранить каждую строку, которую вы упоминаете в исходном коде, в отдельном объекте, например, если у вас несколько раз упоминается строка "имя" в вашем исходном коде, Ruby необходимо хранить их все в отдельных объектах String, потому что они может измениться позже (такова природа строки Ruby).

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

Если вы используете символ в качестве ключа Hash, это неявно означает, что он неизменен, поэтому Ruby может просто выполнить сравнение (hash function of) object-id с (hashed) object-id ключей, которые уже хранятся в Хеш (намного быстрее)

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

Заметки:

Если вы выполняете сравнение строк, Ruby может сравнивать символы только по их идентификаторам объектов, не оценивая их. Это намного быстрее, чем сравнение строк, которые необходимо оценить.

Если вы обращаетесь к хешу, Ruby всегда применяет хеш-функцию для вычисления "хеш-ключа" из любого ключа, который вы используете. Вы можете представить что-то вроде хеша MD5. И тогда Руби сравнивает эти "хешированные ключи" друг с другом.

Длинный ответ:

http://www.reactive.io/tips/2009/01/11/the-difference-between-ruby-symbols-and-strings

http://www.randomhacks.net/articles/2007/01/20/13-ways-of-looking-at-a-ruby-symbol

Причина в эффективности, с множественным усилением по отношению к строке:

  1. Символы являются неизменными, поэтому вопрос "что произойдет, если ключ изменится?" не нужно спрашивать
  2. Строки дублируются в вашем коде и обычно занимают больше места в памяти.
  3. Хеш-поиск должен вычислять хеш ключей для их сравнения. Это O(n) для строк и константа для символов.

Более того, в Ruby 1.9 введен упрощенный синтаксис только для хеша с символами ключей (например, h.merge(foo: 42, bar: 6)), а в Ruby 2.0 есть ключевые аргументы, которые работают только для символьных ключей.

Примечания:

1) Вы можете быть удивлены, узнав, что Ruby лечит String ключи отличается от любого другого типа. В самом деле:

s = "foo"
h = {}
h[s] = "bar"
s.upcase!
h.rehash   # must be called whenever a key changes!
h[s]   # => nil, not "bar"
h.keys
h.keys.first.upcase!  # => TypeError: can't modify frozen string

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

2) Буквы "b", "a" и "r" сохраняются только один раз для всех случаев :bar в программе. До Ruby 2.2 было плохой идеей постоянно создавать новые Symbols которые никогда не использовались повторно, так как они навсегда останутся в глобальной таблице поиска символов. Ruby 2.2 будет собирать их мусор, так что не беспокойтесь.

3) На самом деле, вычисление хеша для Symbol не заняло никакого времени в Ruby 1.8.x, так как идентификатор объекта использовался напрямую:

:bar.object_id == :bar.hash # => true in Ruby 1.8.7

В Ruby 1.9.x это изменилось, поскольку хэши переходят из одного сеанса в другой (включая Symbols):

:bar.hash # => some number that will be different next time Ruby 1.9 is ran

Re: в чем преимущество перед использованием строки?

  • Стиль: его Рубиновый путь
  • (Очень) поиск значений немного быстрее, поскольку хеширование символа эквивалентно хешированию целого числа против хеширования строки.

  • Недостаток: занимает слот в таблице символов программы, который никогда не освобождается.

Я был бы очень заинтересован в продолжении относительно замороженных строк, представленных в Ruby 2.x.

Когда вы имеете дело со множеством строк, поступающих из текстового ввода (я имею в виду HTTP-параметры или полезную нагрузку, например, через Rack), гораздо проще использовать строки везде.

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

Другие вопросы по тегам