编码

基础知识

字符编码,通常缩写为编码,是以下内容之间的映射

一些字符集仅包含 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,或在不兼容时返回 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.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 核心中的许多方法接受关键字参数作为编码选项。

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

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