这个问题特别适用于Ruby 1.9和2.1,其中字符串文字不能自动冻结。特别是我指的是这篇文章,它建议冻结字符串,这样对代码的重复评估就不会每次都创建一个新的 String 对象,据说这在其他优点中使程序性能更好。作为一个具体的例子,本文提出了表达式
("%09d".freeze % id).scan(/d{3}/).join("/".freeze)
我想在我们的项目中使用这个概念,出于测试目的,我尝试了以下代码:
3.times { x="abc".freeze; puts x.object_id }
在 Ruby 2.3 中,每次都会打印相同的对象 ID。在 JRuby 1.7 中,它在语言级别对应于 Ruby 1.9,它打印了三个不同的对象 ID,尽管我已经明确冻结了字符串。
有人可以解释一下原因,以及如何在这种情况下正确使用freeze
吗?
特别是我指的是这篇文章,它建议冻结字符串,以便对代码的重复计算不会每次都创建新的 String 对象
这不是Object#freeze
所做的。顾名思义,它"冻结"了对象,即它不允许对对象的内部状态进行任何进一步的修改。文档中没有任何内容甚至远程表明Object#freeze
执行某种重复数据消除或实习。
你可能正在考虑String#-@
,但这在Ruby 2.1中不存在。它只是在 Ruby 2.3 中添加的,实际上当时有不同的语义:
-
Ruby 2.3–2.4:如果
self
已经冻结,则返回self
,否则返回self.dup.freeze
,即字符串的冻结副本:-str
→str
(冷冻(如果字符串被冻结,则返回字符串本身。
如果字符串未冻结,则复制字符串将其冻结并返回。 -
Ruby 2.5+:如果
self
已经冻结,则返回self
,否则返回重复数据删除的字符串的冻结版本(即,可以在现有冻结字符串的缓存中查找它,并返回现有版本(:-str
→str
(冷冻(返回冻结的、可能预先存在的字符串副本。
只要字符串未被污染,或者在其上设置了任何实例变量,就会对其进行重复数据删除。
因此,您链接到的文章在三个方面是错误的:
仅对字符串执行重复数据消除,- 而不对任意对象执行重复数据消除。
- 重复数据消除不是由 执行的
freeze
执行的。 - 重复数据消除仅由从 Ruby 2.5 开始的
String#-@
执行。
那篇文章中还有第四种说法是错误的,尽管我们不能因此而责怪作者,因为这篇文章是 2016 年的,而且决定只是在 2019 年才改变:Ruby 3.0 默认情况下不会有不可变的字符串文字。
该文章中正确的一件事是,# frozen_string_literal: true
编译指示(或相应的命令行选项--enable-frozen-string-literal
(不仅会冻结所有静态字符串文字,还会删除重复数据。