当被要求从包含未定义的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
"磁盘上的#34;。
现在奇怪来自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转换失败时使用的char。
答案 0 :(得分:2)
当被要求从包含未定义的latin1代码页字符的latin1列读取时,JDBC似乎插入了utf8替换字符
是的,这是CharsetDecoder
instances的默认行为,默认情况下,when the (byte) input is malformed将使用substitution执行此不可映射字节序列的Unicode's replacement character, U+FFFD。
使用此行为的方法的示例是Reader
个,但也是String
构造函数,它们将字节数组作为参数。这就是为什么你永远不应该使用String
存储二进制数据的原因!
唯一能解决错误的方法是获取原始字节输入,创建自己的解码器并在那种情况下将其告诉fail ......