Ohhnews

分类导航

$ cd ..
DZone Java原文

Java JEP 400解析:为何UTF-8成为默认字符集

#java#jep 400#utf-8#字符集#jdk

一个JDK增强提案(JEP)是一个正式流程,用于提议和记录对Java开发工具包的改进。它确保增强功能经过周密规划、审查和集成,以使JDK在未来保持现代化、一致性和可持续性。自提出以来,许多JEPs都引入了重要的语言和运行时特性,塑造了Java的演进。其中一个重要的提案是JEP 400,它于2022年在JDK 18中引入,将UTF-8标准化为默认字符集,解决了长期存在的平台依赖编码问题,并提高了Java的跨平台可靠性。

传统上,Java的I/O API(在JDK 1.1中引入)包含FileReaderFileWriter等类,用于读写文本文件。这些类依赖于Charset来正确解释字节数据。当字符集显式传递给构造函数时,例如:

$ java
public FileReader(File file, Charset charset) throws IOException
$ java
public FileWriter(String fileName, Charset charset) throws IOException

API会使用该字符集进行文件操作。然而,这些类也提供了不接受字符集的构造函数:

$ java
public FileReader(String fileName) throws IOException
$ java
public FileWriter(String filename) throws IOException

在这些情况下,Java会默认使用平台的字符集。根据JDK 17文档的说明:

“默认字符集在虚拟机启动期间确定,通常取决于底层操作系统的区域设置和字符集。”

这种行为可能导致文件在不同字符集下读写时出现错误——尤其是在跨环境时。

为了解决这种不一致性,JEP 400提议在未显式提供字符集时使用UTF-8作为默认字符集。这一改变使得Java应用程序更具可预测性,并减少了错误,尤其是在跨平台环境中。

正如JDK 18 API中所述:

“默认字符集是UTF-8,除非以特定于实现的方式更改。”

重要的是,此更新并未取消指定字符集的能力。开发人员仍然可以通过构造函数或JVM标志-Dfile.encoding来设置它。

接下来,我们通过一个示例来探讨所讨论的问题:

$ java
package com.jep400;

import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.Charset;

public class WritesFiles {

    public static void main(String[] args) {
        System.out.println("Current Encoding: " + Charset.defaultCharset().displayName());
        writeFile();
    }

    private static void writeFile() {
        try (FileWriter fw = new FileWriter("fw.txt")){
            fw.write("résumé");
            System.out.println("Completed file writing.");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

writeFile方法中,我们使用了不带字符集参数的FileWriter构造函数。

因此,JDK会回退到默认字符集,该字符集要么通过-Dfile.encoding JVM参数指定,要么从平台的区域设置中派生。

该程序写入一个包含一些文本的文件。为了模拟字符集不匹配,我们使用特定编码运行程序:

Java -Dfile.encoding=ISO-8859-1 com.jep400.WritesFiles

在这里,我们显式地将字符集设置为ISO-8859-1,以模拟在默认字符集为ISO-8859-1且未通过程序传递字符集的系统上运行程序的情况。

执行时,程序会产生以下输出:

Output:
Current Encoding: ISO-8859-1
Completed file writing.
Consider the following file that reads the same file but with different encoding

上述程序完成后,会创建一个名为fw.txt的文件。

接下来,我们来看一个读取由上一个程序创建的fw.txt文件的程序。

$ java
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.Charset;

public class ReadsFiles {

    public static void main(String[] args) {
        System.out.println("Current Encoding: " + Charset.defaultCharset().displayName());
        readFile();
    }

    private static void readFile() {
        try(FileReader fr = new FileReader("fw.txt")) {
            int character;
            while ((character = fr.read()) != -1) {
                System.out.print((char) character);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

readFile方法中,我们使用了未指定字符集的FileReader构造函数。

为了模拟在具有不同默认字符集的平台上运行程序,我们传递了一个VM参数:

java -Dfile.encoding=UTF-8 com.jep400.ReadsFiles

运行此命令时将显示以下输出:

Current Encoding: UTF-8
r�sum�

输出显示文本与第一个程序写入的内容不匹配。

这突出了在读写文件时未显式指定字符集,而是依赖于平台默认字符集的问题。

在以下场景中,这种不匹配可能导致相同的错误输出:

  1. 当程序在具有不同默认字符集的不同机器上运行时。
  2. 当升级到JDK 18或更高版本时,这会改变默认字符集行为。

现在,让我们看看在JDK 18+环境中运行相同程序时的输出。

运行第一个程序时,观察到以下输出:

Current Encoding: UTF-8

Completed file writing.

运行第二个程序时,输出如下所示:

Current Encoding: UTF-8

résumé

我们可以看到,数据使用标准的UTF-8字符集进行写入和读取,从而有效解决了之前遇到的字符集问题。

结论

自JDK 18中引入以来,JEP 400将UTF-8作为默认字符集的采用已成为全球Java应用程序的基础性改进。通过标准化UTF-8,它有效地消除了开发人员在跨不同平台运行代码时面临的许多字符集相关问题。虽然这不是一个新变化,但其持续影响确保了现代Java项目中更好的一致性和更少的错误。开发人员在必要时仍应显式指定字符集,但依赖UTF-8作为默认值可以增强跨平台兼容性,并有助于随着Java生态系统的快速演进使应用程序面向未来。虽然并非总是必需,但与此默认设置保持一致有助于在不同环境中实现一致性。