如何理解 Ruby 中的符号

Posted

技术标签:

【中文标题】如何理解 Ruby 中的符号【英文标题】:How to understand symbols in Ruby 【发布时间】:2011-01-21 11:02:57 【问题描述】:

尽管阅读了“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"

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

我可以给符号赋值吗? 符号是否只是指向具有值的变量的指针? 如果符号是全局的,这是否意味着一个符号总是指向一个东西?

【问题讨论】:

它将输出 ":ruby",因为你正在打印一个符号。如果你说puts patient1[:ruby],它会打印“red”,如果你说puts patient2[:ruby],它会打印“programming”。 符号不是指向值的指针。在内部,符号只是一个整数。 【参考方案1】:

考虑一下:

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"

这只会在内存中创建五个对象——一个符号、两个字符串和两个散列对象。

【讨论】:

然而,链接中的示例显示了包含 不同 值的符号,但符号具有相同的名称和相同的内存位置。当它们被输出时,它们具有 不同的 值,这是我没有得到的部分。他们肯定应该包含相同的值吗? 我刚刚进行了编辑,试图解释我仍然感到困惑。我的大脑无法计算;) 符号不包含值,它们值。哈希包含值。 存储键/值对的是 Hash(由您的代码中的 ... => ... 创建),而不是 Symbols 本身。 Symbols(例如 :symbol:sym:ruby)是对中的键。只有作为Hash 的一部分,它们才会“指向”任何东西。 符号被用作哈希中的键而不是值,这就是它们可以不同的原因,类似于使用 key1 = 'ruby' 和 hash1 = key1 => 'value '... hash2 = key1 => 'value2'...。【参考方案2】:

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

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

【讨论】:

通读帖子,这一篇可能对我来说最有意义。 :ruby 只是存储在内存中的某处,如果我在某处使用“ruby”,然后在某处再次使用“ruby”,这只是重复。所以使用符号是减少公共数据重复的一种方法。正如你所说,常量字符串。我猜有一些底层机制会再次找到该符号以供使用? @Kezzer 这个答案非常好,对我来说似乎是正确的,但是您的评论说的是不同的东西并且是错误的或具有误导性的,您的评论谈到了数据与字符串的重复,这就是符号的原因,这是错误的或具有误导性的。是的,多次使用该符号不会占用更多的内存空间,但是在许多语言中,您也可以将其用于字符串,例如一些编程语言,如果你写“abc”而在其他地方写“abc”,编译器会看到它是相同的值字符串并将其存储在同一个地方,使其成为同一个对象,这称为字符串实习,c#就是这样做的。 所以它基本上是一个非常轻量级的字符串版本?【参考方案3】:

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

这样想:如果你在圣诞节早上走进客厅,看到两个盒子,上面有一个标签,上面写着“Kezzer”。一个有袜子,另一个有煤。您不会感到困惑并询问“Kezzer”如何同时包含袜子和煤炭,即使它是同一个名字。因为名字不包含(蹩脚的)礼物。它只是指向他们。同样,:ruby 不包含哈希中的值,它只是指向它们。

【讨论】:

这个答案完全有道理。 这听起来像是哈希和符号的完全混合。一个符号不指向一个值,如果你想说它在散列中时,那可能是有争议的,但符号不必在散列中。你可以说mystring = :steveT 这个符号不指向任何东西。散列中的键具有关联的值,键可以是符号。但符号不必在散列中。【参考方案4】:

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

以你的例子为例:

patient1 =  :ruby => "red" 

这应该读作:“声明一个变量 patient1 并将其定义为一个 Hash,并在此存储键下的值 '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

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

[ :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 值视为从内部哈希表中提取的值,并且您可以使用简单的方法调用查看已编码为 Symbols 的所有值:

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 定义新符号时,此表将会增长。

【讨论】:

【参考方案5】:

符号不是指针。它们不包含值。符号只是:ruby 是符号 :ruby,仅此而已。它不包含值,不任何事情,它只是作为符号:ruby 存在。符号:ruby 是一个值,就像数字 1 一样。它并不比数字 1 指向另一个值。

【讨论】:

【参考方案6】:
patient1.each_key |key| puts key.to_s

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

不会,它会输出“ruby”。

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

【讨论】:

【参考方案7】:

简而言之

符号解决了创建人类可读、不可变表示的问题,它还具有比字符串更易于运行时查找的好处。可以把它想象成一个可以重复使用的名称或标签。

为什么 :red 比 "red" 好

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

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

好处

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

由于 :red 总是解析回内存中的同一位置,因此它可以在一百个哈希实例中重复使用而几乎不会增加内存,而使用“red”会增加内存成本,因为每个哈希实例都需要存储创建时可变字符串。

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

总结

使用 :red 之类的符号,由于字符串比较操作的成本和需要将每个字符串实例存储在内存中,您可以通过更少的开销获得字符串表示的可读性。

【讨论】:

【参考方案8】:

我建议阅读Wikipedia article on hash tables - 我认为它会帮助您了解:ruby => "red" 的真正含义。

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

【讨论】:

【参考方案9】:
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。顺便说一句,您只需在 IRB 中输入问题,您就可以在比输入问题更短的时间内发现。

为什么redprogramming?符号总是评估自己。符号:ruby 的值是符号:ruby 本身,符号:ruby 的字符串表示形式是字符串值"ruby"

[顺便说一句:puts 总是将其参数转换为字符串,无论如何。无需致电to_s。]

【讨论】:

我现在的机器上没有 IRB,我也无法安装它,所以我很抱歉。 @Kezzer:不用担心,我只是好奇。有时你把自己深深地埋在一个问题中,甚至连最简单的事情都看不到了。当我基本上将您的问题剪切并粘贴到 IRB 中时,我只是想知道:“他为什么不自己做呢?”不用担心,当答案是“运行它!”时,您不是第一个(也不会是最后一个)问“这会打印什么”的人。顺便说一句:这是您的即时 IRB,随时随地,无需安装:TryRuby.Org 或 Ruby-Versions.Net 让您可以通过 SSH 访问所有版本的 MRI + YARV + JRuby + Rubinius + REE。 谢谢,现在就在玩。不过我还是有点迷茫,所以再看一遍。【参考方案10】:

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

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

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

所以,这个:

"hellobuddy"

和这个是一样的:

:hellobuddy

除非你做不到,例如:hellobuddy.upcase。它是字符串值,并且只是字符串值。

同样,这个:

greeting =>"hellobuddy"

和这个是一样的:

greeting => :hellobuddy

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

【讨论】:

【参考方案11】:

解决这个问题的一个简单方法是思考,“如果我使用的是字符串而不是符号呢?

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",具体取决于您定义哈希的方式。

范式转换和带回家的概念是,符号的值是一个完全独立于哈希映射到的值的概念,给定哈希的键。

【讨论】:

这个解释有什么谬误或错误,否决者?为了学习而好奇。 一个类比可能会让某些人反感,但这并不意味着它有缺陷。 我没有看到任何错误,但很难确定你在这个答案中想说什么。 x 根据上下文/进行解释的实体而被不同地理解和概念化。很简单。 彻底修改了答案。感谢您的反馈。

以上是关于如何理解 Ruby 中的符号的主要内容,如果未能解决你的问题,请参考以下文章

ruby:对符号数组进行排序

ruby 上使用 lbs 或 ' 和 " 符号输入时如何验证表单字段中的身高和体重

ruby 方法中如何使用符号来识别参数

Ruby中的符号和变量有什​​么区别? [复制]

如何存储和比较:ActiveRecord 中的符号(Ruby on Rails)

PHP 等价于 Ruby 符号