编码¶ ↑
基础知识¶ ↑
字符编码,通常简称为编码,是指以下两者之间的映射:
-
一个 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]
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
:默认的外部 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,它是 Encoding 类的一个实例。可以通过方法 String#encoding
获取编码。
字符串字面量的默认编码是脚本编码;请参阅 脚本编码。
's'.encoding # => #<Encoding:UTF-8>
使用方法 String.new
创建的字符串的默认编码是
-
对于 String 对象参数,该字符串的编码。
-
对于字符串字面量,脚本编码;请参阅 脚本编码。
在这两种情况下,都可以指定任何编码
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
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 = '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: hash) # => "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
:将每个换行符 ("\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('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"
-