如何理解Ruby中的符号


85

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

引用来自链接:

与字符串不同,同名符号被初始化,并且在一次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"

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

  • 我可以给符号赋值吗?
  • 符号只是指向变量中包含值的指针吗?
  • 如果符号是全局符号,是否意味着符号总是指向一件事?

1
它将输出“:ruby”,因为您正在打印符号。如果您说puts patient1[:ruby],它将打印“红色”,如果您说puts patient2[:ruby],它将打印“正在编程”。
ROSS

1
符号不是指向值的指针。在内部,符号只是一个整数。
akuhn '16

Answers:


62

考虑一下:

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

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


(针对以下评论)

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

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

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

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

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


但是,链接中的示例显示了包含不同值的符号,但是这些符号具有相同的名称和相同的存储位置。当它们输出时,它们具有不同的值,这是我不了解的部分。当然它们应该包含相同的值?
Kezzer

1
我刚刚进行了编辑,试图说明我仍然感到困惑。我的大脑无法计算;)
Kezzer 2010年

48
符号不包含值,它们值。散列包含值。
MladenJablanović10年

5
这是Hash其存储键/值对,而不是(用{... => ...}在你的代码中创建)Symbolš自己。的SymbolS(例如:symbol:sym:ruby)是在对所述密钥。它们只是作为“一部分Hash”“指向”任何东西。
James A. Rosen

1
符号被用作哈希中的键而不是值,这就是为什么它们可以不同的原因,它类似于使用key1 ='ruby'和hash1 = {key1 =>'value'...} hash2 = { key1 =>'value2'...}。
约书亚·奥尔森

53

当我想到这样的时候,我便能够粗暴地对待符号。Ruby字符串是一个具有许多方法和属性的对象。人们喜欢使用字符串作为键,而当字符串用于键时,则不会使用所有这些额外的方法。因此,他们制作了符号,这些符号是除去所有功能的字符串对象,但要使其成为一个好的键就需要删除这些符号。

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


2
通读这些帖子,这对我来说可能是最有意义的。:ruby只是存储在内存中的某个地方,如果我在某个地方使用“ ruby​​”,然后在另一个地方再次使用“ ruby​​”,那只是重复。因此,使用符号是减少公共数据重复的一种方法。如您所说,常量字符串。我猜想有某种底层的机制可以再次找到该符号来使用?
Kezzer 2010年

@Kezzer这个答案真的很好,对我来说似乎是正确的,但是您的评论说的是不同的东西,是错误的或令人误解的,您的评论谈到了带有字符串的数据重复,这就是符号的原因,这是错误的或令人误解的。多次使用该符号不会占用更多的内存空间,但是对于许多语言,例如对于某些编程语言,如果您编写“ abc”,而在其他地方使用“ abc”,则编译器会看到它是相同的值字符串并存储它在同一位置使它成为同一对象,这就是所谓的字符串实习,而c#做到了。
barlop

所以它基本上是一个非常轻巧的字符串版本?
stevec

34

该符号:ruby不包含"red""programming"。符号:ruby只是符号:ruby。这是您的哈希,patient1并且patient2每个哈希都包含这些值,在每种情况下均由同一键指向。

这样考虑:如果您在圣诞节早晨走进客厅,看到两个盒子上有一个标有“ Kezzer”的标签。上面有袜子,另外一个有煤。您将不会感到困惑,也可以询问“ Kezzer”如何包含袜子和煤,即使它的名字相同。因为该名称不包含(糟糕的)礼物。它只是指向他们。同样,:ruby哈希表中不包含这些值,它只是指向它们。


2
这个答案很有道理。
瓦斯2014年

这听起来像是哈希和符号的混合体。一个符号并不指向一个值,如果您想说它在哈希中时确实如此,那可能是有争议的,但是符号不必在哈希中。您可以说mystring = :steveT 该符号没有指向任何内容。哈希中的键具有关联的值,并且键可以是符号。但是符号不必在哈希中。
barlop

27

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

以您的示例为例:

patient1 = { :ruby => "red" }

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

另一种写法是:

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值是从内部哈希表中绘制的,并且可以使用简单的方法调用查看已编码为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定义新符号时,该表将增加。


17

符号不是指针。它们不包含值。符号简直就是:ruby是符号:ruby,仅此而已。它不包含值,不执行任何操作,仅以符号形式存在:ruby:ruby就像数字1一样,该符号也是一个值。它指向数字1以外的其他值。


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

那么将输出什么呢?“红色”还是“编程”?

两者都不输出“ ruby​​”。

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


10

简而言之

符号解决了创建人类可读,不可变的表示形式的问题,该表示形式还具有运行时查找比字符串更简单的优点。可以将其视为可以重复使用的名称或标签。

为什么:red比“ red”更好

在面向对象的动态语言中,您会创建具有可读引用的复杂的嵌套数据结构。散列是一种常见的情况,你值映射到唯一键-独特的,至少,每个实例。每个哈希最多只能有一个“红色”键。

但是,使用数字索引而不是字符串键会提高处理器效率。因此,引入符号是为了兼顾速度和可读性。符号比等效字符串更容易解析。通过易于阅读并且易于运行时解析符号,是动态语言的理想补充。

好处

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

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

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

概要

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


4

我建议阅读有关哈希表Wikipedia文章-我认为这将帮助您了解{:ruby => "red"}真正的含义。

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


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

patient1而且patient2都是哈希,没关系。:ruby但是是一个符号。如果要输出以下内容:

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

那么将输出什么呢?“红色”还是“编程”?

当然没有。输出将是ruby。顺便说一句,通过简单地将其键入IRB,您所发现的时间可能比键入问题所需的时间短。

为什么这样red还是programming?符号总是对自己进行评估。符号的值:ruby是符号:ruby本身,的字符串表示形式:ruby是字符串值"ruby"

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


我在当前计算机上没有IRB,因此也无法安装它,所以为什么,为此我深表歉意。
Kezzer

2
@Kezzer:不用担心,我只是好奇。有时,您将自己深深地埋在一个问题中,以至于您再也看不到最简单的东西。当我基本将您的问题粘贴到IRB时,我只是想知道:“他为什么不自己做?” 不用担心,当答案是“只是运行它”时,您不是第一个(也不是最后一个)问“此打印内容是什么”的人!顺便说一句:这是您即时的IRB,随时随地,无需安装:TryRuby.OrgRuby-Versions.Net使您可以通过SSH访问MRI发行的所有版本+ YARV + JRuby + Rubinius + REE。
约尔格W¯¯米塔格

谢谢,现在就玩吧。我还是有点困惑,所以再来一遍。
Kezzer

0

我是Ruby的新手,但我认为(希望吗?)这是一种简单的查看方法...

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

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

所以这:

"hellobuddy"

与此相同:

:hellobuddy

除非您不能这样做,例如:hellobuddy.upcase。它是字符串值,仅是字符串值。

同样,这:

greeting =>"hellobuddy"

与此相同:

greeting => :hellobuddy

但是,同样,没有字符串对象的开销。


-1

一种简单的解决方法是:“如果我使用字符串而不是符号会怎样?

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

一点都不令人困惑,对吧?您正在将“ ruby​​”用作hash中的键。

"ruby"是字符串文字,因此是值。内存地址或指针对您不可用。每次调用时"ruby",您都在创建它的新实例,即创建一个包含相同值-的新存储单元"ruby"

然后,哈希将变成“我的关键值是什么?哦,是"ruby"。然后将该值映射到” red”或“ programming”。换句话说,:ruby不要取消引用"red""programming"。哈希映射 :ruby"red""programming"

比较一下我们是否使用符号

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

的值:ruby也是"ruby"有效。

为什么?因为符号本质上是字符串常量。常量没有多个实例。这是相同的内存地址。一旦取消引用,内存地址就具有特定值。对于符号,指针名称是符号,而取消引用的值是字符串,该字符串与符号名称匹配,在这种情况下为"ruby"

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

范式转换和带回家的概念是,符号的值是与散列所映射的值完全独立的概念,给定该散列的键。


拒绝投票的人在这个解释中有什么谬误或错误?为学习而好奇。
ahnbizcad

仅仅因为类比可能会让某些人反感,并不意味着它就有缺陷。
ahnbizcad

2
我不一定看到任何错误,但是要确定您在此答案中要说的内容非常困难。
逆向工程

基于进行解释的上下文/实体,对x进行了不同的解释和概念化。很简单。
ahnbizcad

彻底修改了答案。感谢您的反馈。
ahnbizcad
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.