CodeGym /課程 /JAVA 25 SELF /什麼是編碼,為什麼需要它

什麼是編碼,為什麼需要它

JAVA 25 SELF
等級 37 , 課堂 0
開放

1. 編碼(encoding)的概念

先從最重要的問題開始:什麼是編碼?

想像你抵達一場國際會議。每個人都說自己的語言,但大家都想彼此理解。這就需要一位翻譯,他知道英文的 "hello",在俄語中是 "privet",在西班牙語中是 "hola"。在電腦世界裡,編碼就像這位翻譯。

編碼是把字元表示成位元組的方式

電腦很單純:它只懂 0 和 1,也就是位元與位元組。但人類想要看到的是字母、數字、表情符號,甚至——天哪!——中文漢字。要讓電腦能「寫下」一個字元,就必須先約定每個字元對應哪一組位元組。

編碼(encoding)是一套規則,說明如何把字元(字母、數字、標點、表情符號等)轉換成位元組以便儲存與傳輸;反過來,如何把位元組還原成字元以便顯示。

範例:不同編碼中的字母 'A'

  • UTF-8 中,字母 'A'(西里爾字母,U+0410)會被編為兩個位元組:0xD0 0x90
  • Windows-1251 中,同一個字母會是 1 個位元組:0xC0
  • 而拉丁字母 'A' 在幾乎所有常見編碼中都是 0x41

如果用錯編碼讀取檔案,字元會變成「亂碼」(不明符號或問號)。

2. 為什麼需要編碼

為什麼不能直接把字母原樣存起來?

因為電腦只懂數字(0 和 1)。哪個數字對應哪個字母——這正是編碼的本質。

範例:「Privet」在磁碟上

當你把單字 "Privet" 寫入檔案時,對電腦來說它只是位元組序列。如何解讀這些位元組,取決於編碼。

  • 如果檔案以 UTF-8 儲存,像「P」(西里爾字母 Pe,U+041F)會是兩個位元組,「r」(西里爾字母 er,U+0440)也是兩個,依此類推。
  • 如果是 Windows-1251,每個字母都是一個位元組,但位元組的值不同。

在哪些地方需要編碼?

  • 把文字寫入檔案時:才能之後正確讀回位元組。
  • 從檔案讀取文字時:才能把位元組還原成字母。
  • 透過網路傳送文字時(例如 HTTP、e‑mail)。
  • 使用資料庫時:也必須知道文字以哪種編碼儲存。

如果沒有指定編碼……

就像打開一段不熟悉語言的文字,硬要讀。若你知道那是什麼語言,理解的機會會大些;若不知道——好的情況是什麼也看不懂,壞的情況就是一團亂碼。

3. 沒有正確編碼會遇到的問題

亂碼與資料遺失

新手(不只新手)最常見的抱怨是:「為什麼我應該看到 "Privet",卻看到 "Привет",或乾脆全是問號?」

這發生在檔案用某種編碼寫入,卻用另一種編碼讀取時。比如檔案是在舊版 Windows 上以 Windows-1251 建立,而你在 Linux(預設常為 UTF-8)上開啟;反過來也一樣。

範例

  • Windows-1251 寫入檔案:對於「P」(西里爾字母 Pe,U+041F)而言,位元組是 0xCF
  • UTF-8 中開啟:程式預期西里爾字母是兩個位元組,卻只收到一個,一切就壞了。

資料遺失

若寫入時選用的編碼不支援某個字元(例如嘗試把表情符號存成 ASCII),它會消失或被替換為問號。任何「塞不進」目標編碼的內容都會流失。

交換檔案時的問題

用一種編碼寫入的檔案,若在預設不同編碼的另一台電腦上開啟,可能會顯示不正確。這常見於 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 pairs)。

輸入/輸出時:編碼很重要!

當你讀取寫出字串到外部世界(檔案、網路)時,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. 編碼與與其他系統整合

在真實專案中,檔案常在不同程式之間交換,這些程式可能由不同語言撰寫、跑在不同作業系統上。每一方都可能期望不同的編碼。若不事先協調——就會得到「亂碼」與難以追查的錯誤。典型情況:資料庫以 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