提问人:Joe 提问时间:10/31/2023 最后编辑:Joe 更新时间:11/1/2023 访问量:74
如何诊断字符编码问题
How to diagnose character encoding issue
问:
我无法识别似乎与 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 个字符:
请帮助我了解这些角色是什么以及我能做些什么。它们是否具有有效的 UTF-8 字符?如果是这样,为什么要通过Java程序转换它们,我怎样才能阻止这种情况发生?
2023-10-31 更新:感谢 @Laurenz Albe 的回复。它作为对发生的事情的解释(以及如何在未来防止它)很有意义,但我不确定它是否完全解决了我的问题,因为我没有能力控制将数据插入数据库的上游进程。
我还有一些相关的细节:
我们使用 Google Datastream 从 Postgres 中提取数据并将其移动到 BigQuery。当数据到达 BigQuery 时,它看起来与在 Postgres 中完全相同(这是我想要的)。当我使用 Java (JDBC) 将该值从 BigQuery 中提取出来,然后将其插入到另一个 BigQuery 表中时,实际上会出现此问题。
我不会将此作为单个插入语句来执行此操作,例如“插入到...选择从..”。在这种情况下,数据永远不会离开 BigQuery。我所做的是首先获取数据并将结果分配给 Java 变量。然后,在第二步中,我将该值插入回另一个 BigQuery 表中。当我这样做时,目标表中的数据会略有变化,所以我试图弄清楚如何防止这种情况发生。
下面是原始值和移动到另一个表后的值的示例:
这是我的十六进制查看器中同一文件的屏幕截图:
如您所见,该值已发生一些更改 - 新值似乎是c3 85 e2 80 9a
所以我的问题真的是我怎样才能保留原始价值?在将数据提取到 Java 中,然后将其放回 BigQuery 的过程中,似乎发生了一些事情。我的 Java 环境配置为使用 UTF8 编码,所以我对如何保留原始值有点困惑。
答:
这是一个“双重编码”的情况。
原始字符串必须是“Słavomir”。第二个字母 (ł) 用 UTF-8 的两个字节编码。C582
现在,当 UTF-8 编码的字符串入到数据库中时,有人将 PostgreSQL 客户端编码设置为单字节编码,可能是 LATIN-1。因此,PostgreSQL 将这两个字节解释为单独的字符:is “Å”,并且是一个不可打印的字符,一个名称为“break allowed here”的控制字符。C5
82
PostgreSQL 将这两个字符转换为编码 UTF-8 的服务器,从而将它们转换为您观察到的四个字节。每个字符由 UTF-8 中的两个字节表示。
在转换为 BigQuery 期间,“此处允许的中断”字符会转换为“大括号”(')。必须发生的事情是这样的:
数据使用客户端编码 LATIN-1 提取,因此结果为
C582
客户端入到 BigQuery 中,客户端编码为 WINDOWS-1252,其中表示大括号
82
BigQuery 服务器将大括号转换为 ZTF-8,最终得到
E2809A
总结一下:
PostgreSQL 数据库中的原始数据已损坏,因为已设置为导入数据时,而不是在导入数据时
client_encoding
LATIN1
UTF8
在传输到 BigQuery 期间,由于 BigQuery 客户端编码设置为 WINDOWS-1252,因此数据被进一步破坏
评论
length
bit_length()
select bit_length('Å'); 16
show lc_ctype'
Å
show lc_ctype;
C3 85
是“Å”的 UTF-8 字节。 是 Break Allowed Here 控件字符的 UTF-8 字节。它是一个不可见的字符,允许不带连字符的换行符(无论它来自何处)。请注意,它不返回 UTF-8 字节数,而是返回 Unicode 代码点数。该单词有 8 个可见字母和 1 个不可见控制字符,结果是 9。C2 82
length()
bytes.fromhex('53c385c28261776f6d6972').decode('utf8').encode('latin1').decode('utf8')
'Sławomir'