字符编码引起的奇怪结果

3
这是场景-
- DB2数据库位于主机系统(z / OS)上 - Web服务器在USS上运行(z / OS的Unix部分),使用Spring JDBC运行Java代码 - 我们测试的浏览器和客户端程序在Windows 7上运行(默认编码为windows-1252)
我们有一个包含西班牙字符(ú)的字符串,它使用Spring的JDBCTemplate存储在数据库中,因此实际上是JDBC。
- 使用JDBC客户端(Java编写的Squirrel)查询时,它显示为其他内容(ú)。 - 当用示例JDBC程序查询并将结果打印为字符串时,它会显示为其他内容(ú)。 - 当用UTF-8编码的字符串[new String(str,“UTF-8”)]查询并将结果打印出来时,它会正确地显示(ú)。 - 使用-Dfile.encoding = utf-8以UTF-8编码启动JVM时,在上述两种情况下结果都会打印为其他内容(ú)。 - 运行应用程序前端的浏览器也将其显示为ú,尽管HTML的内容头设置为UTF-8。
在这个阶段我有点困惑,有以下问题-
- 如果特定使用UTF-8格式打印字符串可以正常工作,那么为什么使用UTF-8编码启动JVM时无法正常工作? - 实际上可能发生问题的层是数据库还是JVM? - 我应该在应用程序级别解决问题,而不是在列级别解决问题?
有任何指针都将非常有帮助。

你使用的String的两个参数构造函数将字节数组作为第一个参数。在调用new String(str, "UTF-8")之前,你是如何将数据库中的字符串结果转换为字节的? - Daniel Martin
@Daniel:我在从数据库获取的字符串上调用str.getBytes()方法。 - User2709
1
在JDBC中,您必须指定编码,您已经完成了吗? - Andrea Catania
1
也许数据库已经损坏了。可能是UTF-8被写入了数据库,但是数据库编码设置为不同的值:http://stackoverflow.com/questions/4790679/how-to-get-and-change-encoding-schema-for-a-db2-z-os-database-using-dynamic-sql - ceving
2
永远不要调用 str.getBytes()。除非你想让事情以可怕和神秘的方式崩溃,否则永远不要忘记指定字符编码。 - David Conrad
显示剩余2条评论
1个回答

7
你所看到的效果可以解释为数据以UTF-8字节形式写入数据库,但是数据库认为这些字节是其他字符集(ISO-LATIN-1或Windows-1252),当读取数据时,返回的字符串将把这些字节解释为ISO-LATIN-1或相关字符集。
在UTF-8中,字符ú是两个字节0xC3 0xBA。当这些字节被解释为ISO-LATIN-1或win-1252时,你会得到两个字符ú。
在UTF-8中,这两个字符ú是四个字节0xC3 0x83 0xC2 0xBA。当这四个字节被解释为ISO-LATIN-1(或win-1252)时,你会得到四个字符ú。
(Windows-1252和ISO-LATIN-1恰好同意所有相关的字节/字符,因此从证据上无法区分它们之间的区别)
我认为你所遇到的情况是这样的:
1. JDBC客户端查询数据库,并从数据库获取包含两个字符ú的字符串。 2. 当JVM将结果打印到Windows 7控制台框中时,如果没有使用-Dfile.encoding=utf-8启动,则会向控制台框发送用于表示win-1252中字符串所需的字节。如果使用该选项启动JVM,则会向控制台框发送用于表示UTF-8中的字符串所需的字节。 3. 你的Windows 7控制台框设置为Windows-1252,并通过根据Windows-1252解释Java发送的字节来显示Java打印的内容。 4. 当你调用.getBytes()方法(不带参数)时,你使用JVM的默认编码将字符串转换为字节。因此,如果默认JVM编码为UTF-8,则new String(str.getBytes(), "UTF-8")将产生相同的字符串,如果默认编码与UTF-8不同,则会产生实际操作。
这解释了你提供的所有证据:由JDBC检索到的Java字符串包含字符ú,当非UTF-8 JVM尝试将其打印到控制台框中时,它被打印为ú。当UTF-8 JVM尝试将该字符串打印到控制台框时,它打印四个字节0xC3 0x83 0xC2 0xBA,并且控制台将其解释为四个字符ú。当Java Web服务器尝试将该字符串发送回浏览器时,它会这样做,浏览器看到的是Java应用程序从JDBC接收到的内容。
首先需要检查Spring JDBCTemplate是否正确接收了数据并将其正确写入数据库。您能否让Spring在某个地方记录它从浏览器接收到的内容,并确保浏览器发送的是UTF-8编码,Spring知道浏览器正在发送UTF-8编码的内容吗? (在这里您可能需要检查一下记录接收到的字符串及每个字段中字符串的长度,以便确定是否正确解释为UTF-8)。
假设数据已经正确地进入了数据库,并且您不能在数据库端进行更改,希望纯粹从应用程序端进行更改,您可以对从JDBC接收到的每个字符串执行以下操作:
new String(str.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8)

无论JVM的默认编码是什么,这都应该将您的字符串转换回所需的格式。

供参考,使用-Dfile.encoding=utf-8从Windows命令行运行jvm通常需要先更改控制台上的代码页才能正确显示内容。(可以使用命令chcp 65001来完成。只需记住在运行未设置该选项的JVM命令之前使用chcp 1252更改回去即可)


我认为你的观察是完全有道理的。我非常确定数据库不会将其视为UTF-8,因此我将尝试找出数据库理解的编码类型以及浏览器是否发送了正确的数据。感谢您的精彩解释。 - User2709
这是非常好的解释。 - user3410249

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接