Thursday, October 16, 2008

(13)字符串编码和多字节字符

较之Ruby1.8,Ruby1.9中的String对象有了跟不上的改变。
  • 在Ruby1.8中字符串对象是一个字节序列,当字符串用于描述文本的时候,每一个字节都被当作一个单独的ASCII字符。在Ruby1.8中字符串中的一个元素不是字符而是一个数字,这个数字是字符编码的实际的值。
  • 在Ruby1.9中字符串对象是一个真正的字符序列,并且这些字符并不一定是ACSII字符集内的。在Ruby1.9中字符串中的一个元素是一个字符而不再是一个ACSII码数字,所以字符串是有其自己的编码方式的。
Ruby1.9重写了String对象以便对多自己编码提供更好的支持。如果一个字符串对象包含多字节字符,那么字符串的字节的长度和其字符长度就不同了。在Ruby1.9中length和size返回的是字符串中字符的个数,并且提供了一个新的方法bytesize返回字符串的字节长度:


# -*- coding: utf-8 -*- # 指定Unicode UTF-8

# 这个字符串中包含一个多字节字符
s = "2x2=4"

# 这个字符串的字符长度是5,但是字节长度为6
s.length # => 5: 字符: '2' 'x' '2' '=' '4'
s.bytesize # => 6: 字节(hex): 32 c3 97 32 3d 34

注意,你必须在第一行指定字符的编码,比如utf-8,否则Ruby解析器不知道该如何解码你的多字节字符。
在包含多字节字符的文本中如果你使用[]方式来查找文本中的内容的话,实际上程序会从头到尾的读取这个文本,这样会带来效率问题,一个好的做法是你自己通过each_char的方式来查找字符串的内容,当然如果你没有这样使用,问题也不大,Ruby会自动调优你的程序。
Ruby1.9的字符串定义了一个encoding 方法,你可以通过它得到字符串的编码格式,比如:
s="你好"
s2="Heelo"
puts s.encoding
puts s2.encoding

打印结果为:
UTF-8
ASCII-8BIT

String对象的操作,比如连接两个字符串或者匹配查找,需要两个字符串的编码是兼容的,比如你将一个UTF-8的字符串与一个ACSII编码的字符串相链接,这是可以的,因为UTF-8和ASCII是兼容的,但是如果你将一个UTF-8和一个SJIS的字符串连接或出现异常。你可以通过Encoding.compatible?方法来判断两个字符串是否是编码兼容的,如果是,那么这个方法的返回值为兼容性更好的字符编码:
#-- encoding: utf-8 --
s="你好"
s2="Heelo"
puts Encoding.compatible?(s,s2)

返回值为:UTF-8

你还可以指定用什么编码来处理一个文本,比如有的时候你可以要读取一个文本,并且告诉Ruby用那种编码格式来处理它:
text = stream.readline.force_encoding("utf-8")
bytes = text.dup.force_encoding(nil) # nil 代表字节编码

force_encoding方法返回的不是一个对象拷贝,它会直接改变对象本身的内容。force_encoding 不会校验文本是否符合你给出的编码规格,如果你要校验它必须调用valid_encoding?:
= "\xa4".force_encoding("utf-8")  # 这不是一个UTF-8字符串
s.valid_encoding? # => false

encode 方法和force_encoding不同,他会返回一个字符串拷贝,原有的字符串不会变化:
# -*- coding: utf-8 -*-
euro1 = "\u20AC" # 定义一个unicode字符串
puts euro1 # 打印"€"
euro1.encoding # => <Encoding:UTF-8>
euro1.bytesize # => 3

euro2 = euro1.encode("iso-8859-15") # 转换为Latin-15
puts euro2.inspect # 打印"\xA4"
euro2.encoding # => <Encoding:iso-8859-15>
euro2.bytesize # => 1

euro3 = euro2.encode("utf-8") # 转换为 to UTF-8
euro1 == euro3 # => true

通常你很少直接使用encode方法,我们大多数的应用是将文本写入文件或者网络,这是Ruby的I/O库会自动转换编码。当你指定一个转换的编码但是Ruby无法转换的时候会抛出一个异常。
如果你在Ruby1.8中遇到多字节编码,你可以使用jcode库,这是一个Ruby的标准库,你只要在代码的开头指定你要使用的编码就可以使用类似Ruby1.9中的each_char功能。
$KCODE = "u"        # 指定 Unicode UTF-8, 或者在Ruby命令行中开启 -Ku 选项
require "jcode" # 加载多字节字符支持库

mb = "2\303\2272=4" # 相当于"2x2=4" 不过使用了多字节字符编码
mb.length # => 6: length返回的长度是6,是字节长度
mb.jlength # => 5: jlength返回的长度是5,是字符长度
mb.mbchar? # => 1: 第一个出现多字节字符的位置,如果没有就返回 nil
mb.each_byte do |c| # 在字节码中迭代.
print c, " " # 变量c 是一个Fixnum的整数
end # 输出 "50 195 151 50 61 52 "
mb.each_char do |c| # 在字符中迭代
print c, " " # 变量c是一个jlength 为1 的字符串
end # 输出 "2 x 2 = 4 "

No comments: