JDBC 中古怪的 latin1 到 UTF8 的转换
Whacky latin1 to UTF8 conversion in JDBC
当要求从包含未定义的 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
"on disk".
现在怪事来自JDBC
,以这个groovy为例:
@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。未十六进制的列也转换为 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 转换失败时使用的字符。
JDBC seems to insert a utf8 replacement character when asked to read from a latin1 column containing undefined latin1 codepage characters
是的,这是 CharsetDecoder
instances which by default, when the (byte) input is malformed, will perform a substitution of this unmappable byte sequence with Unicode's replacement character, U+FFFD 的默认行为。
使用此行为的方法示例都是 Reader
,还有 String
以字节数组作为参数的构造函数。这就是为什么你不应该使用 String
来存储二进制数据的原因!
造成该错误的唯一解决方案是获取原始字节输入,创建您自己的解码器并在那种情况下将其告诉 fail...
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
"on disk".
现在怪事来自JDBC
,以这个groovy为例:
@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。未十六进制的列也转换为 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 转换失败时使用的字符。
JDBC seems to insert a utf8 replacement character when asked to read from a latin1 column containing undefined latin1 codepage characters
是的,这是 CharsetDecoder
instances which by default, when the (byte) input is malformed, will perform a substitution of this unmappable byte sequence with Unicode's replacement character, U+FFFD 的默认行为。
使用此行为的方法示例都是 Reader
,还有 String
以字节数组作为参数的构造函数。这就是为什么你不应该使用 String
来存储二进制数据的原因!
造成该错误的唯一解决方案是获取原始字节输入,创建您自己的解码器并在那种情况下将其告诉 fail...