杰瑞科技汇

Java中String的默认编码是什么?

Java 中 String 本身没有“默认编码”String 内部使用 UTF-16 编码来存储字符。

Java中String的默认编码是什么?-图1
(图片来源网络,侵删)

这句话是理解所有问题的关键,下面我们来详细解释。


String 的内部表示:UTF-16

当你创建一个 Java String 对象时,String s = "你好";,JVM 会将这个字符串中的每个字符(char)都使用 UTF-16 编码格式存储在内存中。

  • char 类型:Java 中的 char 是一个 16 位的无符号整数(U+0000U+FFFF),它直接对应 UTF-16 编码中的一个“代码单元”(Code Unit)。

  • 对于 BMP 字符:大多数常见的字符(包括所有英文字母、数字、中文、日文、韩文等)都在 Unicode 的“基本多语言平面”(Basic Multilingual Plane, BMP)内,这些字符的 Unicode 码点(Code Point)可以由一个 char(即一个 UTF-16 代码单元)完美表示。

    Java中String的默认编码是什么?-图2
    (图片来源网络,侵删)
    • 'A' 的码点是 U+0041,在 String 中就存储为一个 char0x0041
    • '你' 的码点是 U+4F60,在 String 中就存储为一个 char0x4F60
  • 对于辅助平面字符(Supplementary Characters):一些不常用的字符(如某些表情符号 、古文字等)的码点超出了 char 的 16 位范围(大于 U+FFFF),这些字符需要用一对 char(称为“代理对”,Surrogate Pair)来表示。

    • 的码点是 U+1F602,在 String 中,它会被存储为两个 char0xD83D0xDE02

关键点:无论你从哪里得到这个字符串(从文件读取、从网络接收、在代码中硬编码),一旦它被转换成了 String 对象,它的内部表示就统一是 UTF-16 了,这个过程是隐式的,通常由 I/O 操作或编译器完成。


编码的“问题”出在 I/O 操作上

既然 String 内部是 UTF-16,那为什么我们还需要关心“编码”呢?因为编码问题不在于 String 的存储,而在于 String 与外部世界(字节数组 byte[]、文件、网络)的转换

当需要将 String 写入文件或发送到网络时,JVM 必须将 UTF-16 的字符序列转换成一系列的字节(byte[]),这时,就需要选择一种字符编码方案(如 UTF-8, GBK, ISO-8859-1 等)来完成这个转换,反之,当从文件或网络读取字节流时,也需要知道它们是用哪种编码方案转换来的,才能正确地将其解码回 UTF-16 的 String

Java中String的默认编码是什么?-图3
(图片来源网络,侵删)

编码转换的两个关键方法:

  1. 编码:String.getBytes(Charset charset)

    • 作用:将 String(UTF-16)按照指定的 charset 编码成字节数组 byte[]

    • 示例

      String s = "Hello, 世界";
      // 使用 UTF-8 编码
      byte[] utf8Bytes = s.getBytes(StandardCharsets.UTF_8); // 推荐
      // 使用 GBK 编码(中文环境下常用)
      byte[] gbkBytes = s.getBytes("GBK"); // 需要处理 UnsupportedEncodingException
      // 使用平台默认编码(强烈不推荐!)
      byte[] defaultBytes = s.getBytes(); // 危险!
  2. 解码:new String(byte[] bytes, Charset charset)

    • 作用:将字节数组 byte[] 按照指定的 charset 解码成 String(UTF-16)。

    • 示例

      byte[] utf8Bytes = {(byte) 0xE4, (byte) 0xB8, (byte) 0xAD, (byte) 0xE6, (byte) 0x96, (byte) 0x87};
      // 使用 UTF-8 解码
      String s1 = new String(utf8Bytes, StandardCharsets.UTF_8); // s1 = "中文"
      // 使用错误的编码(如 ISO-8859-1)解码,会产生乱码
      String s2 = new String(utf8Bytes, StandardCharsets.ISO_8859_1); // s2 = "䏿–‡"
      // 使用平台默认编码解码(危险!)
      String s3 = new String(utf8Bytes); // 结果取决于你运行程序的机器环境

什么是“平台默认编码”?

很多人说的“String 默认编码”其实是指平台默认编码,这个默认编码由 JVM 启动时操作系统的环境变量 file.encoding 决定

  • 如何查看你的平台默认编码?
    public class DefaultEncoding {
        public static void main(String[] args) {
            System.out.println("Default Charset: " + Charset.defaultCharset());
            System.out.println("file.encoding property: " + System.getProperty("file.encoding"));
        }
    }
    • Windows (中文版) 系统上,它很可能是 GBK
    • macOS / Linux 系统上,它很可能是 UTF-8

为什么 s.getBytes()new String(bytes) 是危险的?

因为它们依赖于这个不可预测的“平台默认编码”,你在一台 Windows 机器上用 GBK 编码一个字符串,然后把这个 byte[] 发送到一台默认编码为 UTF-8 的 Linux 服务器上,服务器在用 new String() 解码时就会乱码

永远不要使用 s.getBytes()new String(bytes) 的无参版本! 始终显式地指定 Charset


最佳实践:始终显式指定编码

为了避免乱码和跨平台问题,你应该养成一个良好的习惯:在所有涉及编码转换的地方,都显式地指定一个标准、统一的编码

首选推荐:StandardCharsets.UTF_8

UTF-8 是目前互联网上最通用的编码,它向后兼容 ASCII,并且能高效地表示所有 Unicode 字符,从 Java 7 开始,StandardCharsets 类提供了一些预定义的 Charset 常量,使用它们比使用 String 名称(如 "UTF-8")更安全、更高效。

示例:读取文件

import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.io.IOException;
public class FileReaderExample {
    public static void main(String[] args) {
        String filePath = "my-file.txt";
        try {
            // 推荐:显式使用 UTF-8 读取文件内容
            String content = Files.readString(Paths.get(filePath), StandardCharsets.UTF_8);
            System.out.println(content);
            // 旧版方式(同样推荐)
            // String content = new String(Files.readAllBytes(Paths.get(filePath)), StandardCharsets.UTF_8);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

示例:写入文件

import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.io.IOException;
public class FileWriterExample {
    public static void main(String[] args) {
        String filePath = "my-file.txt";
        String content = "这是一段用 UTF-8 编码的文本。";
        try {
            // 推荐:显式使用 UTF-8 写入文件
            Files.writeString(Paths.get(filePath), content, StandardCharsets.UTF_8);
            System.out.println("文件写入成功!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

概念 描述 关键点
String 内部编码 UTF-16 String 对象在内存中始终使用 UTF-16 编码存储字符,这是不变的。
编码转换 发生在 I/O 操作时 String 需要变成 byte[](写入文件/网络)或 byte[] 需要变成 String(读取文件/网络)时,才需要编码/解码。
平台默认编码 file.encoding 决定 这是混乱的主要来源,在 Windows 上可能是 GBK,在 macOS/Linux 上可能是 UTF-8不可依赖!
最佳实践 始终显式指定 Charset 在所有 getBytes()new String(byte[], ...) 调用中,都使用 StandardCharsets.UTF_8 或其他明确的编码。避免使用无参版本!
分享:
扫描分享到社交APP
上一篇
下一篇