Gangmax Blog

Ruby学习笔记第三篇: character encodings

在字符编码这个问题上,Ruby1.9相比1.8版本有了大幅变化,本文针对的是Ruby1.9版本,主要参考了这篇文章

Ruby有三种默认的encodings设置。

###Source Encoding

Ruby的程序源文件可以使用多种字符集编码来编写,所以你必须让Ruby解释器知道你的源文件使用的是哪种字符集编码。请看以下这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$ cat no_encoding.rb 
p __ENCODING__
$ ruby no_encoding.rb
#<Encoding:UTF-8>

$ cat magic_comment.rb
# encoding: gbk
p __ENCODING__
$ ruby magic_comment.rb
#<Encoding:GBK>
$ cat magic_comment2.rb
#!/usr/bin/env ruby -w
# encoding: gbk
p __ENCODING__
$ ruby magic_comment2.rb
#<Encoding:GBK>

$ echo $LANG
zh_CN.utf8
$ ruby -e 'p __ENCODING__'
#<Encoding:UTF-8>

$ ruby -KU no_encoding.rb
#<Encoding:UTF-8>

从中我们可以知道:

  1. 我们可以使用”__ENCODING__“关键字来得到当前正在运行的源程序的字符编码;

  2. 我们可以使用”magic comment”来指定源程序的字符编码。如果你的源程序的第一行是注释,并且包含了”coding”这个单词,其后跟着一个分号和一个空格,然后是一个字符编码(encoding)名称,那么这个字符编码名称就被用来指定当前Ruby源程序的字符编码,这一行被成为”魔术注释(magic comment)”;

  3. 当你使用交互的命令行方式运行Ruby,则启动Ruby时给定的”-e”开关被用来指定当前运行的Ruby源程序字符编码,即:你在命令行中输入的Ruby程序行会被Ruby解释器用该字符编码来读取。如果你在启动Ruby的命令行中没有指定”-e”开关,则Ruby解释器会读取操作系统环境变量”LC_CTYPE”或”LANG”来作为读取命令行内容时使用的字符编码。

  4. “-KU”参数是为了保持Ruby1.8的兼容性所使用的参数开关,在Ruby1.9中应避免使用。使用”magic comment”是推荐的方式。

  5. “magic comment”的格式相当宽松,以下写法都是合法的:

1
2
3
4
5
# encoding: UTF-8

# coding: UTF-8

# -*- coding: UTF-8 -*-

###IO Encodings: External and Internal Encodings

很多情况下,String对象是根据从IO对象读入的内容创建的。这时的String字符编码和当前的source encoding无关,而与IO有关。此时程序员确有必要知道应以何种字符编码正确读写IO内容。为此,Ruby1.9引入了external encoding和internal encoding的概念。看以下两段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ cat show_external.rb 
open(__FILE__, "r:UTF-8") do |file|
puts file.external_encoding.name
p file.internal_encoding
file.each do |line|
p [line.encoding.name, line]
end
end
$ ruby show_external.rb
UTF-8
nil
["UTF-8", "open(__FILE__, \"r:UTF-8\") do |file|\n"]
["UTF-8", " puts file.external_encoding.name\n"]
["UTF-8", " p file.internal_encoding\n"]
["UTF-8", " file.each do |line|\n"]
["UTF-8", " p [line.encoding.name, line]\n"]
["UTF-8", " end\n"]
["UTF-8", "end\n"]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ cat show_internal.rb 
open(__FILE__, "r:UTF-8:UTF-16LE") do |file|
puts file.external_encoding.name
puts file.internal_encoding.name
file.each do |line|
p [line.encoding.name, line[0..3]]
end
end
$ ruby show_internal.rb
UTF-8
UTF-16LE
["UTF-16LE", "o\x00p\x00e\x00n\x00"]
["UTF-16LE", " \x00 \x00p\x00u\x00"]
["UTF-16LE", " \x00 \x00p\x00u\x00"]
["UTF-16LE", " \x00 \x00f\x00i\x00"]
["UTF-16LE", " \x00 \x00 \x00 \x00"]
["UTF-16LE", " \x00 \x00e\x00n\x00"]
["UTF-16LE", "e\x00n\x00d\x00\n\x00"]

从代码中可以看出:

  1. 读取文件时,可以显式的指定external encoding,方法是调用open方法时加入read参数”open(__FILE__, “r:UTF-8”)”;

  2. 如果读取文件时不指定internal encoding,则File的internal encoding对象默认为nil。这种情况下读取的String对象会默认继承并使用external encoding来创建;

  3. 在第二个例子中,调用open方法同时指定了external和internal encodings: “open(__FILE__, “r:UTF-8:UTF-16LE”)”。

  4. 在显式指定File对象的interal encoding之后,保存File内容的String对象使用File的internal encoding来创建。

  5. 在写文件的时候,情况也类似:Ruby会使用open方法指定的external encoding来保存文本文件。在写文件的情况下,不需要指定internal encoding,因为此时你的String对象已经有了自己的encoding,Ruby只需使用它的encoding作为internal encoding即可。具体请看下面的例子。另外,无论是读还是写,当不同字符编码之前需要转换时,Ruby会自动做这一动作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ cat write_internal.rb 
# encoding: UTF-8
open("data.txt", "w:UTF-16LE") do |file|
puts file.external_encoding.name
p file.internal_encoding
data = "My data…"
p [data.encoding.name, data]
file << data
end
p File.read("data.txt")
$ ruby write_internal.rb
UTF-16LE
nil
["UTF-8", "My data…"]
"M\x00y\x00 \x00d\x00a\x00t\x00a\x00& "

由上述代码可知,IO Encodings的概念比较直接。剩下的问题是:默认情况下会使用什么encoding?

默认的external encoding会来自你的环境变量”LC_CTYPE”或”LANG”,默认的internal encoding为nil。你可以使用以下这两个全局方法修改它们:”Encoding.default_external=()”和”Encoding.default_internal=()”。你还可以在命令行启动Ruby的时候使用开关:

1
2
3
4
5
6
7
8
9
10
11
$ ruby -e 'p [Encoding.default_external, Encoding.default_internal]'
[#<Encoding:UTF-8>, nil]
$ ruby -E Shift_JIS \
> -e 'p [Encoding.default_external, Encoding.default_internal]'
[#<Encoding:Shift_JIS>, nil]
$ ruby -E :UTF-16LE \
> -e 'p [Encoding.default_external, Encoding.default_internal]'
[#<Encoding:UTF-8>, #<Encoding:UTF-16LE>]
$ ruby -E Shift_JIS:UTF-16LE \
> -e 'p [Encoding.default_external, Encoding.default_internal]'
[#<Encoding:Shift_JIS>, #<Encoding:UTF-16LE>]

可以看出,这里的命令行开关参数,和File.open()方法参数一致。

Ruby的”-U”命令行开关,会将”Encoding.default_internal()”设置为”UTF-8”,这样,在打开File这种IO对象时,你只需关心”external encoding”,而”internal encoding”统一使用”UTF-8”。

最后要强调,”Encoding.default_external=()”和”Encoding.default_internal=()”这两个方法会影响全局的IO操作,所以尽量少用慎用或不用,相反,在打开每个IO对象的时候指定IO encodings是比较好的实现方式。

Comments