如何理解Ruby中的符号

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

这从链接引述:

与string不同,同名的符号被初始化,并且在ruby会话期间只存在于内存中一次

我不明白它是如何pipe理区分相同的内存位置中包含的值。

考虑这个例子:

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"

忘记哈希一秒钟,我在想一个符号是一个价值的指针 。 我有的问题是:

  • 我可以给一个符号赋值吗?
  • 符号只是一个指向variables的指针吗?
  • 如果符号是全球性的,这是否意味着一个符号总是指向一个东西?

考虑这个:

 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 

所以,无论你创build一个符号对象,只要其内容相同,它就会引用内存中的同一个对象。 这不是一个问题,因为符号是不可变的对象 。 string是可变的。


(回应下面的评论)

在原始文章中,值不是被存储在一个符号中,而是被存储在一个散列中。 考虑这个:

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

这会在内存中创build六个对象 – 四个string对象和两个哈希对象。

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

这只会在内存中创build五个对象 – 一个符号,两个string和两个散列对象。

当我想到这件事的时候,我能够发现符号。 Rubystring是一个有一堆方法和属性的对象。 人们喜欢使用string作为键,而当string被用作键时,所有这些额外的方法都不被使用。 所以他们做了符号,它是除去了所有function的string对象,除了它是一个好钥匙所需要的。

只要把符号看作是不变的string。

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

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

您可能会假定您所做的声明将符号的值定义为除了原来的值之外的值。 实际上,符号只是一个“内化”的string值,保持不变。 这是因为它们使用简单的整数标识符进行存储,因此比pipe理大量的可变长度string更为有效。

以你的例子为例:

 patient1 = { :ruby => "red" } 

这应该被读作:“声明一个variablespatient1并且定义它是一个哈希值,并且在这个存储区中在键(符号”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 

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

 [ "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 

同样,从符号转换为string每次创build一个不同的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定义新的符号时,这个表将会增长。

符号不是指针。 他们不包含价值。 符号只是。 :ruby是符号:ruby ,这就是它的一切。 它不包含任何值,它不会任何事情,它只是作为符号存在:ruby 。 符号:ruby是一个值,就像数字1一样。 它并不指向数字1以外的其他数值。

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

那么会输出什么? “红”还是“编程”?

也不会输出“ruby”。

你混淆了符号和哈希。 他们不相关,但他们一起使用。 这个符号是:ruby ; 它与散列值无关,它的内部整数表示将始终是相同的,它的“值”(当转换为string)将始终是“ruby”。

简而言之

符号解决了创build人类可读,不可变表示的问题,这种表示也比运行时更易于查找。 把它看作是可以重复使用的名称或标签。

为什么:红色胜过“红色”

在面向dynamic的面向对象语言中,您可以使用可读的引用创build复杂的嵌套数据结构 散列是一种常用的情况 ,您可以将值映射到唯一键 – 至less对每个实例都是唯一的。 每个散列不能有多个“红色”键。

但是,使用数字索引而不是string键会更有效率。 所以引入符号作为速度和可读性之间的折衷。 符号的parsing比等效的string要容易得多。 由于人类可读,并且运行时易于parsing符号,因此是dynamic语言的理想补充。

优点

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

因为:红色总是回到内存中的相同位置,所以可以在一百个散列实例中重用,而内存几乎不增加,而使用“红色”将增加内存开销,因为每个散列实例需要将可变串存储在创build。

不确定Ruby实际上是如何实现符号/string的,但是显然,符号在运行时提供了更less的实现开销,因为它是一个固定的表示。 加上符号比stringless一个字符,打字less就是真正的Rubyists永恒的追求。

概要

使用像红色这样的符号,由于string比较操作的成本以及需要将每个string实例存储在内存中,所以可以获得string表示的可读性,而且开销更less。

 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 。 顺便说一句,你可以用比input问题更less的时间find问题,只需在IRB中input它即可。

为什么redprogramming ? 符号总是自己评估。 符号的值:ruby是符号:ruby本身和符号的string表示forms:ruby是string值"ruby"

无论如何,put总是把它的参数转换成string。 没有必要打电话给它。]

我build议阅读关于散列表的维基百科文章 – 我认为这将帮助你了解{:ruby => "red"}真正含义。

另一个可能有助于你了解情况的练习:考虑{1 => "red"} 。 在语义上,这并不意味着“将1的值设置为"red" “,这在Ruby中是不可能的。 相反,它的意思是“创build一个哈希对象,并将值"red"存储为密钥1

我是Ruby新手,但我想(希望?)这是一个简单的方法来看待它…

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

它是一个没有对象开销的string。 文本和只有文本。

所以这:

 "hellobuddy" 

是这样的:

 :hellobuddy 

除非你不能做,例如:hellobuddy.upcase。 这是string值和唯一的string值。

同样,这个:

 greeting =>"hellobuddy" 

是这样的:

 greeting => :hellobuddy 

但是,再次,没有string对象的开销。

“指向”由散列控制,而不是由符号控制。

一个符号是它自己独特的东西,就像一个实例对象。 或任何其他数据types。

(实际上,可以将符号看作string常量,而由与符号相同字符组成的string将是String类的实例对象,其好处是减less了创build的对象的数量,减less了内存使用,减less了由于引用不同的对象而不是相同的符号对象引起的质朴的行为)。

使用:ruby"red""programming"patient1patient2

哈希patient1看着符号:ruby ,然后去,“哦,对应于"red"

哈希patient2看着符号:ruby ,然后去,“哦,这对应于"programming"