第19章 Encoding类
建立bytes、String encoding标签、source magic comment、Regexp兼容、IO external/internal encoding与严格transcoding的完整边界。
学习目标
- 能解释Ruby的编码与字符串与脚本编码与魔法注释在“第19章 Encoding类”中的责任边界
- 能围绕“怎样从原始字节、编码标签、有效性与转码错误定位乱码根因?”运行正常与故障轨迹并定位首个分岔
- 能用“第19章 Encoding类的输入样本、接收者与方法、关键状态前后值、正常与失败输出、异常或退出状态,以及复位后的再次运行记录。”证明“来源编码有可信元数据,转换严格,任何替换性数据损失都有记录。”
来源、版次与运行边界
“第19章 Encoding类”以作者维护的第 5 版支持页核定 2016 年 3 月 12 日首刷、两位作者、松本行弘监修以及四部分 23 章目录;逐章程序清单、练习答案和勘误只作为公开支持材料,不被冒充为原书全文。
对“第19章 Encoding类”而言,中文解释、示例、交互、练习和答案均为独立教学重写;站内中文章名是与官方 23 章顺序对应的课程映射,不宣称是日文小节的逐字翻译,也不从公开程序清单复制整段实现。
“第19章 Encoding类”固定在 Ruby 2.3 语境;Ruby 2.3.0 官方文档与稳定版发布说明用于核对当时可用的语言和标准库行为。现代 Ruby 的差异只能另列迁移说明,不能静默改变本页示例的版本结论。
围绕“怎样从原始字节、编码标签、有效性与转码错误定位乱码根因?”,本页验收“第19章 Encoding类的输入样本、接收者与方法、关键状态前后值、正常与失败输出、异常或退出状态,以及复位后的再次运行记录。”。先预测正常轨迹,再只注入“对未知字节直接 force_encoding 为 UTF-8 并把标签变化当作转码成功”;若无法定位第一处状态分岔,就拒绝当前解释。
从“乱码就force_encoding UTF-8”开始纠正
force_encoding只改变String的Encoding标签,不改变任何byte;标签错了只会把问题延后。要求source metadata来自protocol、BOM或可信配置,而不是肉眼猜。
先预测:Windows-31J bytes直接force UTF-8后valid_encoding?可能false;即使偶然true,字符也可能错。正确流程先标记实际source encoding,验证,再encode("UTF-8")。
Ruby的编码与字符串
String包含byte sequence与Encoding object。bytesize是bytes,length按当前encoding解释characters;ASCII-only String在许多编码间兼容,但不能据此推断source。是基础。
text = "Ruby中文"
p encoding: text.encoding
p valid: text.valid_encoding?
p bytes: text.bytesize
p characters: text.lengthEncoding不是语言检测;同一bytes在多个single-byte encodings都可能valid。Unicode normalization NFC/NFD也不是encoding conversion:它改变codepoint composition,搜索/identity是否normalize是独立domain policy。
脚本编码与魔法注释
Ruby parser必须知道source file encoding,modern Ruby默认UTF-8;magic comment如# encoding: Windows-31J在文件开头声明。不能与IO encoding混淆。
团队应统一UTF-8 source并由editor/CI验证。Magic comment不是把文件转码;声明必须与实际bytes一致。Shebang与magic comment位置有规则,目标Ruby 2.3需验证。
__ENCODING__可观察当前source encoding。不同source files各自解析literals,运行时连接时仍检查String encoding compatibility。
Encoding类
Encoding对象描述Ruby支持的编码,Encoding.find按name/alias查找,Encoding.list列出。避免用户任意name触发不可预测转换。
source_encoding = Encoding.find("Windows-31J")
target_encoding = Encoding::UTF_8
raw = File.binread(path)
tagged = raw.force_encoding(source_encoding)
raise EncodingError, "invalid source bytes" unless tagged.valid_encoding?
text = tagged.encode(target_encoding)Default external/internal encodings受process locale/arguments影响,不应成为可复现程序的隐式配置;I/O boundary显式指定。Encoding objects immutable-like,可比较identity/name,但serialization使用canonical name。
正则表达式与编码
Regexp pattern与target String必须encoding-compatible。不同于是否pattern grammar正确。
pattern = /[[:alpha:]]+/
match = pattern.match("Ruby日本語")
p match && match[0]Character class、case-insensitive与dot对Unicode的支持受Ruby/Onigmo版本和options影响。Binary String使用byte-oriented pattern,不应混入text pattern。出现CompatibilityError先追两边encoding/validity,不要统一force UTF-8。
IO类与编码:外部编码与内部编码
External encoding描述stream bytes,internal encoding是读取后自动transcode目标;"r:Windows-31J:UTF-8"可表达二者。把转换放在adapter。
File.open(path, "r:Windows-31J:UTF-8") do |file|
file.each_line do |line|
raise "unexpected internal encoding" unless line.encoding == Encoding::UTF_8
end
endMode string支持和encoding names按平台/Ruby版本验证。Binary mode禁止自动transcode。Network protocol若headers声明charset,先验证allowlist再设置source;缺失charset使用协议default,不随locale猜。
编码的设定与作用
IO#set_encoding可配置external/internal和invalid/undef options。Strict default让坏data尽早失败;:replace需给replacement、计数并保留raw locator。是业务决策。
Replacement可能把两个不同identifiers变成同一String,安全/主键数据禁止silent replace。Display-only feed可替换但打标;批处理把坏record隔离并继续,最终status仍反映partial failure。
编码转换与错误证据
encode可能抛InvalidByteSequenceError(source bytes非法)或UndefinedConversionError(target无对应字符)。帮助定位而不泄漏数据。
def decode_strict(raw, source, target = Encoding::UTF_8)
tagged = raw.dup.force_encoding(source)
raise Encoding::InvalidByteSequenceError unless tagged.valid_encoding?
tagged.encode(target)
end手工raise内建异常构造细节不理想,production定义domain DecodeError并保留cause/context。Round-trip decoded.encode(source)只在encoding可逆且normalization/replace未发生时可作为证据;不是所有转换lossless。
正式节点与章专属证据
- ↡在“第19章 Encoding类”中,Ruby的编码与字符串必须连接输入、状态变化与可复核结果。 :第 1 个正式节点要能回到“来源编码有可信元数据,转换严格,任何替换性数据损失都有记录。”,并说明故障发生前后的第一处差异。
- ↡在“第19章 Encoding类”中,脚本编码与魔法注释必须连接输入、状态变化与可复核结果。 :第 2 个正式节点要能回到“来源编码有可信元数据,转换严格,任何替换性数据损失都有记录。”,并说明故障发生前后的第一处差异。
- ↡在“第19章 Encoding类”中,Encoding类必须连接输入、状态变化与可复核结果。 :第 3 个正式节点要能回到“来源编码有可信元数据,转换严格,任何替换性数据损失都有记录。”,并说明故障发生前后的第一处差异。
- ↡在“第19章 Encoding类”中,正则表达式与编码必须连接输入、状态变化与可复核结果。 :第 4 个正式节点要能回到“来源编码有可信元数据,转换严格,任何替换性数据损失都有记录。”,并说明故障发生前后的第一处差异。
- ↡在“第19章 Encoding类”中,IO类与编码必须连接输入、状态变化与可复核结果。 :第 5 个正式节点要能回到“来源编码有可信元数据,转换严格,任何替换性数据损失都有记录。”,并说明故障发生前后的第一处差异。
- ↡在“第19章 Encoding类”中,外部编码与内部编码必须连接输入、状态变化与可复核结果。 :第 6 个正式节点要能回到“来源编码有可信元数据,转换严格,任何替换性数据损失都有记录。”,并说明故障发生前后的第一处差异。
- ↡在“第19章 Encoding类”中,编码的设定与作用必须连接输入、状态变化与可复核结果。 :第 7 个正式节点要能回到“来源编码有可信元数据,转换严格,任何替换性数据损失都有记录。”,并说明故障发生前后的第一处差异。
对象与状态模型
从输入、接收者到可观察证据
怎样从原始字节、编码标签、有效性与转码错误定位乱码根因?
输入与接收者
固定Ruby的编码与字符串所需的原始值、Ruby 版本和调用入口。
状态变化
在执行前记录接收者身份,并声明脚本编码与魔法注释的允许状态。
观察证据
保存第19章 Encoding类的初值、参数、编码或资源位置。
正式节点:Ruby的编码与字符串、脚本编码与魔法注释、Encoding类、正则表达式与编码、IO类与编码、外部编码与内部编码、编码的设定与作用
控制与消息轨迹
在相同初值下定位首个分岔
- 01固定Ruby的编码与字符串的输入和接收者
- 02执行脚本编码与魔法注释并记录状态
- 03观察Encoding类的返回或副作用
- 04用编码的设定与作用核对不变量并复位
运行不变量:来源编码有可信元数据,转换严格,任何替换性数据损失都有记录。
边界故障探针
一次只破坏一个前提
本章回顾:Encoding是边界协议,不是乱码补丁
- String由bytes和Encoding标签组成;validity只证明当前解释合法,source仍需可信metadata。
- Source magic comment只影响parser/literals,不影响runtime File/network input。
- Regexp与String需encoding-compatible;binary/text分别使用byte/character grammar。
- IO external/internal encoding把decode/transcode放入adapter,core统一使用UTF-8等内部representation。
- Invalid/undefined/compatibility分开处理,replace是可审计data loss,关键字段默认strict/quarantine。
练习与答案
练习
- 问题 1:建立正常轨迹。 回答“怎样从原始字节、编码标签、有效性与转码错误定位乱码根因?”,并写出四步执行记录。
- 问题 2:注入单一故障。 只制造“对未知字节直接 force_encoding 为 UTF-8 并把标签变化当作转码成功”,应从哪里开始定位?
- 问题 3:覆盖正式节点。 用一个证据包串联Ruby的编码与字符串、脚本编码与魔法注释、Encoding类、正则表达式与编码、IO类与编码、外部编码与内部编码、编码的设定与作用,说明为什么结论可由另一位读者独立复核。
名词解释
名词解释
本章出现的专业名词,用大白话再讲一遍。
- Ruby的编码与字符串
“第19章 Encoding类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 脚本编码与魔法注释
“第19章 Encoding类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- Encoding类
“第19章 Encoding类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 正则表达式与编码
“第19章 Encoding类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- IO类与编码
“第19章 Encoding类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 外部编码与内部编码
“第19章 Encoding类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 编码的设定与作用
“第19章 Encoding类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。