CodeGym /课程 /JAVA 25 SELF /什么是编码,为什么需要它

什么是编码,为什么需要它

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

1. 编码的概念(encoding)

让我们从最重要的问题开始:什么是编码?

想象你来到一场国际会议。每个人都说不同的语言,但大家都想互相理解。于是需要一个翻译,他知道英文的 "hello",在中文里是 "你好",在西班牙语里是 "hola"。在计算机世界里,编码就像这个“翻译”。

编码——把字符表示为字节的一种方式

计算机很“朴素”:它只懂 0 和 1,也就是比特和字节。而人类希望看到字母、数字、表情符号,甚至——天哪!——中文汉字。为了让计算机能够“记录”字符,我们需要约定:每个字符对应哪一组字节。

编码(encoding)是一组规则:把字符(字母、数字、标点、表情符号等)转换为用于存储和传输的字节;反过来,再把字节转换回用于显示的字符。

示例:不同编码中的西里尔字母 'A (U+0410)'

  • UTF-8 编码中,西里尔字母 'A (U+0410)' 用两个字节表示:0xD0 0x90
  • Windows-1251 中,同一个字母用一个字节表示:0xC0
  • 而拉丁字母 'A' 在几乎所有常见编码中都是 0x41

如果用错误的编码读取文件,字符就会变成“乱码”(奇怪的符号或问号)。

2. 为什么需要编码

为什么不能直接把字母原样存起来?

因为计算机只理解数字(0 和 1)。哪个数字对应哪个字母——这正是编码的意义。

示例:“你好”写入磁盘

当你把单词 "你好" 写入文件时,对计算机来说这只是一串字节。如何解释这些字节——取决于所用的编码。

  • 如果文件以 UTF-8 保存,那么其中的俄文首字母(U+041F)是两个字节,下一字母(U+0440)也是两个字节,依此类推。
  • 如果以 Windows-1251 保存,则每个字母都是一个字节,但字节值不同。

哪些场景需要编码?

  • 写文本到文件:以便之后能正确读取字节。
  • 从文件读取文本:把字节还原为字母。
  • 通过网络发送文本(例如 HTTP、e‑mail)。
  • 使用数据库:也需要明确文本以何种编码存储。

如果不指定编码……

这就像打开一段你不熟悉语言的文字并试图阅读。如果你知道它是什么语言,理解的概率会大大提高;反之,要么看不懂,要么得到一堆天书。

3. 没有正确编码会带来哪些问题

乱码与数据丢失

初学者(不止初学者)最常见的吐槽:“为什么我期望看到 "你好",却看到了 "Hello (corrupted)",或者干脆全是问号?”

当文件用一种编码写入,却用另一种编码读取时就会发生这种情况。比如,文件在旧版 Windows 上以 Windows-1251 创建,而你在默认使用 UTF-8 的 Linux 上打开;或反过来。

示例

  • Windows-1251 写入文件:首个俄文字母(U+041F)的字节是 0xCF
  • UTF-8 中打开:程序期望西里尔字母是两个字节,却只读到一个,结果就出错了。

数据丢失

如果写入时所选编码不支持某个字符(例如试图在 ASCII 中保存表情符号 emoji),该字符会消失或被问号替换。凡是“塞不进”编码的内容都会丢失。

文件交换中的问题

用一种编码写入的文件,在默认使用另一种编码的计算机上可能显示不正确。Windows 与 Linux 之间交换文件、或打开旧文件时,常常会遇到这种情况。

4. Java 中的编码:内部与外部表示

JVM 内部:始终是 Unicode(UTF-16

在 Java 中,字符串(String)在程序内部始终以 Unicode(更准确地说是 UTF-16)存储。这意味着你可以放心地把任何语言的字符串赋值给变量,Java 都能“消化”。

String hello = "你好,世界! 😀";

在 JVM 内存中,这段文本以一组 16 位数(char)保存,每个字符在 Unicode 表中都有自己的编码点。

有趣的事实
在 Java 中,char 是 16 位(2 字节)。但某些字符(例如冷僻汉字或表情符号)需要两个 char——这就是“代理对”(surrogate pair)。

输入/输出:编码很重要!

当你把字符串读入写出到外部世界(文件、网络)时,Java 需要把内部表示(UTF-16)转换为字节序列。这时候就需要编码了。

  • 如果你没有显式指定编码,Java 会使用系统默认编码(在俄文 Windows 上可能是 Windows-1251,在 Linux 上是 UTF-8)。
  • 这很危险:在另一台计算机上结果可能不同。

示例:在未指定编码的情况下进行读写

// 不佳的做法!未指定编码。
FileReader reader = new FileReader("data.txt");
FileWriter writer = new FileWriter("data.txt");

在这种情况下,Java 使用系统编码。如果文件是在其他系统上写的——你就会得到“乱码”。

良好做法:始终显式指定编码

// 做得好!显式指定了编码。
BufferedReader reader = new BufferedReader(
    new InputStreamReader(new FileInputStream("data.txt"), StandardCharsets.UTF_8));
BufferedWriter writer = new BufferedWriter(
    new OutputStreamWriter(new FileOutputStream("data.txt"), StandardCharsets.UTF_8));

5. 简要示意:处理编码时发生了什么

示意图:字符串从文件到程序再回到文件的路径

[磁盘上的文件(字节,编码 X)]
        |
        V
[Java 读取字节,并借助编码 X 转换为 String(UTF-16)]
        |
        V
[你在程序中处理字符串]
        |
        V
[Java 使用编码 Y 将 String 写成字节]
        |
        V
[磁盘上的文件(字节,编码 Y)]

如果 XY 相同——一切正常;如果不同——就可能出问题。

6. 编码的简史(供好奇者)

ASCII

ASCII 是最早的编码之一:每个字符 1 个字节,只包含英文字母、数字和基础标点。其他任何字母表——都不包含在内。

Windows-1251ISO-8859-1 等“老牌编码”

这些是面向不同字符集的一字节编码:有的用于西里尔字母、有的用于拉丁字母、希腊字母等。各自为政,结果就是一片混乱。

UnicodeUTF 家族

  • Unicode——覆盖全世界字符的全局编码表。
  • UTF-8UTF-16UTF-32——将 Unicode 字符表示为字节的不同方式。
  • UTF-8 已成为 Web、文件与系统间交换的事实标准。

7. 实践:编码如何影响文件操作

我们来看一个用不同编码写入与读取字符串的小例子。

示例:以不同编码写入与读取

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

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

        // 以 UTF-8 写入文件
        try (Writer writer = new OutputStreamWriter(
                new FileOutputStream("utf8.txt"), StandardCharsets.UTF_8)) {
            writer.write(text);
        }

        // 现在尝试用错误的编码读取它
        try (Reader reader = new InputStreamReader(
                new FileInputStream("utf8.txt"), Charset.forName("Windows-1251"))) {
            int c;
            while ((c = reader.read()) != -1) {
                System.out.print((char) c);
            }
        }
        // 屏幕上将是一堆乱码!
    }
}

结论:如果编码不一致——文本就会被扭曲。

8. 编码与其他系统的集成

在真实项目中,文件经常在不同程序之间交换,这些程序可能用不同语言编写、运行在不同操作系统上。每一方都有可能期望自己的编码。如果不事先约定好——就会得到“乱码”和难以排查的 bug。典型场景:数据库以 UTF-8 存储文本,而程序把源文件当作 Windows-1251 读取并写入数据库——字符被扭曲是必然的。

9. 处理编码时的常见错误

错误 1:读/写文件时未指定编码。
结果就是“只在我电脑上好用”,而同事那里就是“乱码”。

错误 2:使用过时的构造器(如 FileReaderFileWriter)。
它们总是使用系统默认编码——是新手陷阱。

错误 3:源文件的编码不正确。
如果文件用一种编码写入、却用另一种编码读取,部分字符会被扭曲或替换成问号。

错误 4:在编码之间转换时丢失字符。
如果目标编码不支持所有字符(例如用 ASCII 取代 UTF-8),部分文本会直接消失。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION