其实我一直对 symbol 很感兴趣,但是一直没有理解 symbol 到底有啥用,怎么用,我自己也想借这篇文章来了解下。
对了,symbol 我们一般翻译为 「 符号 」,很通俗易懂。
我觉得如果要理解 symbol ,就要弄清楚下面三个知识点:
- symbol 是什么意思?
- Ruby 中的 symbol 到底是什么?
- Ruby 中的 Symbol 类
- Ruby 中的 Symbol 类背后的逻辑
symbol 是什么意思?
symbol 一般用于标识可以量化的资源。如果用普通人能理解的方式,就是创建一个可以量化的单位。比如 个
, 可以一个人,两个人,三个人,可以一个豆子,两个豆子。比如长度单位 cm (厘米),比如 1cm 、10cm、100cm。
symbol 必须是唯一的,这样大家看到的这个 symbol 的时候都是一个意思,比如,你不能在北京说一个栗子是 1 栗子
,在天津说一个栗子是 2 栗子
。
Ruby 中的 symbol 到底是什么?
Ruby 中的 symbol 是 Symbol
类的实例,用于唯一标识特定资源。这个资源可以是:
- 一个方法
- 一个变量
- 一个哈希键 ( key )
- 一个状态 ( state )
- 等等
symbol 必须是唯一的,因为运行中的程序,对于特定的资源,只能创建一个唯一的 Symbol
类的实例。
比如下面的代码,输出的都是唯一的值
demo.rb
p :pending.object_id p :pending.object_id
运行结果如下
[root@www.twle.cn ruby]# ruby demo.rb 989468 989468
从上面的输出中可以看到,:pending
符号虽然调用了两次,但实际上只创建一次,因为 :pending.object_id
返回相同的对象标识符。
symbol 与字符串的区别
symbol 常常会与字符串比较,也会被常常问到: symbol 与字符串有什么区别 ?
symbol 类似于字符串,但与字符串有着本质的区别:
对于每一个新的字符串,都会创建一个新的 String
类的实例。但对于 symbol ,永远只会创建一次 Symbol 类的实例。
例如下面的代码
demo.py
p 'pending'.object_id p 'pending'.object_id
运行结果如下
[root@www.twle.cn ruby]# ruby demo.rb 70119665954000 70119665953860
经过上面的学习,我们已经对 symbol 有了基本的了解,现在,我们来看看 Symbol 类及它提供的方法
Ruby 中的 Symbol 类
Symbol
类是 Ruby 核心库 ( core-lib
) 的一部分。
虽然 Symbol
是一个类,但它没有提供公开的构造方法 ( Symbol.new
),也就不能公开实例化。例如下面的代码会报错
[root@www.twle.cn ruby]# irb irb(main):001:0> Symbol.new NoMethodError: undefined method `new' for Symbol:Class from (irb):1 from /usr/bin/irb:11:in `<main>' irb(main):002:0>
但是,我们可以使用冒号 :
加上 symbol 的标识来隐式的创建一个 Symbol 的实例。就像上面我们创建 :pending
一样
irb(main):002:0> :pending.class => Symbol
我们可以调用 Symbol.ancestors
来看看 Symbol
的祖先链,也就是 Symbol
的继承链
irb(main):003:0> Symbol.ancestors => [Symbol, Comparable, Object, Kernel, BasicObject]
从祖先链中可以看到,Symbol
类默认继承自 Object
类。
另外需要注意的是,Symbol
包含了 Comparable
模块,也就可以用于比较两个 symbol 是否相等
irb(main):004:0> :sm == :cm => false irb(main):005:0> :sm > :cm => true irb(main):006:0> :sm < :cm => false irb(main):007:0> :sm == :sm => true
如果你稍加留意,就会发现 Symbol 类与 String 类和 Numberic 类有着同样的祖先链
irb(main):008:0> String.ancestors => [String, Comparable, Object, Kernel, BasicObject] irb(main):011:0> Numeric.ancestors => [Numeric, Comparable, Object, Kernel, BasicObject]
Symbol 类提供了许多有趣的方法,这些方法大致可以分为三大类:比较、修改和匹配。大多数修改和匹配符号的方法都使用 Symbol#to_s
方法转换为字符串,以便使用符号的 String 表示
Ruby 中的 Symbol 类背后的逻辑
我们在上面有说过,每一个 symbol 都是唯一的,都是 Symbol 类的唯一实例。因此,Ruby 必须跟踪它们中的每一个以确保它们的唯一性。
为了实现唯一和跟踪目的,Ruby 提供了一个名为 global_symbols
的内部表,负责跟踪正在运行的程序的所有符号。
注意:对于所有 Ruby 版本< 2.2.0 来说,符号只会被放入内存一次。这使得它们使用起来非常有效。它们会一直留在内存中直到程序退出。但比这个更高的版本,符号也会被垃圾收集。
因为对于 Ruby > 2.2.0 的版本,symbol 也会参与垃圾搜集,所以,一般情况下我们并不直接使用 global_symbols
全局表(也获取不到)。Ruby 提供了 Symbol.all_symbols
方法返回一个数组,该数组表示方法调用时 global_symbols
表的内容。
你可以运行下面的代码来演示和查看每次调用 Symbol.all_symbols
方法的不同
Symbol.all_symbols.length # => 3893 Symbol.all_symbols.grep(/Struct/) # => [:Struct] :dummy_symbol Symbol.all_symbols.length # => 3894 Symbol.all_symbols.grep(/dummy_symbol/) # => [:dummy_symbol] dummy_variable = nil Symbol.all_symbols.length # => 3895 Symbol.all_symbols.grep(/dummy_variable/) # => [:dummy_variable] def dummy_method; end Symbol.all_symbols.length # => 3896 Symbol.all_symbols.grep(/dummy_method/) # => [:dummy_method] class DummyClass; end Symbol.all_symbols.length # => 3897 Symbol.all_symbols.grep(/DummyClass/) # => [:DummyClass] Symbol.all_symbols.grep(/Hash/) # => [:Hash] class Hash; end Symbol.all_symbols.length # => 3897
第一个
Symbol.all_symbols.length
输出结果可能因为版本的不同而不同。
在文章开篇时我们就说过,一个符号可以是一个变量,或一个方法,或一个类。
从输出结果中可以看到,程序刚开始时,global_symbols
并不是空的,而是存在 3800+ 个符号,这是为什么呢?
这是因为,对于 Ruby 程序来说,该表填充了 Ruby 核心库中包含的所有方法,变量和类。这些资源作为符号插入表中。
例如,Struct
类就是 Ruby 核心库的一部分。