编码
基础知识
一个字符编码,通常简称为编码,是一种映射关系,用于
-
一组8位字节(每个字节的范围是
0..255)。 -
特定字符集中的字符。
有些字符集只包含1字节字符;例如,US-ASCII有256个1字节字符。这个字符串,以US-ASCII编码,有六个字符,存储为六个字节。
s = 'Hello!'.encode(Encoding::US_ASCII) # => "Hello!" s.encoding # => #<Encoding:US-ASCII> s.bytes # => [72, 101, 108, 108, 111, 33]
其他编码可能涉及多字节字符。例如,UTF-8编码了超过一百万个字符,每个字符占用1到4个字节。其中最低值的字符对应于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`。
-
external:默认的外部 `Encoding`。Encoding.find("external") # => #<Encoding:UTF-8>
-
internal:默认的内部 `Encoding`(可能为nil)。Encoding.find("internal") # => nil
-
locale:来自环境的字符串的默认 `Encoding`。Encoding.find("locale") # => #<Encoding:UTF-8> # Linux Encoding.find("locale") # => #<Encoding:IBM437> # Windows
-
filesystem:来自文件系统的字符串的默认 `Encoding`。Encoding.find("filesystem") # => #<Encoding:UTF-8>
方法 Encoding.default_external 返回默认的外部 `Encoding`。
Encoding.default_external # => #<Encoding:UTF-8>
方法 Encoding.default_external= 设置该值。
Encoding.default_external = Encoding::US_ASCII # => #<Encoding:US-ASCII> Encoding.default_external # => #<Encoding:US-ASCII>
方法 Encoding.default_internal 返回默认的内部 `Encoding`。
Encoding.default_internal # => nil
方法 Encoding.default_internal= 设置默认的内部 `Encoding`。
Encoding.default_internal = Encoding::US_ASCII # => #<Encoding: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(Encoding::ISO_8859_1) # => "\xA1\xA1" s1 = "\xa1\xa1".force_encoding(Encoding::EUCJP) # => "\x{A1A1}" Encoding.compatible?(s0, s1) # => nil
字符串编码
Ruby 的 String 对象有一个 `Encoding` 类实例作为其编码。可以通过方法 String#encoding 来获取编码。
字符串字面量的默认编码是脚本编码;请参阅 脚本编码。
's'.encoding # => #<Encoding:UTF-8>
通过方法 String.new 创建的字符串的默认编码是:
-
无参数时,为 `ASCII-8BIT`。
-
当参数是 `String` 对象时,为该字符串的编码。
-
字符串字面量时,为脚本编码;请参阅 脚本编码。
在任何情况下,都可以指定任何编码。
s = String.new(encoding: Encoding::UTF_8) # => "" s.encoding # => #<Encoding:UTF-8> s = String.new('foo', encoding: Encoding::BINARY) # => "foo" s.encoding # => #<Encoding:BINARY (ASCII-8BIT)>
字符串的编码可以被更改。
s = "R\xC3\xA9sum\xC3\xA9" # => "Résumé" s.encoding # => #<Encoding:UTF-8> s.force_encoding(Encoding::ISO_8859_1) # => "R\xC3\xA9sum\xC3\xA9" s.encoding # => #<Encoding:ISO-8859-1>
更改分配的编码不会改变字符串的内容;它只改变内容的解释方式。
s # => "R\xC3\xA9sum\xC3\xA9" s.force_encoding(Encoding::UTF_8) # => "Résumé"
字符串的实际内容也可以被更改;请参阅 转码字符串。
这里有几个有用的查询方法。
s = "abc".force_encoding(Encoding::UTF_8) # => "abc" s.ascii_only? # => true s = "abc\u{6666}".force_encoding(Encoding::UTF_8) # => "abc晦" s.ascii_only? # => false s = "\xc2\xa1".force_encoding(Encoding::UTF_8) # => "¡" s.valid_encoding? # => true s = "\xc2".force_encoding(Encoding::UTF_8) # => "\xC2" s.valid_encoding? # => false
Symbol 和 Regexp 编码
存储在 Symbol 或 Regexp 对象中的字符串也有一个编码;可以通过方法 Symbol#encoding 或 Regexp#encoding 获取编码。
这些的默认编码是:
-
如果所有字符都是 US-ASCII,则为 `US-ASCII`。
-
否则,为脚本编码;请参阅(脚本 脚本编码)。
文件系统编码
文件系统编码是来自文件系统的字符串的默认 `Encoding`。
Encoding.find("filesystem") # => #<Encoding:UTF-8>
区域设置编码
区域设置编码是来自环境的字符串(非文件系统)的默认编码。
Encoding.find('locale') # => #<Encoding:IBM437>
流编码
某些流对象可以有两个编码;这些对象包括:
这两个编码是:
-
外部编码,它标识流的编码。
-
内部编码,它(如果不是
nil)指定用于从流构建的字符串的编码。
外部编码
外部编码是一个 `Encoding` 对象,它指定如何解释从流中读取的字节作为字符。
默认的外部编码是:
-
文本流为 `UTF-8`。
-
二进制流为 `ASCII-8BIT`。
默认的外部编码由方法 Encoding.default_external 返回,并且可以通过以下方式设置:
-
Ruby 命令行选项
--external_encoding或-E。
您也可以使用方法 Encoding.default_external= 设置默认的外部编码,但这可能会导致问题;在更改之前和之后创建的字符串可能有不同的编码。
对于 `IO` 或 `File` 对象,外部编码可以通过以下方式设置:
-
创建对象时,使用打开选项 `external_encoding` 或 `encoding`;请参阅 打开选项。
对于 `IO`、`File`、`ARGF` 或 `StringIO` 对象,外部编码可以通过以下方式设置:
-
方法 `set_encoding` 或(对于 `ARGF` 除外)`set_encoding_by_bom`。
内部编码
内部编码是一个 `Encoding` 对象或 nil,它指定如何将从流中读取的字符转换为内部编码中的字符;这些字符将成为一个编码设置为内部编码的字符串。
默认的内部编码是 nil(不进行转换)。它由方法 Encoding.default_internal 返回,并且可以通过以下方式设置:
-
Ruby 命令行选项
--internal_encoding或-E。
您也可以使用方法 Encoding.default_internal= 设置默认的内部编码,但这可能会导致问题;在更改之前和之后创建的字符串可能有不同的编码。
对于 `IO` 或 `File` 对象,内部编码可以通过以下方式设置:
-
创建对象时,使用打开选项 `internal_encoding` 或 `encoding`;请参阅 打开选项。
对于 `IO`、`File`、`ARGF` 或 `StringIO` 对象,内部编码可以通过以下方式设置:
-
方法 `set_encoding`。
脚本编码
Ruby 脚本有一个脚本编码,可以通过以下方式检索:
__ENCODING__ # => #<Encoding:UTF-8>
默认脚本编码是 `UTF-8`;Ruby 源文件可以在文件的第一行(如果第一行有 shebang,则为第二行)上使用魔术注释来设置其脚本编码。注释必须包含单词 `coding` 或 `encoding`,后跟冒号、空格和 Encoding 名称或别名。
# encoding: ISO-8859-1 __ENCODING__ #=> #<Encoding:ISO-8859-1>
转码
转码是将字符序列从一种编码更改为另一种编码的过程。
尽可能地,字符保持不变,但表示它们的字节可能会改变。
在目标编码中无法表示的字符的处理方式可以通过 @Encoding+Options 指定。
转码字符串
这些方法中的每一种都会转码字符串:
-
String#encode:根据给定的编码和选项将 `self` 转码为新字符串。 -
String#encode!:与String#encode类似,但将 `self` 原地转码。 -
String#scrub:通过将无效字节序列替换为给定的或默认的替换字符串,将 `self` 转码为新字符串。 -
String#scrub!:与String#scrub类似,但将 `self` 原地转码。 -
String#unicode_normalize:根据 Unicode 规范化将 `self` 转码为新字符串。 -
String#unicode_normalize!:与String#unicode_normalize类似,但将 `self` 原地转码。
转码流
这些方法中的每一种都可能转码流;它是否这样做取决于外部和内部编码。
-
IO.foreach:将给定流的每一行屈服于块。 -
IO.new:为给定的整数文件描述符创建并返回一个新的 `IO` 对象。 -
IO.open:创建一个新的 `IO` 对象。 -
IO.pipe:创建一个连接的读取器和写入器 `IO` 对象对。 -
IO.popen:创建一个 `IO` 对象与子进程进行交互。 -
IO.read:返回一个字符串,其中包含给定流的所有或一部分字节。 -
IO.readlines:返回一个字符串数组,这些字符串是给定流的行。 -
IO.write:将给定的字符串写入给定的流。
此示例将一个字符串写入文件,将其编码为 ISO-8859-1,然后将文件读入一个新的字符串,将其编码为 UTF-8。
s = "R\u00E9sum\u00E9" path = 't.tmp' ext_enc = Encoding::ISO_8859_1 int_enc = Encoding::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 核心中的许多方法接受关键字参数作为编码选项。
其中一些选项指定或使用一个替换字符串,用于某些转码操作。替换字符串可以是任何可以转换为目标字符串编码的编码。
这些关键字-值对指定编码选项:
-
对于无效字节序列:
-
:invalid: nil(默认):引发异常。 -
:invalid: :replace:用替换字符串替换每个无效字节序列。
示例
s = "\x80foo\x80" s.encode(Encoding::ISO_8859_3) # Raises Encoding::InvalidByteSequenceError. s.encode(Encoding::ISO_8859_3, invalid: :replace) # => "?foo?"
-
-
对于未定义的字符:
-
:undef: nil(默认):引发异常。 -
:undef: :replace:用替换字符串替换每个未定义的字符。
示例
s = "\x80foo\x80" "\x80".encode(Encoding::UTF_8, Encoding::BINARY) # Raises Encoding::UndefinedConversionError. s.encode(Encoding::UTF_8, Encoding::BINARY, undef: :replace) # => "�foo�"
-
-
替换字符串
-
:replace: nil(默认):将替换字符串设置为默认值:对于 Unicode 编码为"\uFFFD"(“�”),否则为'?'。 -
:replace: some_string:将替换字符串设置为给定的some_string;覆盖:fallback。
示例
s = "\xA5foo\xA5" options = {:undef => :replace, :replace => 'xyzzy'} s.encode(Encoding::UTF_8, Encoding::ISO_8859_3, **options) # => "xyzzyfooxyzzy"
-
-
替换回退
可以指定以下之一:
-
:fallback: nil(默认):无替换回退。 -
:fallback: hash_like_object:将替换回退设置为给定的hash_like_object;替换字符串是hash_like_object[X]。 -
:fallback: method:将替换回退设置为给定的method;替换字符串是method(X)。 -
:fallback: proc:将替换回退设置为给定的proc;替换字符串是proc[X]。
示例
s = "\u3042foo\u3043" hash = {"\u3042" => 'xyzzy'} hash.default = 'XYZZY' s.encode(Encoding::US_ASCII, fallback: hash) # => "xyzzyfooXYZZY" def (fallback = "U+%.4X").escape(x) self % x.unpack("U") end "\u{3042}".encode(Encoding::US_ASCII, fallback: fallback.method(:escape)) # => "U+3042" proc = Proc.new {|x| x == "\u3042" ? 'xyzzy' : 'XYZZY' } s.encode('ASCII', fallback: proc) # => "XYZZYfooXYZZY"
-
-
XML 实体
可以指定以下之一:
-
:xml: nil(默认):不对 XML 实体进行处理。 -
:xml: :text:将源文本视为 XML;用其大写十六进制数字字符引用替换每个未定义的字符,但:-
&被替换为&。 -
<被替换为<。 -
>被替换为>。
-
-
:xml: :attr:将源文本视为 XML 属性值;用其大写十六进制数字字符引用替换每个未定义的字符,但:-
替换字符串
r被双引号括起来("r")。 -
每个嵌入的双引号都被替换为
"。 -
&被替换为&。 -
<被替换为<。 -
>被替换为>。
-
示例
s = 'foo"<&>"bar' + "\u3042" s.encode(Encoding::US_ASCII, xml: :text) # => "foo\"<&>\"barあ" s.encode(Encoding::US_ASCII, xml: :attr) # => "\"foo"<&>"barあ\""
-
-
Newlines
可以指定以下之一:
-
:cr_newline: true:将每个换行符("\n")替换为回车符("\r")。 -
:crlf_newline: true:将每个换行符("\n")替换为回车符/换行符字符串("\r\n")。 -
:universal_newline: true:将每个回车符("\r")和每个回车符/换行符字符串("\r\n")替换为换行符("\n")。
示例
s = "\n \r \r\n" # => "\n \r \r\n" s.encode(Encoding::US_ASCII, cr_newline: true) # => "\r \r \r\r" s.encode(Encoding::US_ASCII, crlf_newline: true) # => "\r\n \r \r\r\n" s.encode(Encoding::US_ASCII, universal_newline: true) # => "\n \n \n"
-