CodeGym /课程 /JAVA 25 SELF /主要字符编码:UTF-8、UTF-16、ISO-8859-1

主要字符编码:UTF-8、UTF-16、ISO-8859-1

JAVA 25 SELF
第 37 级 , 课程 1
可用

1. UTF-8——现代编码的王者

在程序员的工作中,你会接触到文件、网络协议、数据库、Web 服务等,它们可能使用不同的编码。如果你不知道 UTF-8ISO-8859-1 的区别,就很容易踩坑:你的代码在“我这台电脑上”能跑,但在法国同事的电脑或默认编码不同的云环境中却会出错。

今天我们来解析在 Java 开发中实际会遇到的三种主要编码:

  • UTF-8——现代通用标准。
  • UTF-16——Java 中字符串的内部格式,并且在 Windows 中也很常见。
  • ISO-8859-1(Latin-1)——历史悠久但仍能见到的用于西欧语言的单字节编码。

我们来近距离了解一下它们吧!

它是什么?

UTF-8(Unicode Transformation Format, 8-bit)是一种能够表示 Unicode 超大字符集任意字符的编码(包括西里尔字母、中文汉字、Emoji,甚至冷门古文字)。它的巧妙之处在于:对于最常用的字符(英文字母、数字、标点),只用 1 个字节;其他字符使用 23,甚至 4 个字节。

事实: 所有现代网站、大多数 JSONXML 文件,甚至 Java 源码默认都使用 UTF-8

一个字符需要多少字节?

  • ASCII 字符(英文字母、数字、标点)——1 字节。
  • 西里尔字母、带变音符的欧洲字符——2 字节。
  • 中、日、韩表意文字——3 字节。
  • 特别罕见字符、Emoji——4 字节。

示例:

符号 Unicode 码位 UTF-8(字节)
A
U+0041
41
Ya(西里尔)
U+042F
D0 AF
U+20AC
E2 82 AC
😀
U+1F600
F0 9F 98 80

为什么 UTF-8 很棒?

  • 向后兼容 ASCII:如果文件只包含英文字符,它与 ASCII 完全一致。
  • 紧凑:英文短文本用 UTF-8 体积最小。
  • 国际化:支持全世界所有语言,包括 Emoji。
  • Web 标准HTMLCSSJSONXMLJavaScript——默认都是 UTF-8

Java 中的使用示例

import java.nio.charset.StandardCharsets;
import java.nio.file.*;

public class Utf8Demo {
    public static void main(String[] args) throws Exception {
        // 以 UTF-8 将字符串写入文件
        String text = "你好,世界! 😀";
        Files.write(Paths.get("utf8.txt"), text.getBytes(StandardCharsets.UTF_8));

        // 以 UTF-8 读取文件
        String read = Files.readString(Paths.get("utf8.txt"), StandardCharsets.UTF_8);
        System.out.println(read); // 输出:你好,世界! 😀
    }
}

这里我们在写入前显式按 UTF-8 对字符串编码,读取时也显式指定编码。这样即使包含 Emoji 或多语言字符,文本也能无损保存与还原。

何时使用 UTF-8?

始终使用,除非你有非常充分的理由选择其他编码。这是所有现代应用的通用之选。

2. UTF-16——Java 的内部格式

UTF-16Unicode 家族的另一种编码。与 UTF-8 不同,它通常对每个字符使用 2 个字节。Java 在 JVM 内部就是用这种方式存储字符串(String)和字符(char)。但有个细节:某些字符(如罕见汉字或 Emoji)需要 4 个字节(由两个“代理项对”组成)。

UTF-16 在哪里使用?

  • Java 内部:所有字符串和字符都是 UTF-16
  • Windows:许多系统文件在 Windows 中使用 UTF-16(例如 Notepad 默认保存为 UTF-16 LE)。
  • 一些网络协议和格式:例如,XML 可以是 UTF-16

示例:UTF-16 中的字符串是什么样

符号 Unicode 码位 UTF-16(字节) 说明
A
U+0041
00 41
2 字节
Ya(西里尔)
U+042F
04 2F
2 字节
U+20AC
20 AC
2 字节
😀
U+1F600
D8 3D DE 00
4 字节(代理项对)

Java 中的使用示例

import java.nio.charset.StandardCharsets;
import java.nio.file.*;

public class Utf16Demo {
    public static void main(String[] args) throws Exception {
        String text = "你好,世界! 😀";

        // 使用 UTF-16 编码将字符串写入文件
        Files.write(Paths.get("utf16.txt"), text.getBytes(StandardCharsets.UTF_16));

        // 指定 UTF-16 编码读取文件内容
        String read = Files.readString(Paths.get("utf16.txt"), StandardCharsets.UTF_16);
        System.out.println(read); // 正确输出原始字符串
    }
}

关键点:写入与读取必须使用相同的编码。如果以 UTF-16 写入,却按默认方式读取(例如 UTF-8),就会出现“乱码”。

UTF-16 的特点

  • 对大多数字符为定长2 字节。
  • 代理项对:部分字符(如 Emoji)需要 4 字节。
  • Byte Order Mark (BOM):文件开头常有一个特殊“标头”,指示字节序(LE——little endian,BE——big endian)。Java 通常能自动识别 BOM
  • 与 ASCII 不兼容:若在期望 ASCIIUTF-8 的编辑器中打开 UTF-16 文件,会看到大量 0 和奇怪字符。

何时使用 UTF-16?

  • 当你需要对接明确要求使用 UTF-16 的系统或文件(如与某些 Windows 程序集成)。
  • 对于普通应用和文件——使用 UTF-8 更好。

4. ISO-8859-1 (Latin-1)——来自过去的问候

ISO-8859-1,又称 Latin-1,是上世纪 80 年代诞生的单字节编码。它支持 256 个字符:英文字母、西欧带变音符的字母(é、ü、ç 等)、标点、特殊符号等。

事实: 该编码不包含西里尔字母、希腊语、阿拉伯语、中文等非拉丁字符。

ISO-8859-1 在哪里还能见到?

  • 旧文件和旧程序中(尤其在欧洲)。
  • 某些数据库和协议的“默认值”。
  • HTTP 头部(标准上未指定编码的文本数据被视为 ISO-8859-1,但实践中并不常见)。

示例:ISO-8859-1 中的字符串是什么样的

符号 Unicode 码位 ISO-8859-1(字节) 说明
A
U+0041
41
与 ASCII 相同
é
U+00E9
E9
法语
ü
U+00FC
FC
德语
Ya(西里尔)
U+042F
没有该字符!

Java 中的使用示例

import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.nio.charset.Charset;

public class Latin1Demo {
    public static void main(String[] args) throws Exception {
        String text = "Bonjour, ça va? Café!"; // 法语文本
        Files.write(Paths.get("latin1.txt"), text.getBytes(StandardCharsets.ISO_8859_1));

        // 以 ISO-8859-1 读取文件
        String read = Files.readString(Paths.get("latin1.txt"), StandardCharsets.ISO_8859_1);
        System.out.println(read); // 全部能正确读取!

        // 尝试写入西里尔字母
        String cyrillic = "你好,世界!";
        try {
            Files.write(Paths.get("badlatin1.txt"), cyrillic.getBytes(StandardCharsets.ISO_8859_1));
        } catch (Exception e) {
            System.out.println("错误:无法将西里尔字母写入 ISO-8859-1!");
        }
    }
}

ISO-8859-1(Latin-1)只支持西欧字符——例如法语、德语或西班牙语中的带重音字母。因此法语文本可以正常读写。

然而该编码不包含西里尔字母,一旦尝试写入,就会报错或丢失字符。这也是为什么多语言文本更适合使用 UTF-8UTF-16,它们覆盖的字符集合要大得多。

ISO-8859-1 的特点

  • 语言受限:仅支持西欧语言。
  • 1 字节/字符:体积小,但限制大。
  • 写入“外来”字符会产生乱码:如尝试保存西里尔字母或汉字,它们会变成问号或垃圾字符。

何时使用 ISO-8859-1?

  • 只有在与非常老旧的系统或数据库集成,并且它明确要求这种编码时。
  • 对现代场景——不推荐。

5. 有用的细节

编码对比表

编码 每个字符的字节数 支持的语言 与 ASCII 的兼容性 典型用途
UTF-8
1-4 全部 完全兼容 Web, Java, JSON, XML, Linux
UTF-16
2 或 4 全部 不兼容 Java 内部, Windows, XML
ISO-8859-1
1 西欧 完全兼容 旧程序, 数据库, HTTP

如何选择编码?

  • UTF-8——你在99% 的任务中的首选。兼容 ASCII、支持所有语言、对英文文本体积最小,是 Web 与 Java 的标准。
  • UTF-16——仅当规范要求或需要与 Windows 程序集成时使用。
  • ISO-8859-1——只为兼容老系统。切勿用于存储西里尔字母、希腊语、阿拉伯语等。

如何判断文件的编码?

  • 用能显示编码信息的编辑器打开文件(例如 Notepad++、VS Code)。
  • 如果看到“乱码”,尝试以其他编码打开。
  • 在 Linux 终端可以使用命令 file 文件名——它有时能提示编码。

如何在 Java 中指定编码?

使用包 java.nio.charset 中的类:

  • StandardCharsets.UTF_8
  • StandardCharsets.UTF_16
  • StandardCharsets.ISO_8859_1

或者通过编码名:

Charset windows1251 = Charset.forName("Windows-1251");
Charset utf8 = Charset.forName("UTF-8");

6. 实践:搞错编码会发生什么?

来做个小实验。先用 UTF-8 写入一段字符串,然后尝试按 ISO-8859-1 读取:

import java.nio.file.*;
import java.nio.charset.*;

public class EncodingMismatchDemo {
    public static void main(String[] args) throws Exception {
        String text = "你好,世界!"; // 西里尔字母

        // 以 UTF-8 写入
        Files.write(Paths.get("utf8demo.txt"), text.getBytes(StandardCharsets.UTF_8));

        // 按 ISO-8859-1 读取(错误示范!)
        String wrong = Files.readString(Paths.get("utf8demo.txt"), StandardCharsets.ISO_8859_1);
        System.out.println("错误读取: " + wrong);

        // 现在按正确方式读取
        String correct = Files.readString(Paths.get("utf8demo.txt"), StandardCharsets.UTF_8);
        System.out.println("正确读取: " + correct);
    }
}

结果:

  • 第一种情况会看到“乱码”——一堆怪异符号。
  • 第二种情况——正常的俄文文本。

7. 编码处理中的常见错误

错误 1:以错误的编码读取文件。 如果文件以 UTF-8 写入,而你按 ISO-8859-1Windows-1251 读取,那么所有非 ASCII 字符都会变成垃圾。这是经典剧情:“在我电脑上能跑,在同事那儿就不行”。

错误 2:尝试把“外来”字符写入单字节编码。 如果你试图把西里尔字母或汉字写入 ISO-8859-1,Java 会把它们替换为问号,或者直接抛出错误。数据会不可逆地丢失。

错误 3:依赖系统“默认”编码。 在 Java 的某些方法中(例如 new FileReader("file.txt"))未显式指定编码——将使用系统默认。在一台机器上可能是 UTF-8,在另一台是 Windows-1251,第三台甚至是非常奇怪的编码。因此请总是使用能显式指定编码的方法。

错误 4:把 UTF-16 文件当作 UTF-8 打开。 一个以 UTF-16 写入的文件,如果按 UTF-8 打开,会看到大量 0 和奇怪字符,因为字节被错误解释了。

评论 (1)
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION
ncksllpo 级别 44,Cherkasy,Ukraine
20 三月 2026
no