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

Много раз люди используют символы в качестве ключей в хеше Ruby.

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

например:.

hash[:name]

против.

hash['name']

Ответ 1

TL; DR:

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

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

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

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

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

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

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

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

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

Примечания:

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

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

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

https://web.archive.org/web/20180709094450/http://www.reactive.io/tips/2009/01/11/the-difference-between-ruby-symbols-and-strings

http://www.randomhacks.net.s3-website-us-east-1.amazonaws.com/2007/01/20/13-ways-of-looking-at-a-ruby-symbol/

Ответ 2

Причиной является эффективность с несколькими коэффициентами усиления над строкой:

  • Символы неизменны, поэтому вопрос "что произойдет, если ключ изменится?" не нужно спрашивать.
  • Строки дублируются в коде и обычно занимают больше места в памяти.
  • Поиск хэшей должен вычислять хэш ключей для их сравнения. Это значение 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, которые никогда не использовались повторно, поскольку они останутся в глобальной таблице поиска Symbol навсегда. Ruby 2.2 будет мусор собирать их, поэтому не беспокойтесь.

3) На самом деле вычисление хэша для символа не занимало времени в 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

Ответ 3

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

  • Styling: его Ruby-way
  • (Очень) немного быстрее оценивает поиск, поскольку хеширование символа эквивалентно хешированию целого числа с хешированием строки.

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

Ответ 4

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

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

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