冻结早期 Ruby 版本中的字符串文字

Freezing string literals in early Ruby versions

这个问题特别适用于 Ruby 1.9 和 2.1,其中不能自动冻结字符串文字。特别是我指的是 this article,它建议冻结字符串,这样代码的重复计算就不会每次都创建一个新的 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

In particular I am refering to this article, which suggests to freeze strings, so that repeated evaluation of the code does not create a new String object every time

那不是 Object#freeze 所做的。顾名思义,它 "freezes" 对象,即它不允许对对象的内部状态进行任何进一步的修改。文档中没有任何内容甚至远程暗示 Object#freeze 执行某种重复数据删除或实习。

可能正在考虑String#-@,但这在Ruby 2.1 中不存在。它只是在 Ruby 2.3 中添加的,并且实际上在当时具有不同的语义:

  • Ruby 2.3–2.4: returns self 如果 self 已经冻结,否则 returns self.dup.freeze , 即 a frozen duplicate of the string:

    -strstr (frozen)

    If the string is frozen, then return the string itself.
    If the string is not frozen, then duplicate the string freeze it and return it.

  • Ruby 2.5+: returns self 如果 self 已经冻结,否则 returns 字符串的冻结版本that is de-duplicated(即可以在现有冻结字符串的缓存中查找并返回现有版本):

    -strstr (frozen)

    Returns a frozen, possibly pre-existing copy of the string.
    The string will be deduplicated as long as it is not tainted, or has any instance variables set on it.

因此,您链接到的文章在三个方面是错误的:

  1. 去重只针对字符串,不针对任意对象。
  2. freeze 未执行重复数据删除。
  3. 重复数据删除仅由 String#-@ 从 Ruby 2.5 开始执行。

那篇文章还有第四个说法是错误的,尽管我们不能真的为此责怪作者,因为这篇文章是 2016 年的,而决定是在 2019 年才改变的:Ruby 3.0 will not have immutable string literals by default

在那篇文章中 正确的一件事是 # frozen_string_literal: true pragma(或相应的命令行选项 --enable-frozen-string-literal)不仅会冻结所有静态字符串文字,它也会删除它们。