如何诊断字符编码问题

How to diagnose character encoding issue

提问人:Joe 提问时间:10/31/2023 最后编辑:Joe 更新时间:11/1/2023 访问量:74

问:

我无法识别似乎与 Postgres 数据库中存在的奇怪字符相关的问题。我正在使用 Java 从 Postgres 中提取数据并将其加载到 BigQuery 中。偶尔,我注意到一些值似乎在此过程中无缘无故地发生了变化。经过仔细检查,我发现在所有情况下,问题似乎都是由我认为不正常的字符引起的。

Postgres 数据库编码为 UTF-8。Java 编码也是 UTF-8。

以下是我所看到的示例:

我有一个包含此值的文本字段:。SÅ‚awomir

如果我运行这个 SQL:
select length('SÅ‚awomir')

我得到的值为 9,看起来是正确的。但是,如果我将该字符串导出到文本文件并在十六进制编辑器中查看它(在我的情况下,使用 HEX 编辑器扩展的 Visual Studio Code),则该字符串的长度似乎是 11,而不是 9。经过仔细检查,第二个和第三个字符由 2 个十六进制值表示,而不是像其他字符那样只有一个。第二个和第三个字符由以下 4 个十六进制值表示:

C3 85 C2 82

下面是显示这些字符的十六进制编辑器的屏幕截图。正如你所看到的,字符串似乎有 11 个字符,而不是 9 个字符:enter image description here

请帮助我了解这些角色是什么以及我能做些什么。它们是否具有有效的 UTF-8 字符?如果是这样,为什么要通过Java程序转换它们,我怎样才能阻止这种情况发生?

2023-10-31 更新:感谢 @Laurenz Albe 的回复。它作为对发生的事情的解释(以及如何在未来防止它)很有意义,但我不确定它是否完全解决了我的问题,因为我没有能力控制将数据插入数据库的上游进程。

我还有一些相关的细节:

我们使用 Google Datastream 从 Postgres 中提取数据并将其移动到 BigQuery。当数据到达 BigQuery 时,它看起来与在 Postgres 中完全相同(这是我想要的)。当我使用 Java (JDBC) 将该值从 BigQuery 中提取出来,然后将其插入到另一个 BigQuery 表中时,实际上会出现此问题。

我不会将此作为单个插入语句来执行此操作,例如“插入到...选择从..”。在这种情况下,数据永远不会离开 BigQuery。我所做的是首先获取数据并将结果分配给 Java 变量。然后,在第二步中,我将该值插入回另一个 BigQuery 表中。当我这样做时,目标表中的数据会略有变化,所以我试图弄清楚如何防止这种情况发生。

下面是原始值和移动到另一个表后的值的示例:

enter image description here

这是我的十六进制查看器中同一文件的屏幕截图:

enter image description here

如您所见,该值已发生一些更改 - 新值似乎是c3 85 e2 80 9a

所以我的问题真的是我怎样才能保留原始价值?在将数据提取到 Java 中,然后将其放回 BigQuery 的过程中,似乎发生了一些事情。我的 Java 环境配置为使用 UTF8 编码,所以我对如何保留原始值有点困惑。

PostgreSQL UTF-8 字符编码

评论

0赞 Adrian Klaver 10/31/2023
1) 我得到 8 分和 72 分,这是有道理的。换句话说,它是一个多字节字符。2) 在您的数据库实例中,返回什么?将作为文本更新添加到您的问题文本中。3)实际上它看起来像由四个十六进制字符表示。lengthbit_length()select bit_length('Å'); 16show lc_ctype'Å
0赞 Adrian Klaver 10/31/2023
那应该是.show lc_ctype;
3赞 Codo 10/31/2023
C3 85是“Å”的 UTF-8 字节。 是 Break Allowed Here 控件字符的 UTF-8 字节。它是一个不可见的字符,允许不带连字符的换行符(无论它来自何处)。请注意,它不返回 UTF-8 字节数,而是返回 Unicode 代码点数。该单词有 8 个可见字母和 1 个不可见控制字符,结果是 9。C2 82length()
0赞 Mark Tolonen 10/31/2023
可能是双 UTF-8 编码。使用 Python 时,可以得到 .如果数据以 UTF-8 格式发送,但解码为 ISO-8859-1(又名 Latin-1),然后在发送时再次以 UTF-8 编码,则可能会发生这种情况。这两个词都不能翻译成谷歌翻译中的任何内容。也许后者是波兰的专有名称?不容易修复,因为坏数据已经进入了数据库。修复上游问题。bytes.fromhex('53c385c28261776f6d6972').decode('utf8').encode('latin1').decode('utf8')'Sławomir'

答:

4赞 Laurenz Albe 10/31/2023 #1

这是一个“双重编码”的情况。

原始字符串必须是“Słavomir”。第二个字母 (ł) 用 UTF-8 的两个字节编码。C582

现在,当 UTF-8 编码的字符串入到数据库中时,有人将 PostgreSQL 客户端编码设置为单字节编码,可能是 LATIN-1。因此,PostgreSQL 将这两个字节解释为单独的字符:is “Å”,并且是一个不可打印的字符,一个名称为“break allowed here”的控制字符。C582

PostgreSQL 将这两个字符转换为编码 UTF-8 的服务器,从而将它们转换为您观察到的四个字节。每个字符由 UTF-8 中的两个字节表示。

在转换为 BigQuery 期间,“此处允许的中断”字符会转换为“大括号”(')。必须发生的事情是这样的:

  • 数据使用客户端编码 LATIN-1 提取,因此结果为C582

  • 客户端入到 BigQuery 中,客户端编码为 WINDOWS-1252,其中表示大括号82

  • BigQuery 服务器将大括号转换为 ZTF-8,最终得到E2809A


总结一下:

  • PostgreSQL 数据库中的原始数据已损坏,因为已设置为导入数据时,而不是在导入数据时client_encodingLATIN1UTF8

  • 在传输到 BigQuery 期间,由于 BigQuery 客户端编码设置为 WINDOWS-1252,因此数据被进一步破坏

评论

0赞 Joe 11/1/2023
谢谢。我在描述中添加了更新,以便根据您的反馈更好地描述问题(这很有帮助)。任何其他见解都值得赞赏。
1赞 Laurenz Albe 11/1/2023
伙计,你一团糟。
0赞 tripleee 11/1/2023
更新后的问题仍然遵循相同的模式。U+0082 是一个控制字符,但呈现为较低的单引号,例如在 Windows 代码页 1252 中;一个字符,其常规 Unicode 表示形式为 U+201A,这是您在更新问题中的“目标”中的内容。