Ruby symbol String


Ruby的符号足以让很多初学者迷惑上一段时间,看过本章节后,或许会解开你心中的疑惑。

在Ruby中,一个符号是就是一个Symbol类的实例,它的语法是在通常的变量名前加一个冒号,如 
:my_sy

Ruby的符号像一个字符串,因为它内部表现形式是一个字符序列。而与字符串不同的是,每个符号的实例只有一个。看下面的例子:
array = ["foo", "foo", "foo", :foo, :foo, :foo]

建立这个数组后,内存中将有三个内容为"foo"的字符串对象,而只有一个:foo对象。

一个符号有它唯一对应的一个整数值,可以用to_i来获取它。

符号常用的地方是用它来代表变量名或者方法名:
class SomeClass
attr_accessor :whatever
end

与上面程序对等的程序如下:
class SomeClass
def whatever
    @whatever
end
def whatever=(val)
    @whatever = val
end
end

你可能会问,为什么不用字符串来表示呢?其实,字符串也是可以的:
attr_reader :alpha
attr_reader "beta"   # 也是合法的

那它们的区别在哪里?某种意义上来说,是为了提高程序性能,节省内存吧。

有的人说,"符号就如同不可变的字符串",从它的行为上来说,可能是对的。但是,Symbol并是是继承至String,而且,与字符串相关
的那些典型操作方法,Symbol也不一定有,也不需要有。


符号不一定看起来是像变量一样,它也可以这样:
sym1 = :"This is a symbol"
sym2 = :"This is, too!"
sym3 = :")(*&^%$"
你可以用这样奇怪的符号来定义实例变量名或方法,你可以使用诸如send或instance_variable_get来获取它们的引用。虽然是可行的,
但请尽量不要使用这么奇怪的符号。


用Symbol作为枚举
==========================================
像Pascal或版本稍晚的C语言,都存在enum这种类型。Ruby不能正真的拥有这样的类型,因为它没有类型检查。但使用Symbol可以用来产生
这样的效果:
North, South, East, West = :north, :south, :east, :west

用Symbol作为元数据
==========================================
通常我们使用捕获异常的方式来处理错误,避免使用老式的返回代码的方式。但是,如果你想用老式的方式,还是可以的。像nil就是这样一个元数据。
我们通常可以这样使用(因为符号是全局的,所以在之后的整个程序中,都可以使用这些符号来作为元数据):
str = get_string
case str
when String
    # Proceed normally
when :eof
    # end of file, socket closed, whatever
when :error
    # I/O or network error
when :timeout
    # didn't get a reply
end


Symbols, Variables, Methods
==========================================
Symbol最常用的地方就是我们熟知的定义属性了:
class MyClass
attr_reader :alpha, :beta
attr_writer :gamma, :delta
attr_accessor :epsilon
# ...
end
这里的方法,如attr_accessor,它以传入的符号来确定实例变量的名称,以及读写属性的方法的名称,但这不代表所有的情况
(即总是会自动精确匹配符号和实例变量的名称),例如,当使用instance_variable_set方法的时候,符号和实例变量名必须
精确给出:
sym1 = :@foo
sym2 = :foo
instance_variable_set(sym1,"str")   # 正常,匹配到@foo
instance_variable_set(sym2,"str")   # 错误

不过,记住,在这些地方不一定要使用符号,可以用字符串来替代。


转换Symbol
============================================
Symbol和字符串之间可以互相转换,使用to_s和to_sym来实现:
a = "foobar"
b = :foobar
a == b.to_str    # true
b == a.to_sym    # true

=========================================================

1:是Symbol类的实例 
2: 是名称的对象 
3:符号是不可改变的字符串,也不可被GC的 
4:在大多数情况下,接受符号作为参数的方法也能接受字符串,反过来不成立; 
5:用符号要比用字符串省内存 
6:每个符号在对象空间中只出现一次:和Fixnum一样,都存为直接值 
7:有意思的Symbol#to_proc 
ri Symbol 可看究竟

===========================================================

1. 在一个名字或者字符串前面加上冒号,得到一个symbol对象。还可以通过String#to_sym、Fixnum#to_sym和String#intern得到。

2. 一般用symbol做hash的key,号称是为了节省内存,提高执行效率。

3. 为什么可以节省内存?Ruby中的String是可变对象,这一点跟Java、C#、Python都不一样。注意跟某些C++标准库中的COW的basic_string<T>也不一样。Ruby中每一个String都可以就地改变。可能是因为这个原因,Ruby中两个内容相同的字符串文本量实际上是两个不同的对象。

    a = "hello"
    b = "hello"

    虽然俩字符串内容都一样,但是你比一下a和b,就知道a.object_id != b.object_id,它们指向的不是同一个对象。结果反而很像未经string pooling优化的C语言的行为。到底immutable好还是mutable好,或者还是貌似聪明的COW好,见仁见智了。不过Ruby的设计在把字符串用作hash key的时候毛病就大了。比如你写:

    h["ruby"].name   = "Ruby"
    h["ruby"].author = "matz"
    h["ruby"].birth_year = 1995

的时候,"ruby"这个字符串动态生成了三次,占用三倍内存。这就严重地浪费了内存。而用:ruby做为key,因为在整个运行过程中,Ruby runtime保证名为:ruby的symbol对象只有一个,所以就不用生成三个,节省内存。

4. 为什么可以提高执行效率?显然的原因是免得多次动态生成'ruby'字符串了。还不单如此,Hash的key值应该是常量,所以Ruby的Hash对于作为key的String对象都要施加保护,所谓保护,也就是把String冻结了,免得你之后还改变其值。保护当然是有代价的,symbol无需保护,当然是能提高效率的。附带说明,其他mutable的对象也可以作为hash的key,这是Ruby设计得比较奇怪的地方。在irb里运行以下代码,你会发现Ruby的Hash丢值。

    h = Hash.new
    L = [1, 2]
    h[L] = "A big object!"
    L << 3 # 居然能改! 
    h[L]   # ==> nil,找不到了,似乎正常
    # 可是
    h[[1, 2]] # ==> nil,居然还是找不到
    # 看看keys
    h.keys     # ==> {[1, 2, 3]} 似乎还在里面
    h[[1, 2, 3]] # ==> nil
    # 可是
    h          # ==> {[1, 2, 3]=>'A big object'},明明在这里,就是找不到

    h.rehash # ==> 这样就会一切恢复正常。
   
    这一点上Python的设计要比较容易理解,list根本就是unhashable的,不能用来做hash的key。

        回过头来在说提高效率的事。Symbol效率提高还有第三个原因,那是因为symbol本质上不比一个整数多出多少东西,用Symbol#to_i可以得到一个在整个程序中唯一的整数。Hash完全可以利用这个整数来产生hash值,那岂不是比根据字符串内容去算hash值快得多?这还是小意思,既然这个整数是唯一的,那么产生一个唯一的hash值也就是小菜一碟,要是能保证hash值唯一,那还是什么hash表,根本就变成数组了。Hash表还可能会冲突,数组根本不会冲突,百分之百保证O(1),当然快。我没看Ruby源码,不知道是不是这么处理的。

5. 为什么Ruby runtime可以保证每一个symbol唯一?因为Ruby把symbol存放在运行时维护的一个符号表里了,而这个符号表实际上是一个atom数据结构,其中存储着当前所有的程序级的name,确保不出现内容相同的多个对象。几乎每一个语言和系统都会有这样一个符号表,只不过象C/C++那样的语言,这个符号表只是在编译时存在,运行时就没了。而Python、Ruby则在运行时也保留这张表备用。有这样一个现成的数据结构干嘛不用?

6. 但是这个表中存放的并不光是我们自己主动生成的symbols,还有Ruby解释器对当前程序进行词法分析、语法分析后存在其中的、当前程序的所有名字。这可是Ruby引擎用的东西啊,我们只要加上一个冒号,就让自己的对象跟Ruby引擎内部使用的对象成邻居了。所以String#intern这个方法叫做intern(内部化)。

   .NET Framework中String类也有一个Intern方法,意思是一样一样一样的,在李建忠的经典译本里翻译为“驻留”。

7. 可以用Symbol#all_symbols查看当前定义的全部symbol。可以体验一下自己往符号表中塞一个对象的感觉,想想你写的程序跟Ruby引擎能干一样的事情,应该还是挺爽的。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值