如何理解 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
patient1
和 patient2
都是哈希,没关系。 :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
(由您的代码中的 ... => ... 创建),而不是 Symbol
s 本身。 Symbol
s(例如 :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
。是您的哈希值,patient1
和 patient2
,每个都包含这些值,在每种情况下都由相同的键指向。
这样想:如果你在圣诞节早上走进客厅,看到两个盒子,上面有一个标签,上面写着“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
patient1
和patient2
都是哈希,没关系。:ruby
然而是一个符号。如果我们要输出以下内容:patient1.each_key |key| puts key.to_s
那会输出什么? “红色”,还是“编程”?
当然也不是。输出将是ruby
。顺便说一句,您只需在 IRB 中输入问题,您就可以在比输入问题更短的时间内发现。
为什么会是red
或programming
?符号总是评估自己。符号: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 上使用 lbs 或 ' 和 " 符号输入时如何验证表单字段中的身高和体重