Ruby 有关 Array #join 的 encoding

046569 · 2013年11月02日 · 最后由 046569 回复于 2013年11月02日 · 5887 次阅读

起因是这样:昨天有用户反应在网站某处输入中文会报错,查看日志发现Encoding::CompatibilityError (incompatible character encodings: UTF-8 and ASCII-8BIT),很显然编码出了问题。出问题的那行只是将一个 Gem 执行的结果进行了 join,当时的解决办法是 force_encoding. 今天详细尝试了下,发现此 Gem 返回的编码确实在变化,有时候是 UTF-8,有时候是 ASCII-8BIT.而我印象中从 1.9 开始 Ruby 是可以很好处理字符编码的。可是测试结果让我大跌眼镜:

a=['hello']
a.push 'kitty'.force_encoding('ASCII-8BIT')
puts a.join
=> hellokitty

a=['问题']
a.push '错误'.force_encoding('ASCII-8BIT')
puts a.join
=> incompatible character encodings: UTF-8 and ASCII-8BIT (Encoding::CompatibilityError)

至此我认为问题出在#join 上,但 nobu 认为是"Third Party's Issue",理由是"Bugs in gems return ASCII-8BIT strings.". 可是脱离了此 Gem,执行结果仍然不同。为什么不同语言的执行结果不同却不认为是 Bug 呢?谁能详细解释下?

这个能说明啥问题。你期望的是什么情况

irb(main):001:0> a=['hello']
=> ["hello"]
irb(main):002:0> a.push 'kitty'.force_encoding('ASCII-8BIT')
=> ["hello", "kitty"]
irb(main):003:0> puts a.join
hellokitty
=> nil
irb(main):004:0> a=['问题']
=> ["问题"]
irb(main):005:0> a.push '错误'.force_encoding('ASCII-8BIT')
=> ["问题", "\xE9\x94\x99\xE8\xAF\xAF"]
irb(main):006:0> puts a.join
Encoding::CompatibilityError: incompatible character encodings: UTF-8 and ASCII-8BIT
    from (irb):6:in `join'
    from (irb):6
    from /Users/dxiao/.rubies/ruby-2.0.0-p247/bin/irb:12:in `<main>'

#1 楼 @xds2000

a=['问题']
a.push '错误'.force_encoding('ASCII-8BIT')
puts a.join
=> 问题错误(期望得到的结果)

和 join 没有关系,在不兼容的字符集上对字符串操作,都会有问题。

a = "问题"
b = '错误'.force_encoding('ASCII-8BIT')
a << b
# => Encoding::CompatibilityError: incompatible character encodings: UTF-8 and ASCII-8BIT
a.sub(b, 'dudu')
# => Encoding::CompatibilityError: incompatible character encodings: UTF-8 and ASCII-8BIT

这都会出错

但是 US-ASCII(0 ~ 127)是兼容的,所以这样不会出错

a = "我爱Rub"
b = "y".force_encoding("ASCII-8BIT")
a << b
# => "我爱Ruby"

ASCII-8BIT 基本是 binary 了

"我".force_encoding("binary").encoding
# => <Encoding:ASCII-8BIT>

其他语言虽然不报错,但是实际内容也是不对的,比如 Java

String a = "问题";
String b = "错误";
String c = a + new String(b.getBytes("UTF-8"), "ASCII");
System.out.println(c);
// => 问题������

如果确定字符串的实际内容编码是 UTF-8,但是编码信息有时候是 UTF-8,有时候是 ASCII-8BIT,那就都force_encoding("UTF-8") 就好了

a.map { |i| i.force_encoding('UTF-8') }.join 有什么必须使用 ascii 的条件么?

我觉得这是个 bug.

irb(main):027:0> '错误'.force_encoding('ASCII-8BIT').force_encoding('utf-8').encoding
=> #<Encoding:UTF-8>

#6 楼 @xds2000 force_encoding 只是改变编码,并不重新编码字符串,所以不管怎么改,最后改回 UTF-8 都是一样的

"错误".force_encoding("ASCII-8BIT").force_encoding("EUC-JP").force_encoding("GBK").force_encoding("Big5").force_encoding("UTF-8")
# => "错误"

#3 楼 @kenshin54 是的,我也发现了,但不理解这么设计的原因。现在似乎搞清了. #4 楼 @WolfLee 没什么必须的,第三方的 Gem 可以修改,并非不可解决。只是很麻烦,每个都要 encoding,并且从偷懒的角度考虑,可能 Ruby 本身解决这个问题更好些。

不是说在 2.0 里面已经很好的解决了文字编码问题了么

#9 楼 @046569 这种情况应该是对所有用户的输入都进行编码转换吧。 如果用 Rails 的话,本身应该就有强制转换了吧。

#10 楼 @ywjno 2.0 是源代码改用 utf-8 编码,数据编码还是要我们自己来处理的

#11 楼 @cantin 问题并非出自用户输入,而是第三方的 Gem 返回结果时编码在变化,而恰好发生了 3L 说的情况. anyway,搞清楚问题解决了就好。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号