6

我真的希望下面的字节数据应该显示不同,但实际上它们是相同的,根据 wiki http://en.wikipedia.org/wiki/UTF-8#Examples,字节中的编码看起来不同,但为什么Java把它们打印出来一样吗?

    String a = "€";
    byte[] utf16 = a.getBytes(); //Java default UTF-16
    byte[] utf8 = null;

    try {
        utf8 = a.getBytes("UTF-8");
    } catch (UnsupportedEncodingException e) {
        throw new RuntimeException(e);
    }

    for (int i = 0 ; i < utf16.length ; i ++){
        System.out.println("utf16 = " + utf16[i]);
    }

    for (int i = 0 ; i < utf8.length ; i ++){
        System.out.println("utf8 = " + utf8[i]);
    }
4

4 回答 4

9

尽管 Java 在内部将字符保存为 UTF-16,但当您使用 转换为字节时String.getBytes(),每个字符都会使用默认平台编码进行转换,该编码可能类似于windows-1252。我得到的结果是:

utf16 = -30
utf16 = -126
utf16 = -84
utf8 = -30
utf8 = -126
utf8 = -84

这表明我的系统上的默认编码是“UTF-8”。

另请注意, String.getBytes() 的文档有以下注释:The behavior of this method when this string cannot be encoded in the default charset is unspecified.

不过,通常情况下,如果您始终像使用a.getBytes("UTF-8")

此外,可能导致混淆的另一件事是直接在源文件中包含 Unicode 字符:String a = "€";. 该欧元符号必须经过编码才能在文件中存储为一个或多个字节。当 Java 编译您的程序时,它会看到这些字节并将它们解码回欧元符号。你希望。您必须确保将欧元符号保存到文件中的软件(记事本、eclipse 等)对其进行编码的方式与 Java 在将其读回时所期望的方式相同。UTF-8 变得越来越流行,但它并不通用而且很多编辑器不会用 UTF-8 编写文件。

于 2012-10-18T03:00:08.530 回答
4

一个好奇心,我想知道 JVM 是如何知道原始默认字符集的......

JVM 用来确定初始默认字符集的机制是特定于平台的。在 UNIX/类 UNIX 系统上,它由 LANG 和 LC_* 环境变量决定;见man locale


Ermmm .. 此命令用于检查特定操作系统中的默认字符集是什么?

那是对的。但是我告诉你这是因为手册条目描述默认编码是如何由环境变量确定的。

回想起来,这可能不是您最初评论的意思,但这就是指定平台默认编码的方式。(并且单个文件的“默认字符集”的概念是没有意义的;见下文。)

如果假设我有 10 个 Java 源文件,其中一半保存为 UTF-8,其余保存为 UTF-16,编译后,我将它们(类文件)移动到另一个操作系统平台,现在 JVM 如何知道它们的默认编码? Java 类文件中是否会包含默认字符集信息?

这是一组相当混乱的问题:

  1. 文本文件没有默认字符集。它有一个字符集/编码。

  2. 非文本文件根本没有字符编码。这个概念毫无意义。

  3. 没有 100% 可靠的方法来确定文本文件的字符编码是什么。

  4. 如果您不告诉 java 编译器文件的编码是什么,它将假定它是平台的默认编码。编译器不会尝试第二次猜测您。如果您的编码不正确,编译器可能会也可能不会注意到您的错误。

  5. 字节码(“.class”)文件是二进制文件(见 2)。

  6. 当字符和字符串文字被编译成“.class”文件时,它们现在以不受平台默认编码或您可以影响的任何其他方式影响的方式表示。

  7. 如果编译时源文件编码出错,则无法在“.class”文件级别进行修复。您唯一的选择是返回并重新编译类,告诉 Java 编译器正确的源文件编码。

  8. “如果假设我有 10 个 Java 源文件,其中一半保存为 UTF-8,其余保存为 UTF-16”
    只是不要这样做!

    • 不要将源文件保存在混合编码中。你会把自己逼疯的。
    • 我根本没有充分的理由以 UTF-16 存储文件......

所以,我很困惑,虽然人们说“平台相关”,但它与源文件有关吗?

平台相关意味着它可能依赖于操作系统、JVM 供应商和版本、硬件等。

它不一定与源文件有关。(任何给定源文件的编码可能与默认字符编码不同。)

如果不是,我该如何解释上述现象?无论如何,上面的困惑将我的问题扩展到“所以,在我将源文件编译成类文件后会发生什么,因为类文件可能不包含编码信息,所以现在结果真的依赖于‘平台’而不是源文件了?”

特定于平台的机制(例如环境变量)决定了 java 编译器将什么视为默认字符集。除非您覆盖它(例如,通过在命令行上为 java 编译器提供选项),否则 Java 编译器将使用它作为源文件字符集。但是,这可能不是源文件的正确字符编码;例如,如果您在具有不同默认字符集的不同机器上创建它们。如果 java 编译器使用错误的字符集来解码你的源文件,很可能会将不正确的字符代码放入“.class”文件中。

“.class”文件不依赖于平台。但是,如果因为您没有告诉 Java 编译器源文件的正确编码而导致它们被错误地创建,“.class”文件将包含错误的字符。


为什么你的意思是:“单个文件的“默认字符集”的概念是没有意义的”?

我说它是因为它是真的!

默认字符集是指未指定时使用的字符集。

但是我们可以控制我们想要文本文件的存储方式吗?即使使用记事本,也可以在编码之间进行选择。

那是对的。那就是你告诉记事本该文件使用什么字符集。如果您不告诉它,记事本将使用默认字符集来写入文件。

记事本中有一点黑魔法来猜测读取文本文件时的字符编码是什么。基本上,它查看文件的前几个字节以查看它是否以 UTF-16 字节顺序标记开头。如果它看到一个,它可以启发式地区分 UTF-16、UTF-8(由 Microscoft 产品生成)和“其他”。但它无法区分不同的“其他”字符编码,并且它不会将不以 BOM 标记开头的文件识别为 UTF-8。(UTF-8 文件上的 BOM 是 Microsoft 特定的约定......如果 Java 应用程序读取文件并且不知道跳过 BOM 字符,则会导致问题。)

无论如何,问题不在于编写源文件。它们发生在 Java 编译器使用不正确的字符编码读取源文件时。

于 2012-10-18T03:39:25.897 回答
3

你正在处理一个糟糕的假设。该getBytes()方法不使用 UTF-16 编码。它使用平台默认编码。

您可以使用方法查询它java.nio.charset.Charset.defaultCharset()。就我而言,它是 UTF-8,对你来说也应该是一样的。

于 2012-10-18T03:03:49.230 回答
1

默认是UTF-8或者ISO-8859-1如果未找到特定于平台的编码。不是UTF-16。所以最终你只做字节转换UTF-8。这就是为什么你的byte[]比赛你可以找到默认编码使用

 System.out.println(Charset.defaultCharset().name());
于 2012-10-18T03:00:34.053 回答