Ruby 是一个完全面向对象的语言。对于字符串来说,它们背后的类是 String
。我们今天要介绍的,就是这个 String
类。Ruby 从 1.8 发展到今天的 Ruby 2.5,这个类发生了很大的变化。
因此,本文的目的是详细说明每个主要版本发生的主要更改。
从 Ruby 1.8 到 Ruby 1.9
在 Ruby 1.8 来说,那个时候还不存在 ancestors
祖先链属性,只有 include_modules
属性
这个属性的内容如下
String.included_modules # => [String, Enumerable, Comparable, Object, Kernel]
到了 Ruby 1.9 中,有了祖先链属性,属性内容如下
String.ancestors # => [String, Comparable, Object, Kernel, BasicObject]
从继承链来说,就是抛弃了 Enumerable
添加了 BasicObject
。
我们再来看看它们之间方法的区别
首先是 Ruby 1.8 ,要获取字符串所有的实例方法,可以使用下面的语句
ims_18 = String.instance_methods(false).map(&:to_sym)
其次是 Ruby 1.9,可以使用下面的语句获取字符串所有的实例方法
ims_19 = String.instance_methods(false)
然后,我们就可以通过 ims_19 - ims_18
来获得差异方法
diff = ims19 - ims18 diff # => [:===, :clear, :chr, :getbyte, :setbyte, :byteslice, :codepoints, :prepend, :ord, :each_codepoint, :encoding, :force_encoding, :valid_encoding?, :ascii_only?, :encode, :encode!, :to_r, :to_c]
可以看到,Ruby 1.9 中,为字符串 String
类添加了很多方法,大多数方法都用于处理字节。
发生这样的改变,原因很重要。那就是: Ruby 1.8 中,字符串是一个字节序列,ascii 字符序列,而在 Ruby 1.9 中字符串代码点( codepoint ) 序列。
什么叫做 「 代码点序列 」 呢?
Ruby 1.9 中,每一个字符串对象都有自己的字符编码,字符串字面量总是以定义它的源代码文件的字符编码来编码的。
因此,对于每一个字符串,源文件的编码决定了它的编码,也决定了它是什么编码序列。对于 ASCII 字符,那么还是原来的字节序列,但对于 GBK 和 UTF8 ,那么就是 GBK 序列和 UTF8 序列。
比如下面这段代码
#!/usr/bin/env ruby # coding: utf-8 str = "中文" sym = :name regex = Regexp.new(str.encode("GBK")) puts str.encoding puts sym.encoding puts regex.encoding
运行的结果如下
[root@www.twle.cn ruby] ruby demo.rb UTF-8 US-ASCII GBK
虽然每个字符串对象看起来都有自己的编码,但在硬盘上,它们都是一字节序列来存储的。编码只是指定如何获取这些字节并将它们转换为代码点。
因为这种变更,从 Ruby 1.9 开始,Ruby 本身可以处理字符串编码,而在 1.8 及之前的版本中需要 iconv
库来完成这项工作。
也因为这个原因,在 Ruby 1.9 中不推荐使用 iconv 库,因为它已经被废弃了。
注意: 每个字符串的默认编码是二进制,也成为字节序列。
Ruby 1.9 到 Ruby 2.0
Ruby 2.0 中,最重要的变更,就是确定使用 UTF-8 作为文件的默认编码,也是唯一的编码。这也间接造成了字符串对象的默认编码是 UTF-8。
而在 Ruby 1.9 及之前的版本中,默认的编码是二进制 ( Binary ) ,也就是字节序列。
这种行为与使用 UTF16 作为默认编码的 Java 有点类似。
同时需要注意的是,Ruby 2.0 中,iconv
库已经完全删除,再也不是自带的一个库了。
Ruby 2.0 到 Ruby 2.1
虽然 Ruby 2.0 中使用 UTF-8 作为默认编码,但是,将已经编码的字符串编码为相同的字符串(例如 UTF8
到UTF8
)会导致无操作 ( no-op )
比如在 Ruby 1.9 及之前的版本中,字符串编码的行为如下
# in ruby <= 2.0.x content = "Is your pl\xFFace available?".force_encoding("UTF-8") content.encode("UTF-8", invalid: :replace) # => "Is your pl\xFFace available?"
但在 Ruby 2.1.x 中,字符串的编码行为则变得诡异了
# in ruby 2.1.x content = "Is your pl\xFFace available?".force_encoding("UTF-8") content.encode("UTF-8", invalid: :replace) # => "Is your pl�ace available?"
在这里我们可以看到,在 Ruby 2.0 中,我们在 UTF8 中显式编码的 UTF8 字符串返回字符串而不替换未知的代码点。所以 invalid: :replace
操作是被忽略了的。
而在 Ruby 2.1 中, invalid: :replace
操作是被处理的,默认字符 �
替换了序列中的每个无效代码点。
Ruby 2.1 到 2.5
从 Ruby 2.1 到 Ruby 2.5,String
类的大多数变更都体现在提高性能上。但,仍然有两个主要的功能值得讲解下。
-
一个是 Ruby 2.3 中新增的
frozen_string_literal: true
魔法注释frozen_string_literal: true
注释用于告诉 Ruby 解释器,所有的字符串都是不可变的。因为不可变,对于相同的字符串,也就没必要重新创建一个新的对象
# frozen_string_literal: true hash = { 'key' => 'value' } hash['key'] # 这里并不会为 'key' 字符串创建一个新的实例
-
Ruby 2.4 中的非 ASCII 字符串的大小写转换 这个,不多说了,直接看代码就好了
'Türkiye'.upcase # => "TÜRKIYE" - Full Unicode case mapping 'Türkiye'.upcase :ascii # => "TüRKIYE" - ASCII only 'Türkiye'.upcase :turkic # => "TÜRKİYE" - Full Unicode case mapping, adapted for Turkic languages "Ačiū".upcase :lithuanian # => "AČIŪ" - Full Unicode case mapping, adapted for Lithuanian