如何理解Ruby中的符号

时间:2010-02-26 13:22:05

标签: ruby symbols

尽管阅读了“Understanding Ruby Symbols”,但在使用符号时,我仍然对内存中数据的表示感到困惑。如果一个符号(其中两个包含在不同的对象中)存在于同一个内存位置,那么它们如何包含不同的值?我希望相同的内存位置包含相同的值。

这是来自链接的引用:

  

与字符串不同,同名的符号在ruby会话期间初始化并仅存在于内存中

我不明白它如何设法区分同一内存位置中包含的值。

考虑这个例子:

patient1 = { :ruby => "red" }
patient2 = { :ruby => "programming" }

patient1.each_key {|key| puts key.object_id.to_s}
3918094
patient2.each_key {|key| puts key.object_id.to_s}
3918094

patient1patient2都是哈希,没关系。 :ruby然而是一个象征。如果我们要输出以下内容:

patient1.each_key {|key| puts key.to_s}

然后会输出什么? "red""programming"

忘记哈希一秒钟,我认为符号是一个值的指针。我的问题是:

  • 我可以为符号指定值吗?
  • 符号只是一个指向变量的指针吗?
  • 如果符号是全局的,那是否意味着符号总是指向一个东西?

11 个答案:

答案 0 :(得分:60)

考虑一下:

x = :sym
y = :sym
(x.__id__ == y.__id__ ) && ( :sym.__id__ == x.__id__) # => true

x = "string"
y = "string"
(x.__id__ == y.__id__ ) || ( "string".__id__ == x.__id__) # => false

因此,无论您创建一个符号对象,只要它的内容相同,它就会引用内存中的同一个对象。这不是问题,因为符号是immutable object。字符串是可变的。


(回应下面的评论)

在原始文章中,值未存储在符号中,而是存储在哈希中。考虑一下:

hash1 = { "string" => "value"}
hash2 = { "string" => "value"}

这会在内存中创建六个对象 - 四个字符串对象和两个哈希对象。

hash1 = { :symbol => "value"}
hash2 = { :symbol => "value"}

这只在内存中创建了五个对象 - 一个符号,两个字符串和两个哈希对象。

答案 1 :(得分:49)

当我这样想时,我能够生成符号。 Ruby字符串是一个具有大量方法和属性的对象。人们喜欢使用字符串作为键,当字符串用于键时,则不使用所有这些额外的方法。所以他们制作了符号,这些符号是删除了所有功能的字符串对象,除了它是一个好键所需的符号。

只需将符号视为常量字符串。

答案 2 :(得分:31)

符号:ruby不包含"red""programming"。符号:ruby只是符号:ruby。您的哈希值patient1patient2各自包含这些值,在每种情况下由相同的键指向。

以这种方式思考:如果你在圣诞节早晨进入起居室,看到两个带有标签的盒子,上面写着“Kezzer”。在它上面有袜子,另一个有煤。你不会感到困惑,并问“Kezzer”如何包含袜子和煤炭,即使它是同一个名字。因为名称不包含(蹩脚)礼物。它只是指着他们。同样,:ruby不包含哈希值中的值,只是指向它们。

答案 3 :(得分:26)

您可能会假设您所做的声明将Symbol的值定义为不同于它的值。实际上,Symbol只是一个保持不变的“内化”字符串值。这是因为它们是使用经常使用的简单整数标识符存储的,因为它比管理大量可变长度字符串更有效。

以你的例子为例:

patient1 = { :ruby => "red" }

这应该理解为:“声明一个变量patient1并将其定义为哈希,并在此存储中键下的值为'red'(符号'ruby')”

另一种写作方式是:

patient1 = Hash.new
patient1[:ruby] = 'red'

puts patient1[:ruby]
# 'red'

当你正在进行任务时,你得到的结果与你在第一时间分配的结果相同并不奇怪。

符号概念可能有点令人困惑,因为它不是大多数其他语言的特征。

即使值相同,每个String对象也是不同的:

[ "foo", "foo", "foo", "bar", "bar", "bar" ].each do |v|
  puts v.inspect + ' ' + v.object_id.to_s
end

# "foo" 2148099960
# "foo" 2148099940
# "foo" 2148099920
# "bar" 2148099900
# "bar" 2148099880
# "bar" 2148099860

具有相同值的每个符号都指向同一个对象:

[ :foo, :foo, :foo, :bar, :bar, :bar ].each do |v|
  puts v.inspect + ' ' + v.object_id.to_s
end

# :foo 228508
# :foo 228508
# :foo 228508
# :bar 228668
# :bar 228668
# :bar 228668

将字符串转换为符号会将相同的值映射到相同的唯一符号:

[ "foo", "foo", "foo", "bar", "bar", "bar" ].each do |v|
  v = v.to_sym
  puts v.inspect + ' ' + v.object_id.to_s
end

# :foo 228508
# :foo 228508
# :foo 228508
# :bar 228668
# :bar 228668
# :bar 228668

同样,每次从Symbol转换为String都会创建一个不同的字符串:

[ :foo, :foo, :foo, :bar, :bar, :bar ].each do |v|
  v = v.to_s
  puts v.inspect + ' ' + v.object_id.to_s
end

# "foo" 2148097820
# "foo" 2148097700
# "foo" 2148097580
# "bar" 2148097460
# "bar" 2148097340
# "bar" 2148097220

您可以将Symbol值视为从内部哈希表中绘制,您可以使用简单的方法调用查看已编码为符号的所有值:

Symbol.all_values

# => [:RUBY_PATCHLEVEL, :vi_editing_mode, :Separator, :TkLSHFT, :one?, :setuid?, :auto_indent_mode, :setregid, :back, :Fail, :RET, :member?, :TkOp, :AP_NAME, :readbyte, :suspend_context, :oct, :store, :WNOHANG, :@seek, :autoload, :rest, :IN_INPUT, :close_read, :type, :filename_quote_characters=, ...

当您通过冒号或使用.to_sym定义新符号时,此表将会增长。

答案 4 :(得分:15)

符号不是指针。它们不包含值。符号只是 :ruby是符号:ruby,这就是它的全部内容。它不包含值,它不任何东西,它只是作为符号:ruby存在。符号:ruby是一个值,就像数字1一样。它不会指向另一个值,而不是数字1。

答案 5 :(得分:12)

patient1.each_key {|key| puts key.to_s}
     

然后会输出什么? “红色”,或   “编程”?

两者都不会输出“ruby”。

你混淆了符号和哈希。它们没有关联,但它们在一起很有用。有问题的符号是:ruby;它与散列中的值无关,它的内部整数表示将始终相同,而它的“值”(转换为字符串时)将始终为“ruby”。

答案 6 :(得分:10)

简而言之

符号解决了创建人类可读,不可变表示的问题,这些表示还具有运行时查找比字符串更简单的优点。可以把它想象成可以重复使用的名称或标签。

为什么:红色比“红色”好

在动态面向对象语言中,您可以使用可读引用创建复杂的嵌套数据结构。 hash是一个常见用例,您可以将值映射到唯一键 - 至少对每个实例都是唯一的。每个哈希不能有多个“红色”密钥。

然而,使用数字索引而不是字符串键会更有效。因此,引入符号作为速度和可读性之间的折衷。符号解析比等效字符串容易得多。通过人类可读且易于运行时解析符号是动态语言的理想补充。

<强>优势

由于符号是不可变的,因此可以在运行时共享它们。如果两个哈希实例对红色项具有共同的词典或语义需求,则符号:red将使用大约一半的内存,而字符串“red”将需要两个哈希。

因为:red总是回退到内存中的相同位置,它可以在几百个哈希实例中重复使用而内存几乎没有增加,而使用“red”会增加内存成本,因为每个哈希实例都需要存储创造时可变的字符串。

不确定Ruby实际上如何实现符号/字符串,但显然符号在运行时提供的实现开销较少,因为它是一个固定的表示。 Plus符号的字符输入比引用的字符串少一个,而较少的输入是真正的Rubyists的永恒追求。

<强>摘要

使用如下符号:red,由于字符串比较操作的成本以及将每个字符串实例存储在内存中的需要,您可以获得字符串表示的可读性,同时减少开销。

答案 7 :(得分:3)

patient1 = { :ruby => "red" }
patient2 = { :ruby => "programming" }

patient1.each_key {|key| puts key.object_id.to_s}
3918094
patient2.each_key {|key| puts key.object_id.to_s}
3918094
     

patient1patient2都是哈希,没关系。 :ruby然而是一个象征。如果我们要输出以下内容:

patient1.each_key {|key| puts key.to_s}
     

然后会输出什么? “红色”,还是“编程”?

当然也没有。输出将为ruby。顺便说一句,BTW,您可以在比输入问题所花费的时间更短的时间内找到答案,只需将其输入IRB即可。

为什么 <{1}}或red?符号总是评估自己。符号programming的值是符号:ruby本身,符号:ruby的字符串表示形式是字符串值:ruby

无论如何,[BTW:"ruby"总是将其参数转换为字符串。无需在其上调用puts。]

答案 8 :(得分:3)

我建议您阅读Wikipedia article on hash tables - 我认为这有助于您了解{:ruby => "red"}的真正含义。

可能有助于您了解情况的另一项练习:考虑{1 => "red"}。从语义上讲,这并不意味着“将1的值设置为"red"”,这在Ruby中是不可能的。相反,它意味着“创建一个Hash对象,并为关键"red"存储值1

答案 9 :(得分:0)

我是Ruby的新手,但我认为(希望?)这是一种简单的方式来看待它......

符号不是变量或常量。它并不代表或指向一个价值。符号是一个值。

它只是一个没有对象开销的字符串。文字只有文字。

所以,这个:

"hellobuddy"

与此相同:

:hellobuddy

除了你不能这样做,例如:hellobuddy.upcase。它是字符串值,只有字符串值。

同样,这:

greeting =>"hellobuddy"

与此相同:

greeting => :hellobuddy

但是,再次,没有字符串对象开销。

答案 10 :(得分:-1)

一个简单的方法就是思考,“如果我使用的是字符串而不是符号,该怎么办?

patient1 = { "ruby" => "red" }
patient2 = { "ruby" => "programming" }

这根本不是混乱,对吧? 您使用“ruby”作为哈希中的键。

"ruby"是字符串文字,因此这是值。您无法使用内存地址或指针。 每次调用"ruby"时,您都要创建一个新实例,即创建一个包含相同值的新内存单元格"ruby"

然后散列“我的关键值是什么?哦它是"ruby"。然后将该值映射为”红色“或”编程“。 换句话说,:ruby不会取消引用"red""programming"。 哈希映射 :ruby"red""programming"

如果我们使用符号

则将其与之比较
patient1 = { :ruby => "red" }
patient2 = { :ruby => "programming" }

:ruby的值也有效"ruby"

为什么呢?因为符号基本上是字符串常量。 常量没有多个实例。它是相同的内存地址。一旦取消引用,内存地址就有一定的值。对于符号,指针名称是符号,取消引用的值是一个字符串,它匹配符号名称,在本例中为"ruby"

在哈希中,您不使用符号,指针,而是使用引用的值。您没有使用:ruby,而是"ruby"。 然后哈希查找键"ruby",值为"red""programming",具体取决于您如何定义哈希值。

范式转换和带回家的概念是,给定该哈希的密钥,符号的值是与哈希映射的值完全分离的概念。