【问题标题】:Java: Readers and EncodingsJava:阅读器和编码
【发布时间】:2009-12-11 13:50:20
【问题描述】:

Java 的默认编码是ASCII。是的? (见下面我的编辑)

当文本文件以UTF-8 编码时?读者怎么知道他必须使用UTF-8

我所说的读者是:

  • FileReaders
  • BufferedReaders 来自Sockets
  • 来自System.inScanner
  • ...

编辑

原来我们的编码依赖于操作系统,这意味着以下内容并非在每个操作系统上都是正确的:

'a'== 97

【问题讨论】:

  • 我发现这个问题(和答案)很有教育意义,谢谢!
  • 您编辑的结论是正确的。 'a'==97 在 Java 的所有操作系统上都是正确的,因为在内部(对于 char/String)Java 在任何地方都使用 UTF-16。只有当您以某种方式使用平台默认编码(即从 char/String 转换为 byte/byte[] 而不明确指定编码)时,结果才会取决于 OS/settings/...

标签: java encoding io


【解决方案1】:

读者如何知道他必须使用 UTF-8?

您通常在InputStreamReader 中指定您自己。它有一个采用字符编码的构造函数。例如

Reader reader = new InputStreamReader(new FileInputStream("c:/foo.txt"), "UTF-8");

所有其他读者(据我所知)使用平台默认字符编码,这可能确实不是正确的编码(例如 -cough- CP-1252)。

理论上你也可以根据byte order mark自动检测字符编码。这将几种 unicode 编码与其他编码区分开来。不幸的是,Java SE 没有任何 API,但您可以自制一个可用于替换 InputStreamReader 的 API,如上面的示例所示:

public class UnicodeReader extends Reader {
    private static final int BOM_SIZE = 4;
    private final InputStreamReader reader;

    /**
     * Construct UnicodeReader
     * @param in Input stream.
     * @param defaultEncoding Default encoding to be used if BOM is not found,
     * or <code>null</code> to use system default encoding.
     * @throws IOException If an I/O error occurs.
     */
    public UnicodeReader(InputStream in, String defaultEncoding) throws IOException {
        byte bom[] = new byte[BOM_SIZE];
        String encoding;
        int unread;
        PushbackInputStream pushbackStream = new PushbackInputStream(in, BOM_SIZE);
        int n = pushbackStream.read(bom, 0, bom.length);

        // Read ahead four bytes and check for BOM marks.
        if ((bom[0] == (byte) 0xEF) && (bom[1] == (byte) 0xBB) && (bom[2] == (byte) 0xBF)) {
            encoding = "UTF-8";
            unread = n - 3;
        } else if ((bom[0] == (byte) 0xFE) && (bom[1] == (byte) 0xFF)) {
            encoding = "UTF-16BE";
            unread = n - 2;
        } else if ((bom[0] == (byte) 0xFF) && (bom[1] == (byte) 0xFE)) {
            encoding = "UTF-16LE";
            unread = n - 2;
        } else if ((bom[0] == (byte) 0x00) && (bom[1] == (byte) 0x00) && (bom[2] == (byte) 0xFE) && (bom[3] == (byte) 0xFF)) {
            encoding = "UTF-32BE";
            unread = n - 4;
        } else if ((bom[0] == (byte) 0xFF) && (bom[1] == (byte) 0xFE) && (bom[2] == (byte) 0x00) && (bom[3] == (byte) 0x00)) {
            encoding = "UTF-32LE";
            unread = n - 4;
        } else {
            encoding = defaultEncoding;
            unread = n;
        }

        // Unread bytes if necessary and skip BOM marks.
        if (unread > 0) {
            pushbackStream.unread(bom, (n - unread), unread);
        } else if (unread < -1) {
            pushbackStream.unread(bom, 0, 0);
        }

        // Use given encoding.
        if (encoding == null) {
            reader = new InputStreamReader(pushbackStream);
        } else {
            reader = new InputStreamReader(pushbackStream, encoding);
        }
    }

    public String getEncoding() {
        return reader.getEncoding();
    }

    public int read(char[] cbuf, int off, int len) throws IOException {
        return reader.read(cbuf, off, len);
    }

    public void close() throws IOException {
        reader.close();
    }
}

编辑作为对您编辑的回复:

所以编码取决于操作系统。所以这意味着并非在每个操作系统上都是如此:

'a'== 97

不,这不是真的。 ASCII 编码(包含 128 个字符,0x00 直到 0x7F)是所有其他字符编码的基础。只有ASCII 字符集之外的字符可能会在另一种编码中以不同方式显示。 ISO-8859 编码覆盖ASCII 范围内具有相同代码点的字符。 Unicode 编码涵盖ISO-8859-1 范围内具有相同代码点的字符。

您可能会发现这些博客中的每一个都很有趣:

  1. The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)(两者更理论化)
  2. Unicode - How to get the characters right?(两者中更实用)

【讨论】:

  • 您忘记了 EBCDIC... 在 EBCDIC 中,'a' != 97。
  • 哈哈,你说得对。 EBCDIC 确实是一种专有 (IBM) 编码,它在旧时代存在于ASCII下一个,但最终ASCII 获得了世界统治地位。现在你只会在旧的“大型机”(S/390、VM 和 consorts)中看到 EBCDIC,它们轮流使用,但可以配置为使用普通的 ASCII
  • 这个贴很透彻,代码也不错。我本可以发誓某些 API 会自动检测常见编码,但这可能仅适用于网络内容(特别是 HTTP)。
  • 另外,'a'==97 在 Java 中始终为真这一事实不一定与 ASCII 直接有关,而是 Java 在内部使用 UTF-16 的结果。
【解决方案2】:

Java 的默认编码取决于您的操作系统。对于 Windows,通常是“windows-1252”,对于 Unix,通常是“ISO-8859-1”或“UTF-8”。

读者知道正确的编码是因为您告诉它正确的编码。不幸的是,并非所有读者都允许您这样做(例如,FileReader 不允许),因此您通常必须使用 InputStreamReader

【讨论】:

  • 仅供参考:Windows 上的编码取决于操作系统语言。 msdn.microsoft.com/en-us/library/dd317756%28VS.85%29.aspx 但本质上,你是对的——默认编码是系统编码——使用它,后果自负。
  • 默认编码的系统属性是file.encoding,它通常取决于您的区域设置(Win 中的语言,如上所述,以及 *nix 上的LC_* 环境变量)。
  • @gustafc - 感谢您提供的信息;我查看了 System.getProperties() 并没有在标准属性中看到它,因此从我的回复中对其进行了编辑。我仍然认为依赖正确设置的默认值是一个坏主意。
  • 遗憾的是,FileReader 是一个方便的类,用于从文件中读取默认编码的字符,仅此而已。您会认为它也允许您选择字符集,但事实并非如此。实际上,您会注意到构造函数签名与FileInputStream 的构造函数签名相同。
  • @kdgregory - 关于file.encoding 不是标准属性,您是正确的;这是一个开发人员不应该直接设置或阅读的实现细节:bugs.sun.com/view_bug.do?bug_id=4163515
【解决方案3】:

我想先处理这部分:

Java 的默认编码是 ASCII。是吗?

Java 环境中至少有 4 种不同的东西可以称为“默认编码”:

  1. “默认字符集”是 Java 在运行时用于将字节转换为字符(以及 byte[]String)的内容,此时未指定其他任何内容。这取决于平台、设置、命令行参数……通常只是平台默认编码。
  2. Java 在char 值和String 对象中使用的内部字符编码。这个是永远UTF-16!没有办法改变它,它只是UTF-16!这意味着表示achar 总是 具有数值97,而表示π 的字符总是具有数值960。
  3. Java 用于在.class 文件中存储字符串常量的字符编码。这个是始终 UTF-8。没有办法改变它。
  4. Java 编译器用来解释.java 文件中的Java 源代码的字符集。这个默认为默认字符集,但可以在编译时配置。

阅读器如何知道他必须使用 UTF-8?

它没有。如果您有一些纯文本文件,那么您必须知道正确读取它的编码。如果幸运的话,您可以猜到(例如,您可以尝试平台默认编码),但这是一个容易出错的过程,在许多情况下您甚至无法意识到您猜错了。这不是特定于 Java 的。对于所有系统都是如此。

某些格式(例如 XML 和所有基于 XML 的格式)在设计时就考虑到了这一限制,并包含一种指定数据编码的方法,因此不再需要猜测。

阅读The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)了解详情。

【讨论】:

    【解决方案4】:

    对于大多数读者来说,Java 使用您的平台使用的任何编码和字符集——这可能是 ASCII 或 UTF-8 的某种风格,或者像 JIS(在日本)这样更奇特的东西。然后将此集中的字符转换为 Java 内部使用的 UTF-16。

    如果平台编码不同于文件编码(我的问题——UTF-8 文件是标准的,但我的平台使用 Windows-1252 编码),则有一种解决方法。创建一个 InputStreamReader 实例,该实例使用指定编码的构造函数。

    编辑:这样做:

    InputStreamReader myReader = new InputStreamReader(new FileInputStream(myFile),"UTF-8");
    //read data
    myReader.close();
    

    但是,IIRC 有一些规定可以自动检测常见编码(例如 UTF-8 和 UTF-16)。 UTF-16 可以通过开头的字节顺序标记来检测。 UTF-8 也遵循某些规则,但通常情况下,您的平台编码和 UTF-8 的区别并不重要,除非您使用国际字符代替拉丁字符。

    【讨论】:

    • 不,我不是法国人。我是荷兰人。我说弗拉芒语。
    • 哈,我自己其实是半荷兰人。我忘了你们这些疯狂的 Flamish 家伙。
    • 你当然是荷兰人。它将是法语中的“Martin”,而不是“Martijn”:)
    • 狂热?你的意思是佛兰芒语:)
    • 我拒绝用听起来像纸巾里的东西的名字来称呼一个民族。发火,我说! :)
    【解决方案5】:

    你可以从这里开始了解这个想法java Charset API

    请注意,根据文档,

    本机字符编码 Java 编程语言是 UTF-16

    编辑:

    对不起,我还没来得及完成这个就被叫走了,也许我不应该按原样发布部分答案。无论如何,其他答案解释了细节,关键是每个平台的本机文件字符集以及常见的备用字符集将被 java 正确读取。

    【讨论】:

    • 虽然技术上是正确的,但这无关紧要……本机编码仅在 Java 中使用。文件 I/O 完成后,Reader 类使用平台默认编码,除非您使用支持编码/字符集参数的 InputStreamReader 的构造函数指定一种。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-16
    相关资源
    最近更新 更多