编码

基础知识

字符编码,通常简称为编码,是指以下两者之间的映射:

某些字符集仅包含 1 字节字符;例如,US-ASCII 具有 256 个 1 字节字符。此字符串以 US-ASCII 编码,具有六个字符,存储为六个字节

s = 'Hello!'.encode('US-ASCII')  # => "Hello!"
s.encoding                       # => #<Encoding:US-ASCII>
s.bytes                          # => [72, 101, 108, 108, 111, 33]

其他编码可能涉及多字节字符。例如,UTF-8 编码了一百多万个字符,每个字符用一到四个字节编码。这些字符中值最低的对应于 ASCII 字符,因此是 1 字节字符

s = 'Hello!' # => "Hello!"
s.bytes      # => [72, 101, 108, 108, 111, 33]

其他字符,例如欧元符号,是多字节的

s = "\u20ac" # => "€"
s.bytes      # => [226, 130, 172]

Encoding 类

Encoding 对象

Ruby 编码由 Encoding 类中的常量定义。对于每个常量,只能有一个 Encoding 实例。方法 Encoding.list 返回一个 Encoding 对象数组(每个常量一个)

Encoding.list.size        # => 103
Encoding.list.first.class # => Encoding
Encoding.list.take(3)
# => [#<Encoding:ASCII-8BIT>, #<Encoding:UTF-8>, #<Encoding:US-ASCII>]

名称和别名

方法 Encoding#name 返回一个 Encoding 的名称

Encoding::ASCII_8BIT.name  # => "ASCII-8BIT"
Encoding::WINDOWS_31J.name # => "Windows-31J"

一个 Encoding 对象有零个或多个别名;方法 Encoding#names 返回一个包含名称和所有别名的数组

Encoding::ASCII_8BIT.names
# => ["ASCII-8BIT", "BINARY"]
Encoding::WINDOWS_31J.names
#=> ["Windows-31J", "CP932", "csWindows31J", "SJIS", "PCK"]

方法 Encoding.aliases 返回所有别名/名称对的哈希

Encoding.aliases.size # => 71
Encoding.aliases.take(3)
# => [["BINARY", "ASCII-8BIT"], ["CP437", "IBM437"], ["CP720", "IBM720"]]

方法 Encoding.name_list 返回所有编码名称和别名的数组

Encoding.name_list.size # => 175
Encoding.name_list.take(3)
# => ["ASCII-8BIT", "UTF-8", "US-ASCII"]

方法 name_list 返回的条目比方法 list 多,因为它同时包含名称和它们的别名。

方法 Encoding.find 返回给定名称或别名的 Encoding(如果存在)

Encoding.find("US-ASCII")       # => #<Encoding:US-ASCII>
Encoding.find("US-ASCII").class # => Encoding

默认编码

上面的方法 Encoding.find 也为以下每个特殊名称返回一个默认的 Encoding

方法 Encoding.default_external 返回默认的外部 Encoding

Encoding.default_external # => #<Encoding:UTF-8>

方法 Encoding.default_external= 设置该值

Encoding.default_external = 'US-ASCII' # => "US-ASCII"
Encoding.default_external              # => #<Encoding:US-ASCII>

方法 Encoding.default_internal 返回默认的内部 Encoding

Encoding.default_internal # => nil

方法 Encoding.default_internal= 设置默认的内部 Encoding

Encoding.default_internal = 'US-ASCII' # => "US-ASCII"
Encoding.default_internal              # => #<Encoding:US-ASCII>

兼容的编码

方法 Encoding.compatible? 返回两个给定对象是否编码兼容(即,它们是否可以连接);返回连接的字符串的 Encoding,如果不是不兼容,则返回 nil

rus = "\u{442 435 441 442}"
eng = 'text'
Encoding.compatible?(rus, eng) # => #<Encoding:UTF-8>

s0 = "\xa1\xa1".force_encoding('iso-8859-1') # => "\xA1\xA1"
s1 = "\xa1\xa1".force_encoding('euc-jp')     # => "\x{A1A1}"
Encoding.compatible?(s0, s1)                 # => nil

字符串编码

一个 Ruby String 对象有一个 Encoding,它是 Encoding 类的一个实例。可以通过方法 String#encoding 获取编码。

字符串字面量的默认编码是脚本编码;请参阅 脚本编码

's'.encoding # => #<Encoding:UTF-8>

使用方法 String.new 创建的字符串的默认编码是

在这两种情况下,都可以指定任何编码

s = String.new(encoding: 'UTF-8')             # => ""
s.encoding                                    # => #<Encoding:UTF-8>
s = String.new('foo', encoding: 'ASCII-8BIT') # => "foo"
s.encoding                                    # => #<Encoding:ASCII-8BIT>

字符串的编码可以更改

s = "R\xC3\xA9sum\xC3\xA9"     # => "Résumé"
s.encoding                     # => #<Encoding:UTF-8>
s.force_encoding('ISO-8859-1') # => "R\xC3\xA9sum\xC3\xA9"
s.encoding                     # => #<Encoding:ISO-8859-1>

更改指定的编码不会改变字符串的内容;它只会改变内容的解释方式

s                         # => "R\xC3\xA9sum\xC3\xA9"
s.force_encoding('UTF-8') # => "Résumé"

字符串的实际内容也可以更改;请参阅 转码字符串

以下是一些有用的查询方法

s = "abc".force_encoding("UTF-8")         # => "abc"
s.ascii_only?                             # => true
s = "abc\u{6666}".force_encoding("UTF-8") # => "abc晦"
s.ascii_only?                             # => false

s = "\xc2\xa1".force_encoding("UTF-8") # => "¡"
s.valid_encoding?                      # => true
s = "\xc2".force_encoding("UTF-8")     # => "\xC2"
s.valid_encoding?                      # => false

符号和正则表达式编码

存储在 SymbolRegexp 对象中的字符串也有一个编码;可以通过方法 Symbol#encodingRegexp#encoding 获取编码。

然而,这些的默认编码是

文件系统编码

文件系统编码是来自文件系统的字符串的默认 Encoding

Encoding.find("filesystem") # => #<Encoding:UTF-8>

区域设置编码

区域设置编码是来自环境的字符串的默认编码,而不是来自文件系统的字符串

Encoding.find('locale') # => #<Encoding:IBM437>

流编码

某些流对象可以有两个编码;这些对象包括以下实例:

这两个编码是

外部编码

外部编码(是一个 Encoding 对象)指定如何将从流读取的字节解释为字符。

默认的外部编码是

默认的外部编码由方法 Encoding.default_external 返回,可以通过以下方式设置:

您也可以使用方法 Encoding.default_external= 设置默认的外部编码,但这样做可能会导致问题;更改前后创建的字符串可能具有不同的编码。

对于 IO 或 File 对象,外部编码可以通过以下方式设置:

对于 IO、File、ARGF 或 StringIO 对象,可以通过以下方式设置外部编码:

内部编码

内部编码(是一个 Encoding 对象或 nil)指定如何将从流读取的字符转换为内部编码中的字符;这些字符变成一个字符串,其编码设置为内部编码。

默认的内部编码是 nil(不转换)。它由方法 Encoding.default_internal 返回,可以通过以下方式设置:

您也可以使用方法 Encoding.default_internal= 设置默认的内部编码,但这样做可能会导致问题;更改前后创建的字符串可能具有不同的编码。

对于 IO 或 File 对象,内部编码可以通过以下方式设置:

对于 IO、File、ARGF 或 StringIO 对象,可以通过以下方式设置内部编码:

脚本编码

Ruby 脚本具有一个脚本编码,可以通过以下方式检索:

__ENCODING__ # => #<Encoding:UTF-8>

默认的脚本编码是 UTF-8;Ruby 源代码文件可以使用文件第一行(如果第一行是 shebang,则使用第二行)的魔术注释来设置其脚本编码。注释必须包含单词 codingencoding,后跟一个冒号、空格和 Encoding 名称或别名

# encoding: ISO-8859-1
__ENCODING__ #=> #<Encoding:ISO-8859-1>

转码

转码是将字符序列从一种编码更改为另一种编码的过程。

在可能的情况下,字符保持不变,但表示它们的字节可能会更改。

对于无法在目标编码中表示的字符的处理方式,可以通过 @Encoding+Options 指定。

转码字符串

以下每个方法都会对字符串进行转码

转码流

以下每个方法都可以转码流;是否进行转码取决于外部编码和内部编码

此示例将字符串写入文件,并将其编码为 ISO-8859-1,然后将文件读取到新的字符串中,并将其编码为 UTF-8

s = "R\u00E9sum\u00E9"
path = 't.tmp'
ext_enc = 'ISO-8859-1'
int_enc = 'UTF-8'

File.write(path, s, external_encoding: ext_enc)
raw_text = File.binread(path)

transcoded_text = File.read(path, external_encoding: ext_enc, internal_encoding: int_enc)

p raw_text
p transcoded_text

输出

"R\xE9sum\xE9"
"Résumé"

编码选项

Ruby 核心中的许多方法都接受关键字参数作为编码选项。

一些选项指定或使用替换字符串,用于某些转码操作。替换字符串可以是任何可以转换为目标字符串编码的编码。

以下关键字-值对指定编码选项