为啥在 ruby​​ 中动态创建大量符号不是一个好主意(对于 2.2 之前的版本)?

Posted

技术标签:

【中文标题】为啥在 ruby​​ 中动态创建大量符号不是一个好主意(对于 2.2 之前的版本)?【英文标题】:Why is it not a good idea to dynamically create a lot of symbols in ruby (for versions before 2.2)?为什么在 ruby​​ 中动态创建大量符号不是一个好主意(对于 2.2 之前的版本)? 【发布时间】:2011-06-02 05:16:02 【问题描述】:

ruby中符号的作用是什么?字符串和符号有什么区别? 为什么动态创建大量符号不是一个好主意?

【问题讨论】:

你也在通过EdgeCase Ruby koans 工作吗? 有人可以更新这个问题以指定 Ruby 2.1Ruby 2.2 之前的版本 @dewet:你能不能提一下为什么? @ack_inc 因为符号从 Ruby 2.2 开始就被垃圾回收了,而在它们永远存在于内存中之前。因此,从 Ruby 2.2 开始,创建大量它们会产生截然不同(更小)的影响。 【参考方案1】:

符号类似于字符串,但它们是不可变的——它们不能被修改。

它们只被放入内存一次,这使得它们可以非常有效地用于哈希中的键之类的东西,但它们会一直保留在内存中直到程序退出。如果您滥用它们,这会使它们成为内存消耗者。

如果您动态创建大量符号,则会分配大量内存,直到程序结束才能释放。如果你知道你会动态创建符号(使用string.to_sym):

    需要重复访问符号 不需要修改

正如我之前所说,它们对于诸如哈希之类的东西很有用——在这种情况下,您更关心变量的身份而不是其值。正确使用符号是传递身份的一种可读且有效的方式。

我将解释我的意思是关于符号的不变性 RE 您的评论。

字符串就像数组;它们可以就地修改:

12:17:44 ~$ irb
irb(main):001:0> string = "Hello World!"
=> "Hello World!"
irb(main):002:0> string[5] = 'z'
=> "z"
irb(main):003:0> string
=> "HellozWorld!"
irb(main):004:0> 

符号更像数字;它们不能就地编辑:

irb(main):011:0> symbol = :Hello_World
=> :Hello_World
irb(main):012:0> symbol[5] = 'z'
NoMethodError: undefined method `[]=' for :Hello_World:Symbol
    from (irb):12
    from :0

【讨论】:

如果一个符号不是动态创建的,那么它不能被修改吗?可以举个例子吗? 有人可以更新这个问题以指定 Ruby 2.1Ruby 2.2 之前的版本【参考方案2】:

一个符号是相同的对象和相同的内存分配,无论它在哪里使用:

>> :hello.object_id
=> 331068
>> a = :hello
=> :hello
>> a.object_id
=> 331068
>> b = :hello
=> :hello
>> b.object_id
=> 331068
>> a = "hello"
=> "hello"
>> a.object_id
=> 2149256980
>> b = "hello"
=> "hello"
>> b.object_id
=> 2149235120
>> b = "hell" + "o"

两个“相同”的字符串包含相同的字符可能不会引用相同的内存,如果您将字符串用于哈希,这可能会效率低下。

因此,符号对于减少内存开销很有用。但是 - 它们是等待发生的内存泄漏,因为符号一旦创建就不能被垃圾收集。创建成千上万个符号将分配内存并且不可恢复。哎呀!

【讨论】:

据我所知,您不能垃圾收集符号。您可以随时使用GC.start 开始垃圾收集。这是一篇关于这个主题的好文章:viewsourcecode.org/why/hacking/theFullyUpturnedBin.html【参考方案3】:

如果不根据某种白名单验证输入(例如,对于 RoR 中的查询字符串参数),则根据用户输入创建符号可能特别糟糕。如果用户输入在未经验证的情况下转换为符号,恶意用户可能会导致您的程序消耗大量内存,而这些内存永远不会被垃圾收集。

错误(无论用户输入如何创建符号):

name = params[:name].to_sym

好(仅当允许用户输入时才会创建符号):

whitelist = ['allowed_value', 'another_allowed_value']
raise ArgumentError unless whitelist.include?(params[:name])
name = params[:name].to_sym

【讨论】:

【参考方案4】:

Starting Ruby 2.2 及以上Symbols 会自动进行垃圾回收,因此这应该不是问题。

【讨论】:

【参考方案5】:

如果您使用的是 Ruby 2.2.0 或更高版本,通常应该可以动态创建大量符号,因为它们将根据 Ruby 2.2.0-preview1 announcement 进行垃圾收集,其中包含指向 more details about the new symbol GC 的链接。但是,如果您将动态符号传递给某种将其转换为 ID(C 源代码中使用的内部 Ruby 实现概念)的代码,那么在这种情况下,它将被固定并且永远不会被垃圾收集。我不确定这种情况发生的频率有多高。

您可以将符号视为某事物的名称,而将字符串(大致)视为一系列字符。在许多情况下,您可以使用符号或字符串,也可以混合使用两者。符号是不可变的,这意味着它们在创建后无法更改。符号的实现方式,比较两个符号是否相等是非常有效的,因此使用它们作为哈希的键应该比使用字符串快一点。符号没有很多字符串所具有的方法,例如start_with?,因此在调用这些方法之前,您必须使用to_s 将符号转换为字符串。

您可以在文档中阅读更多关于符号的信息:

http://www.ruby-doc.org/core-2.1.3/Symbol.html

【讨论】:

以上是关于为啥在 ruby​​ 中动态创建大量符号不是一个好主意(对于 2.2 之前的版本)?的主要内容,如果未能解决你的问题,请参考以下文章

何时在 Ruby 中使用符号而不是字符串?

Ruby如何打印/放置读取的文件内容而不是一堆数字/符号

Ruby/Rails - 为啥自我关联条件取决于创建时间?

为啥 Haskell 没有符号(a la ruby​​)/原子(a la erlang)?

Python 和 Ruby 的对比

为啥 Ruby 象征着我的哈希键?