Ruby 2/1.9.3中移除无效UTF-8字节保留有效字符的方法
移除UTF-8字符串中的无效字节(兼容Ruby 2.6.10/1.9.3)
此前发布过类似问题,但本次问题不重复。当前开发的软件必须兼容Ruby 2.6.10或1.9.3版本。
问题复现
存在一个编码标记为Encoding::UTF_8的字符串,但包含UTF-8无效字节(十六进制F6):
b = "L\xF6sé侍"
查看字节序列:
p b.bytes.to_a
输出:
[76, 246, 115, 195, 169, 228, 190, 141]
目标
移除所有UTF-8无效字节,最终得到字符串 "Lsé侍"。
尝试过的方法及问题
- 直接调用
encode方法,结果与原字符串完全一致:
c1 = b.encode('UTF-8', invalid: :replace, replace: '')
- 强制编码为
ASCII_UTF8后再转码,会误删有效多字节字符(如é、侍):
b.force_encoding(Encoding::ASCII_UTF8) c2 = b.encode('UTF-8', invalid: :replace, replace: '')
- 硬编码无效字节列表删除,实现不够优雅。
更新说明
此前测试基于irb,但irb与非交互式Ruby表现存在差异。实际场景中,这类含无效字节的字符串通常来自文件读取(JRuby编译阶段会拒绝直接赋值此类字符串)。
复现脚本(需读取含错误文本的文件):
p RUBY_VERSION str = File.read("./errf.txt") p str.bytes.to_a str2 = str.encode('UTF-8', invalid: :replace, replace: '') p str2.bytes.to_a
解决方案
通过正则匹配所有有效的UTF-8字节序列,保留有效部分并重新编码,兼容Ruby 2.6.10和1.9.3:
def clean_invalid_utf8(str) # 强制转为二进制编码,避免原有编码检查干扰 bin_str = str.force_encoding(Encoding::ASCII_8BIT) # 匹配所有合法UTF-8序列:单字节、双字节、三字节、四字节 valid_segments = bin_str.scan( /[\x00-\x7F]|[\xC2-\xDF][\x80-\xBF]|[\xE0-\xEF][\x80-\xBF]{2}|[\xF0-\xF4][\x80-\xBF]{3}/ ) # 拼接有效片段并恢复UTF-8编码 valid_segments.join.force_encoding(Encoding::UTF_8) end # 测试示例 b = "L\xF6sé侍".force_encoding(Encoding::UTF_8) puts clean_invalid_utf8(b) # 输出:Lsé侍 # 文件读取场景使用 str = File.read("./errf.txt", mode: "rb").force_encoding(Encoding::UTF_8) clean_str = clean_invalid_utf8(str) p clean_str.bytes.to_a # 输出:[76, 115, 195, 169, 228, 190, 141]
正则说明
[\x00-\x7F]:匹配单字节UTF-8(ASCII字符)[\xC2-\xDF][\x80-\xBF]:匹配双字节UTF-8序列(覆盖大部分西欧扩展字符)[\xE0-\xEF][\x80-\xBF]{2}:匹配三字节UTF-8序列(覆盖中日韩等多字节字符)[\xF0-\xF4][\x80-\xBF]{3}:匹配四字节UTF-8序列(覆盖emoji等扩展字符)
内容的提问来源于stack exchange,提问作者user1934428
相关产品推荐
相关产品推荐

