编码

基础

一个字符编码,通常缩写为编码,是以下两者之间的映射:

一些字符集只包含 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]

编码类

编码对象

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,如果 incompatible 则返回 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 类的实例。可以通过方法 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.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 核心中的许多方法接受关键字参数作为编码选项。

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

这些关键字-值对指定编码选项