【问题标题】:Weird results with character encodings字符编码的奇怪结果
【发布时间】:2016-02-02 12:19:34
【问题描述】:

这是场景-

  • DB2 数据库位于大型机系统 (z/OS) 上
  • Web 服务器在 USS(z/OS 的 Unix 部分)上运行,使用 Spring JDBC 运行 Java 代码
  • 我们测试的浏览器和客户端程序在Windows 7上运行(默认编码是windows-1252)

我们有一个包含西班牙字符 (ú) 的字符串,它使用 Spring 的 JDBCTemplate 存储在数据库中,因此本质上是 JDBC。

  • 当使用 JDBC 客户端(Squirrel,用 Java 编写)进行查询时,它会显示为其他内容 (ú)。
  • 当使用示例 JDBC 程序进行查询并将结果打印为字符串时,它会显示为其他内容 (ú)。
  • 当使用示例 JDBC 程序查询并将结果打印为 UTF-8 编码字符串 [new String(str, "UTF-8")] 时,它会正确显示 (ú)。
  • 使用 -Dfile.encoding=utf-8 以 UTF-8 编码启动 JVM 时,在上述两种情况下,结果都会打印为其他内容 (ú)。
  • 运行应用程序前端的浏览器也显示为 ú,但 HTML 的内容标头设置为 UTF-8。

现阶段我有点迷茫,有这些疑问-

  • 如果打印 UTF-8 格式的字符串特别有效,为什么 JVM 以 UTF-8 编码启动时不起作用。
  • 问题可能实际发生在哪一层,是数据库还是 JVM?

我应该怎么做才能在应用程序级别而不是列级别解决?

任何指针都会有所帮助。

【问题讨论】:

  • 您使用的 String 的双参数构造函数将字节数组作为第一个参数。在调用 new String(str, "UTF-8") 之前如何将数据库中的字符串结果转换为字节?
  • @Daniel:我在从数据库中获取的字符串上调用 str.getBytes()。
  • 在 JDBC 中你必须指定编码你已经这样做了吗?
  • 可能数据库已经坏了。可能是 UTF-8 已写入数据库,但数据库编码设置为不同的值:stackoverflow.com/questions/4790679/…
  • 永远不要打电话给str.getBytes()。永远不要不指定字符编码,除非您希望事情以可怕和神秘的方式中断。

标签: java encoding utf-8


【解决方案1】:

您看到的效果都可以通过以下假设来解释:数据以 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 开头,它会将表示字符串所需的字节发送到控制台框赢1252。如果 JVM 以该选项启动,它会将表示 UTF-8 字符串所需的字节发送到控制台框。

  3. 您的 windows 7 控制台框设置为 windows-1252,并通过解释 java 根据 windows-1252 发送的字节来显示 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,因此我将尝试弄清楚数据库将其理解为哪种编码类型以及浏览器是否发送了正确的数据。感谢您的精彩解释。
  • 这是很好的解释。
猜你喜欢
  • 2014-10-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-03
相关资源
最近更新 更多