为什么一个散列字符串键冻结?(Why is a string key for a hash froz

2019-08-01 19:47发布

根据该规范 ,被用作一个关键的哈希字符串复制和冻结。 其他可变对象似乎不具有这种特殊的考虑。 例如,以与阵列键,以下是可能的。

a = [0]
h = {a => :a}
h.keys.first[0] = 1
h # => {[1] => :a}
h[[1]] # => nil
h.rehash
h[[1]] # => :a

在另一方面,类似的事情不能用字符串键来完成。

s = "a"
h = {s => :s}
h.keys.first.upcase! # => RuntimeError: can't modify frozen String

为什么字符串设计是从其它可变对象不同,当涉及到一个哈希键? 是否有一个地方本规范变成有用的任何使用情况? 这是否说明还有哪些后果?


其实,我在那里没有这样的特殊规格的关于字符串可能是有用的用例。 也就是说,我所读yaml的宝石,描述了一个散列手动写YAML文件。 键可以是字符串,我想允许不区分大小写原YAML文件。 当我读了文件,我可能会得到这样的哈希:

h = {"foo" => :foo, "Bar" => :bar, "BAZ" => :baz}

我想正常化的钥匙,小写得到这个:

h = {"foo" => :foo, "bar" => :bar, "baz" => :baz}

通过做这样的事情:

h.keys.each(&:downcase!)

但返回上面解释了原因的错误。

Answer 1:

总之它只是红宝石努力示好。

当一个键在哈希输入,一个特殊的数的计算,使用hash密钥的方法。 哈希对象使用该号码提取的关键。 举例来说,如果你问什么价值h['a']哈希调用hash串的“A”方法和检查其是否具有存储该号码的值。 当有人(你)变异的字符串对象,所以字符串“一”是现在别的东西,让我们说“AA”,就会出现问题。 哈希不会找到“AA”的哈希数字。

最常见的类型为哈希键是字符串,符号和整数。 符号和整数是不可改变的,但字符串是不是。 红宝石试图保护你从dupping和冷冻字符串键上述的混乱行为。 我想这不是其他类型的完成,因为有可能是讨厌性能的副作用(认为大阵列)。



Answer 2:

不可改变键意义一般,因为他们的哈希码将是稳定的。

这就是为什么字符串是专门转换,在MRI这部分代码:

if (RHASH(hash)->ntbl->type == &identhash || rb_obj_class(key) != rb_cString) {
  st_insert(RHASH(hash)->ntbl, key, val);
}
else {
  st_insert2(RHASH(hash)->ntbl, key, val, copy_str_key);
}

简而言之,在串钥匙的情况下, st_insert2传递指针的函数,将触发DUP和冻结。

因此,如果我们在理论上要支持不可改变的名单和不可改变的哈希作为哈希键,那么我们就可以修改代码是这样的:

VALUE key_klass;
key_klass = rb_obj_class(key);
if (key_klass == rb_cArray || key_klass == rb_cHash) {
  st_insert2(RHASH(hash)->ntbl, key, val, freeze_obj);
}
else if (key_klass == rb_cString) {
  st_insert2(RHASH(hash)->ntbl, key, val, copy_str_key);
}
else {
  st_insert(RHASH(hash)->ntbl, key, val);
}

freeze_obj将被定义为:

static st_data_t
freeze_obj(st_data_t obj)
{
    return (st_data_t)rb_obj_freeze((VALUE) obj);
}

因此,这将解决你观察,数组,关键是可变的具体不一致。 然而要真正保持一致,更多类型的对象将需要做出不变为好。

并非所有类型的,但是。 例如,存在会是没有指向冷冻直接对象像Fixnum对象,因为有效地存在对应于每个整数值Fixnum对象的仅一个实例。 这就是为什么只有String必须是特例,这样一来,不FixnumSymbol

字符串是一个特殊的例外,只是为方便Ruby程序员的事,因为字符串经常作为哈希键。

相反,其他对象类型冻结这样的,这固然会导致不一致的行为的原因,主要是方便对马茨和公司事不支持边缘情况。 在实践中,相对较少的人会用一个容器对象,像一个数组或哈希作为哈希键。 所以,如果你这样做,就看你在插入前冻结。

请注意,这是不严格有关的性能,因为冻结非直接对象的行为只涉及翻转FL_FREEZE对位basic.flags位域这是目前每一个对象。 这当然是一个廉价的操作。

再者说来的表现,注意,如果你要使用字符串键,而你的代码性能关键部分,你可能想要做的插入之前冻结你的字符串。 如果你不这样做,那么DUP被触发,这是一种更昂贵的操作。

更新 @sawa指出,离开你的数组键简单地冻结意味着原来的阵列可能是关键的使用方面,它也可能是一个令人不快的意外(但OTOH它会为您正确使用数组作为的意外不变外散列键,真的)。 如果你因此推测,DUP +冻结的出路,那么你实际上会招致可能的性能显着降低。 在三只手,把它完全解冻,你得到了OP的原始怪事。 怪事各地。 另一个原因是马茨等人推迟这些优势情况下的程序员。



Answer 3:

见在ruby-core邮件列表上的这个线程的解释(freakily,正好是我遇到的时候我打开了我的邮件应用程序的邮件列表跌跌撞撞第一封邮件!)。

我没有你的问题的第一部分的想法,除H这里是第二部分实际的答案:

  new_hash = {}
  h.each_pair do |k,v|
   new_hash.merge!({k.downcase => v}) 
  end

  h.replace new_hash

有很多这样的代码的排列,

  Hash[ h.map{|k,v| [k.downcase, v] } ]

是另一个(你可能知道这些,但有时是最好走实用路线:)



Answer 4:

您在阿斯金2个不同的问题:理论和实践。 说谎是第一个回答,但我想提供什么,我认为正确的,懒惰的解决您实际的问题:

Hash.new { |hsh, key| # this block get's called only if a key is absent
  downcased = key.to_s.downcase
  unless downcased == key # if downcasing makes a difference
    hsh[key] = hsh[downcased] if hsh.has_key? downcased # define a new hash pair
  end # (otherways just return nil)
}

所使用的块Hash.new构造函数只调用那些丢失的钥匙,这实际上是要求。 上述溶液中还接受的符号。



文章来源: Why is a string key for a hash frozen?