如果我们使用字符串作为哈希键,Ruby 需要评估该字符串并查看其内容(并计算其哈希函数),并将结果与已存储在哈希。
如果我们使用符号作为哈希键,则隐含着它是不可变的,因此 Ruby 基本上可以将对象 id 的(哈希函数)与已存储的键的(哈希)对象 id 进行比较在哈希中。 (快得多)。
但问题是在 Rails
params
中,它是 HashWithIndifferentAccess
的实例,如果我们编写 params[:some_key]
,它会将 :some_key
转换为 'some_key'
,然后尝试在 params 哈希中查找键。
159 号线
def convert_key(key)
key.kind_of?(Symbol) ? key.to_s : key
end
所以如果在 Hash 中使用 String 作为键查找很慢,为什么
HashWithIndifferentAccess
将符号键转换为字符串。
曾经的原因是安全。它在 Ruby 2.2 或更高版本中不再相关。
在 Ruby 2.2 之前,符号不会被垃圾回收。这意味着一旦通过文字 (
:my_symbol
) 或 #to_sym
创建了一个符号,它就会永远。
如果 Rails 使用符号而不是字符串,那么它将创建与请求中的参数名称相对应的符号。攻击者可以发送带有名为
param1
、param2
、... 的参数的请求,并通过让应用程序分配数十万个符号来耗尽服务器内存。
在 Ruby 2.2 或更高版本中不再是这种情况
:symbol.to_s
将始终创建一个新的实例字符串,而 "string".to_sym
将始终生成相同的符号。
p "string".to_sym.object_id
#=> 272028
p "string".to_sym.object_id
#=> 272028
p :symbol.to_sym.to_s.object_id
#=>70127441809260
p :symbol.to_sym.to_s.object_id
#=>70127441809160
因此,该类的设计者似乎将键存储为字符串,以避免在访问期间不必要地创建字符串,特别是如果使用
hash.keys
方法(它将键作为字符串返回)。
对于整数键,HashWithIndeffrentAccess 返回 nil,因此使用 String 是安全的
pry(main)> hash = HashWithIndifferentAccess.new
#=> {}
[2] pry(main)> hash[1] = "a"
#=> "a"
[3] pry(main)> hash
#=> {1=>"a"}
[4] pry(main)> hash["1"]
#=> nil
我们也不能使用这样的整数
pry(main)> hash[:1] = "a"
SyntaxError: unexpected integer literal, expecting literal content or terminator or tSTRING_DBEG or tSTRING_DVAR