【问题标题】:Whacky latin1 to UTF8 conversion in JDBCJDBC 中古怪的 latin1 到 UTF8 转换
【发布时间】:2015-01-09 18:04:44
【问题描述】:

当要求从包含未定义的 latin1 代码页字符的 latin1 列中读取时,JDBC 似乎插入了一个 utf8 替换字符。这种行为与 MySQL 内部函数的行为不同。

字符编码是我上周陷入的一个兔子洞,为了不产生 100 个明显的答案,我将通过几个代码示例来演示发生了什么。

Mysql:

[admin@yarekt ~]$ echo 'SELECT CONVERT(UNHEX("81") using latin1);' | mysql --init-command='set names latin1' | tail -1| hexdump -C
00000000  81 0a                                             |..|
00000002
[admin@yarekt ~]$ echo 'SELECT CONVERT(UNHEX("81") using latin1);' | mysql --init-command='set names utf8' | tail -1| hexdump -C
00000000  c2 81 0a                                          |...|
00000003

这很明显,并且完全按预期工作。 0x81 是未定义的 latin1 代码点。它在 UTF8 中表示为 \u0081 或在十六进制“磁盘上”表示为 c2 81。

现在怪异来自JDBC,举个时髦的例子:

@GrabConfig(systemClassLoader=true)
@Grab(group='mysql', module='mysql-connector-java', version='5.1.6')
import groovy.sql.Sql
sql = Sql.newInstance( 'jdbc:mysql://localhost/test', 'root', '', 'com.mysql.jdbc.Driver' )
sql.eachRow( 'SELECT CONVERT(UNHEX("C281") using utf8) as a;' ) { println "$it.a --" }

这个查询的输出是两个字节,如预期的那样c2 81。很容易理解这里发生的事情。 Mysql 连接默认为 UTF8。 unhexxed 列也被强制转换为 UTF8(不编码,由于源是二进制,CONVERT() 之后的数据仍然是c2 81)。

现在考虑这种情况。连接仍然是 UTF8,这是 JDBC 的默认设置。我们将 0x81 字节转换为 latin1,因此希望 mysql 将其转换为 c2 81,就像在上面的 bash 示例中所做的那样。

@GrabConfig(systemClassLoader=true)
@Grab(group='mysql', module='mysql-connector-java', version='5.1.6')
import groovy.sql.Sql
sql = Sql.newInstance( 'jdbc:mysql://localhost/test', 'root', '', 'com.mysql.jdbc.Driver' )
sql.eachRow( 'SELECT CONVERT(UNHEX("81") using latin1) as a;' ) { println "$it.a --" }

使用groovy latin1_test.groovy | hexdump -C 运行会产生这样的结果:

00000000  ef bf bd 0a                                       |....|
00000004

ef bf bd 是一个 utf8 替换字符。 utf8 转换失败时使用的字符。

【问题讨论】:

    标签: java mysql jdbc groovy utf-8


    【解决方案1】:

    当要求从包含未定义的 latin1 代码页字符的 latin1 列中读取时,JDBC 似乎插入了一个 utf8 替换字符

    是的,这是CharsetDecoder instances 的默认行为,默认情况下when the (byte) input is malformed 将使用Unicode's replacement character, U+FFFD 执行此不可映射字节序列的substitution。

    使用此行为的方法示例都是 Readers 以及将字节数组作为参数的 String 构造函数。这就是为什么你永远不应该使用String 来存储二进制数据的原因!

    产生错误的唯一解决方案是获取原始字节输入,创建您自己的解码器并在这种情况下将其告诉fail...

    【讨论】:

    • 为什么 Java 做字符集转换,而 mysql 可以用 set names utf8 做呢?。不幸的是,我无法访问此处的 Java 代码(groovy 仅用于测试),因为问题来自 Sqoop,一个 mysql 到 hadoop 导入应用程序
    • 嗯,你要求打印内容,所以它必须做解码,对吧?
    • 能否以二进制形式打印数据(或使其不被修改/转换的任何内容)。只是,c2 81 是一个有效的 UTF8 代码点,mysql 将任何 latin1 代码点(00 到 FF)转换为 U+0000 到 U+00FF,这可能是错误的,但至少它是可逆的
    • 对不起,我真的帮不上忙;我已经放弃 MySQL 很长时间了,转而支持 PostgreSQL...
    猜你喜欢
    • 1970-01-01
    • 2016-12-03
    • 2012-03-13
    • 2013-01-04
    • 2015-04-09
    • 2010-11-29
    • 2011-05-03
    • 2010-12-04
    • 1970-01-01
    相关资源
    最近更新 更多