编码¶ ↑
基础知识¶ ↑
字符编码,通常缩写为编码,是以下内容之间的映射
-
8 位字节序列(每个字节在
0..255
范围内)。 -
特定字符集中的字符。
一些字符集仅包含 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
-
external
:默认外部 EncodingEncoding.find("external") # => #<Encoding:UTF-8>
-
internal
:默认内部 Encoding(可能是nil
)Encoding.find("internal") # => nil
-
locale
:环境中字符串的默认 EncodingEncoding.find("locale") # => #<Encoding:UTF-8> # Linux Encoding.find("locale") # => #<Encoding:IBM437> # Windows
-
filesystem
:文件系统中字符串的默认 EncodingEncoding.find("filesystem") # => #<Encoding:UTF-8>
方法 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
符号和正则表达式编码¶ ↑
存储在 Symbol
或 Regexp
对象中的字符串也具有编码;可以通过方法 Symbol#encoding
或 Regexp#encoding
检索编码。
但是,它们的默认编码是
-
如果所有字符都是 US-ASCII,则为 US-ASCII。
-
否则为脚本编码;请参阅
Encoding)[rdoc-ref:encodings.rdoc
中的 [脚本脚本编码]。
文件系统编码¶ ↑
文件系统编码是从文件系统获取的字符串的默认编码
Encoding.find("filesystem") # => #<Encoding:UTF-8>
区域设置编码¶ ↑
区域设置编码是从环境(而不是文件系统)获取的字符串的默认编码
Encoding.find('locale') # => #<Encoding:IBM437>
流编码¶ ↑
某些流对象可以具有两种编码;这些对象包括
两种编码是
-
一个外部编码,它标识流的编码。
-
一个内部编码,它(如果不是
nil
)指定要用于从流构建的字符串的编码。
外部编码¶ ↑
外部编码是一个编码对象,它指定从流中读取的字节如何解释为字符。
默认外部编码是
-
文本流的 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 = '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 核心中的许多方法接受关键字参数作为编码选项。
某些选项指定或使用替换字符串,用于某些转码操作。替换字符串可以采用任何编码,该编码可以转换为目标字符串的编码。
这些关键字-值对指定编码选项
-
对于无效字节序列
-
:invalid: nil
(默认):引发异常。 -
:invalid: :replace
:用替换字符串替换每个无效字节序列。
示例
s = "\x80foo\x80" s.encode('ISO-8859-3') # Raises Encoding::InvalidByteSequenceError. s.encode('ISO-8859-3', invalid: :replace) # => "?foo?"
-
-
对于未定义字符
-
:undef: nil
(默认):引发异常。 -
:undef: :replace
:用替换字符串替换每个未定义字符。
示例
s = "\x80foo\x80" "\x80".encode('UTF-8', 'ASCII-8BIT') # Raises Encoding::UndefinedConversionError. s.encode('UTF-8', 'ASCII-8BIT', undef: :replace) # => "�foo�"
-
-
替换字符串
-
:replace: nil
(默认):Set
替换字符串为默认值:Unicode 编码为"\uFFFD"
(“�”),否则为'?'
。 -
:replace: some_string
:Set
替换字符串为给定的some_string
;覆盖:fallback
。
示例
s = "\xA5foo\xA5" options = {:undef => :replace, :replace => 'xyzzy'} s.encode('UTF-8', 'ISO-8859-3', **options) # => "xyzzyfooxyzzy"
-
-
替换回退
可以指定以下选项之一
-
:fallback: nil
(默认):无替换回退。 -
:fallback: hash_like_object
:Set
替换回退为给定的hash_like_object
;替换字符串为hash_like_object[X]
。 -
:fallback: method
:Set
替换回退为给定的method
;替换字符串为method(X)
。 -
:fallback: proc
:Set
替换回退到给定的proc
;替换字符串为proc[X]
。
示例
s = "\u3042foo\u3043" hash = {"\u3042" => 'xyzzy'} hash.default = 'XYZZY' s.encode('ASCII', fallback: h) # => "xyzzyfooXYZZY" def (fallback = "U+%.4X").escape(x) self % x.unpack("U") end "\u{3042}".encode("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('ASCII', xml: :text) # => "foo\"<&>\"barあ" s.encode('ASCII', xml: :attr) # => "\"foo"<&>"barあ\""
-
-
换行符
可以指定以下选项之一
-
:cr_newline: true
:用回车符("\r"
)替换每个换行符("\n"
)。 -
:crlf_newline: true
:用回车/换行字符串("\r\n"
)替换每个换行符("\n"
)。 -
:universal_newline: true
:用换行符("\n"
)替换每个回车符("\r"
)和每个回车/换行字符串("\r\n"
)。
示例
s = "\n \r \r\n" # => "\n \r \r\n" s.encode('ASCII', cr_newline: true) # => "\r \r \r\r" s.encode('ASCII', crlf_newline: true) # => "\r\n \r \r\r\n" s.encode('ASCII', universal_newline: true) # => "\n \n \n"
-