【问题标题】:Java compiler platform file encoding problemJava编译平台文件编码问题
【发布时间】:2011-02-07 22:48:27
【问题描述】:

最近我遇到了一个我不记得曾经遇到过的文件字符编码问题。必须了解文本文件的字符编码并编写在不同平台上运行时正确处理编码的代码是很常见的。但我发现的问题是由与执行平台不同的平台上的编译引起的。这完全出乎我的意料,因为根据我的经验,当 javac 创建类文件时,重要的参数是 java 源和目标参数,以及进行编译的 JDK 版本。我的情况是,在 Mac OS X 上使用 JDK 1.6.0_22 编译的类与在 Linux 上使用 1.6.0_23-b05 编译的类在 Mac OS X 上运行时的行为不同。指定的源和目标是 1.4。

使用 PrintStream println 方法将内存中编码为 ISO-8859_1 的字符串写入磁盘。根据 Java 代码在哪个平台上编译,字符串的编写方式不同。这会导致一个错误。该错误的修复是在写入和读取文件时明确指定文件编码。

令我惊讶的是,行为会因类的编译位置而异,而不是类在哪个平台上运行。我非常熟悉在不同平台上运行时表现不同的 Java 代码。但是,当在不同平台上编译的相同代码在同一平台上运行不同时,这有点可怕。

有人遇到过这个具体问题吗?对于任何在没有明确指定字符编码的情况下读取和写入字符串到文件的 Java 代码来说,这似乎都是不祥之兆。多久进行一次?

【问题讨论】:

  • 问题文件是否编码为 utf-8?源中是否存在有问题的字符,或者这些字符是否仅在该特定机器上编译之后才无效?
  • 这是否使用静态 final 编译到类中(编译静态 final 将字符串“烘焙”到类中)?或者当您说写入磁盘时,您是在序列化数据吗?序列化一个类实例?使用默认(即编译平台)编码编译的序列化方法?
  • @Steve B.:事实上,所有的字符串字面量和其他编译时常量字符串都被“烘焙”到了类中,而不仅仅是静态的 final 。

标签: java character-encoding javac


【解决方案1】:

没有像 内存中编码为 ISO-8859-1 的字符串这样的东西。内存中的 Java 字符串始终是 Unicode 字符串。 (以 UTF-16 编码(截至 2011 年——我认为它随着更高的 Java 版本而改变),但您现在不需要这样做)。

编码仅在您输入或输出字符串时起作用 - 然后,在没有明确编码的情况下,它使用系统默认值(在某些系统上取决于用户设置)。

正如 McDowell 所说,源文件的实际编码应该与编译器对源文件假定的编码相匹配,否则会出现问题。您可以通过多种方式实现这一目标:

  • 使用编译器的-encoding 选项,给出源文件的编码。 (使用 ant,您可以设置 encoding= 参数。)
  • 使用您的编辑器或任何其他工具(如recode)将文件的编码更改为编译器默认值。
  • 使用native2ascii(带有正确的-encoding 选项)通过\uXXXX-escapes 将源文件转换为ASCII。

在最后一种情况下,您稍后可以使用每种默认编码在任何地方编译此文件,因此如果您将源代码提供给不了解编码的人在某个地方编译,这可能是一种方法。

如果您有一个包含多个文件的较大项目,它们都应该具有相同的编码,因为编译器只有一个这样的开关,而不是几个。

在过去几年的所有项目中,我总是以 UTF-8 编码我的所有文件,并在我的 ant 构建文件中将 encoding="utf-8" 参数设置为 javac 任务。 (我的编辑器很聪明,可以自动识别编码,但我将默认设置为 UTF-8。)

编码对其他源代码处理工具很重要,例如 javadoc。 (在那里,您还应该为输出添加 -charset-docencoding 选项 - 它们应该匹配,但可以与源不同 --encoding。)

【讨论】:

  • 这与源编码无关。不涉及字符串文字。从网络连接读取字符串,然后写入文件。我所说的“在内存中编码为 ISO-8859-1”的意思是使用该字符集读取输入流,因为这就是它的编码方式。
  • "没有显式编码,它使用系统默认值" 是的,但是运行时 VM 的系统默认值,对吗?在这种情况下,编码显然是由编译平台决定的。 PrintStream 的行为不同,具体取决于编译平台。这不是可移植的行为。你明白我的意思了吗?
  • 我认为我们需要一个最小的代码示例。这看起来像是两个系统上的两个编译器选择了不同的方法。
  • 抱歉,NDA 阻止包含源代码。
  • 这就是为什么我说 minimal example ...最小化你的代码,直到问题消失(然后你找到了罪魁祸首),或者直到没有任何秘密(仍然是问题)。
【解决方案2】:

我猜测在编译阶段存在转码问题,并且编译器缺乏关于源文件编码的方向(例如,参见 javac -encoding 开关)。

如果您不具体,编译器通常会使用系统默认编码,这可能会导致字符串和字符文字被破坏(在内部,Java 字节码使用修改后的 UTF-8 格式,因此二进制文件是可移植的)。这是我可以想象在编译时引入问题的唯一方法。

我已经写了一些关于这个here的文章。

【讨论】:

    【解决方案3】:

    始终在您的源文件中使用转义码(例如\uxxxx),这不会有问题。 @Paulo 提到了这一点,但我想明确指出。

    【讨论】:

      【解决方案4】:

      在做数学公式时,我在使用不是 ascii 的变量名(Σ、σ、Δ 等)时遇到了类似的问题。在 linux 上,它在解释时使用 UTF-8 编码。在 Windows 上,它抱怨名称无效,因为 Windows 使用 ISO-LATIN-1。解决方案是在我用来编译这些文件的 ant 脚本中指定编码。

      【讨论】:

      • 很好,我想通常人们会写Sigma(或sum)、sigmadelta 等等,而不是使用正确的希腊字母。我曾经创建了一个名为 的变量。我想叫它ℕ₀,但是javac不接受这个,因为不是Java的数字。
      • @Paŭlo Ebermann 我遇到的问题是变量太多,方程复杂到足以使文档成为 PITA。我使用了特殊字符,文档/正确性证明是“参见:skolnik,pp XXX-XXX”。变量与文本相同的事实使其他人更容易理解。
      猜你喜欢
      • 2017-01-28
      • 1970-01-01
      • 1970-01-01
      • 2013-02-04
      • 2012-06-12
      • 1970-01-01
      • 1970-01-01
      • 2018-10-04
      • 2013-09-22
      相关资源
      最近更新 更多