Java中的UTF-8字符编码

10

我尝试将一些法语文本转换为UTF8以便在控制台、文本文件或GUI元素中正确显示,但遇到了一些问题。

原始字符串为

HANDICAP╔ES

应该是

HANDICAPÉES

这里是一个代码片段,展示了我如何在Eclipse/Linux环境下使用jackcess数据库驱动程序读取Acccess MDB文件。

Database database = Database.open(new File(filepath));
Table table = database.getTable(tableName, true);
Iterator rowIter = table.iterator();
while (rowIter.hasNext()) {
    Map<String, Object> row = this.rowIter.next();
    // convert fields to UTF
    Map<String, Object> rowUTF = new HashMap<String, Object>();
    try {
        for (String key : row.keySet()) {
            Object o = row.get(key);
            if (o != null) {
                String valueCP850 = o.toString();
                // String nameUTF8 = new String(valueCP850.getBytes("CP850"), "UTF8"); // does not work!
                String valueISO = new String(valueCP850.getBytes("CP850"), "ISO-8859-1");
                String valueUTF8 = new String(valueISO.getBytes(), "UTF-8"); // works!
                rowUTF.put(key, valueUTF8);
            }
        }
    } catch (UnsupportedEncodingException e) {
        System.err.println("Encoding exception: " + e);
    }   
}

在代码中,你会看到我想直接转换为UTF8,但这似乎不起作用,所以我必须进行双重转换。此外,请注意,在使用jackcess驱动程序时似乎没有指定编码类型的方法。

谢谢, Cam


1
这不是UTF-8,而是CP850。 - Joey
你是说原始字符串是CP850编码吗?我意识到原始字符串不是UTF-8编码,但我不确定具体的编码方式。我正在尝试将其转换为UTF-8以便正确显示。而且据我所知,É字符是由UTF-8支持的。谢谢。 - cambo
3
当你将CP1252中的É解释为CP850时,所得到的结果是 - Joey
4个回答

9

基于新信息的新分析。
看起来您的问题与文本在存储到Access数据库之前的编码有关。它似乎已被编码为ISO-8859-1或windows-1252,但解码为cp850,导致字符串HANDICAP╔ES存储在数据库中。

现在,您已正确地从数据库检索到该字符串,正在尝试纠正原始编码错误并恢复应存储的字符串:HANDICAPÉES。您可以通过这行代码实现:

String valueISO = new String(valueCP850.getBytes("CP850"), "ISO-8859-1");

getBytes("CP850") 将字符 转换为字节值 0xC9,而字符串构造函数则根据 ISO-8859-1 对其进行解码,得到字符 É。接下来的一行:

String valueUTF8 = new String(valueISO.getBytes(), "UTF-8");

...没有任何作用。 getBytes()使用平台默认编码对字符串进行编码,在您的Linux系统上是UTF-8。然后,String构造函数使用相同的编码进行解码。删除该行代码,应该仍然可以得到相同的结果。

更重要的是,您试图创建“UTF-8字符串”是错误的。您不需要关心Java字符串的编码 - 它们始终是UTF-16。在将文本带入Java应用程序时,您只需要确保使用正确的编码进行解码即可。

如果我的分析是正确的,您的Access驱动程序确实正在正确解码它;问题出现在另一端,可能是在数据库甚至进入图片之前。这就是您需要解决的问题,因为那个new String(getBytes()) hack不能保证在所有情况下都能正常工作。


基于没有信息的原始分析。 :-/
如果您在控制台上看到HANDICAP╔ES,那么可能没有问题。鉴于此代码:

System.out.println("HANDICAPÉES");

JVM会在发送字符串到控制台前,将其转换为平台默认编码windows-1252(Unicode),然后控制台使用自己的默认编码cp850对其进行解码。因此,控制台显示不正确是正常的。如果您希望正确显示,可以使用以下命令更改控制台的编码:

CHCP 1252

要在GUI元素(如JLabel)中显示字符串,您无需做任何特殊处理。只需确保使用的字体可以显示所有字符即可,但这对于法语来说不应该是问题。
至于写入文件,只需在创建Writer时指定所需的编码即可:
OutputStreamWriter osw = new OutputStreamWriter(
    new FileOutputStream("myFile.txt"), "UTF-8");

我想我应该更清楚地说明我的开发环境。 我在Ubuntu Linux机器上使用Eclipse进行开发。 无论是从Eclipse控制台还是通过常规终端控制台运行,我都会得到相同的结果。我们正在使用jackcess Java API读取Access MDB数据库文件。 似乎没有办法为jackcess驱动程序指定默认编码,因此我必须按照上述描述进行转换。我尝试直接将字符串输出到GUI元素(JLabel,JTextField)中,但也没有帮助。 - cambo
是的,这似乎是一个相当奇特的问题,在原始问题中没有任何提示。如果我们能看到您用于检索数据的实际代码,可能会有所帮助。不要尝试将其放在评论中 - 您已经看到它的效果如何。编辑问题并将其放在那里。 - Alan Moore
好的,我已经编辑了问题,展示了我用来检索数据的代码示例。谢谢。 - cambo

8
String s = "HANDICAP╔ES";
System.out.println(new String(s.getBytes("CP850"), "ISO-8859-1")); // HANDICAPÉES

这显示了正确的字符串值。这意味着它最初是使用ISO-8859-1进行编码/解码,然后使用CP850(最初是CP1252,也称为Windows ANSI,因为在那里É具有与ISO-8859-1相同的代码点)错误地进行编码。将您的环境和二进制流水线对齐以使用完全相同的字符编码。您不能且不应该在它们之间进行转换。那样会冒失失去非ASCII范围内的信息。注意:不要使用上面的代码片段来“修复”问题!那不是正确的解决方案。
更新:显然您仍在努力解决这个问题。我将重复答案中的重要部分:
  1. 使您的环境和二进制管道使用全部相同的字符编码。

  2. 不能也不应该在它们之间进行转换。这样会冒失在非ASCII范围内丢失信息。

  3. 不要使用上面的代码片段来“修复”问题!那不是正确的解决方案。

为解决问题,您需要选择字符编码X,并在整个应用程序中使用它。我建议使用UTF-8。更新MS Access以使用编码X。更新开发环境以使用编码X。更新代码中的java.io读写器以使用编码X。更新编辑器以使用编码X读/写文件。更新应用程序的用户界面以使用编码X。在任何步骤中不要使用Y或Z或其他编码。如果字符已经在某些数据存储(MS Access、文件等)中损坏,则需要手动替换数据存储中的字符。不要使用Java来实现这一点。
如果您实际上使用“命令提示符”作为用户界面,那么您实际上已经迷失了方向。它不支持UTF-8。如评论和评论中链接的文章所建议的,您需要创建一个Swing应用程序,而不是依赖于受限制的命令提示符环境。

2
你需要指示JDBC驱动程序和/或数据库使用正确的编码(即数据库本身正在使用的编码!)。 UTF-8确实支持这些字符,但使用不同的二进制表示法,如果你知道我的意思。字符就像任何其他东西一样以字节形式传输。这是因为计算机无法理解其他任何内容。 此文章可能会更有助于理解底层问题。 - BalusC
我又有一个问题了...我不应该能够直接从原始编码转换为UTF8吗?<code> String name = "HANDICAP╔ES"; String nameISO = new String(name.getBytes("CP850"), "ISO-8859-1"); String nameUTF8 = new String(name.getBytes("CP850"), "UTF8"); String nameUTF8_2 = new String(nameISO.getBytes(), "UTF8"); System.out.println("nameISO=" + nameISO); // 可以运行 System.out.println("nameUTF8=" + nameUTF8); // 无法运行 System.out.println("nameUTF8=" + nameUTF8_2); // 可以运行 </code> 显然,我仍然不明白"引擎盖下面"的东西。我现在会重新阅读你的文章。 - cambo
你应该在所有层面上保持并使用相同的编码,以避免编码问题。不应该从一种编码转换为另一种编码。如果数据库中包含编码X的信息,则应使用编码X而不是Y来显示它。当处理用户输入时,应使用编码X进行处理,而不是Y。如果需要更改编码,则应在应用程序的所有层面以及数据库中进行更改。 - BalusC
还要仔细阅读之前链接文章中的“开发环境”部分。Windows命令控制台不支持Unicode。使用Swing或IDE,或者只需写入文本文件即可。 - BalusC
亲爱的BalusC,感谢您更新的回复。我们在整个应用程序中使用的是单一编码UTF8。然而,正如我在之前的评论中解释的那样,我们无法控制Access DB文件的创建 - 我们从第三方来源获取它,并且没有办法让他们修复其编码问题。这就是为什么我必须将其从Access DB中的错误编码转换为UTF8,这是我们应用程序的其余部分使用的编码。这个从Access DB导入的过程是我们应用程序流水线的初始步骤。 - cambo
显示剩余4条评论

0

在建立连接时,您可以指定编码方式。这种方法非常完美,解决了我的编码问题:

    DatabaseImpl open = DatabaseImpl.open(new File("main.mdb"), true, null, Database.DEFAULT_AUTO_SYNC, java.nio.charset.Charset.availableCharsets().get("windows-1251"), null, null);
    Table table = open.getTable("FolderInfo");

-1

使用 "ISO-8859-1" 帮助我处理法语字符。


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