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 — 現代編碼的王者

在程式設計的工作中,你會接觸到使用不同編碼的檔案、網路協定、資料庫與網路服務。如果你不了解 UTF-8ISO-8859-1 有何不同,就很容易踩坑:你的程式可能在「我的電腦」上正常,卻會在法國同事的機器上或在雲端(預設系統不同)中壞掉。

今天我們要剖析在 Java 實務中最常見的三種編碼:

  • UTF-8 — 現代且通用的標準。
  • UTF-16 — Java 的內部字串格式,Windows 中也常見。
  • ISO-8859-1(Latin-1)— 舊但仍可見的西歐語系單位元組編碼。

讓我們更深入地認識它們!

這是什麼?

UTF-8(Unicode Transformation Format, 8-bit)是一種能表示巨大 Unicode 字元集任意符號的編碼(包括西里爾字母、中文漢字、表情符號,甚至罕見的古文字)。而且它很聰明:對於最常用的符號(英文字母、數字、標點),只用 1 個位元組;其他符號則用 23,甚至 4 個位元組。

事實: 幾乎所有現代網站、多數 JSONXML 檔案,甚至 Java 原始碼,預設都使用 UTF-8

一個字元需要多少位元組?

  • ASCII 字元(英文字母、數字、標點)— 1 個位元組。
  • 西里爾字母、帶變音符的歐洲字元2 個位元組。
  • 中日韓漢字3 個位元組。
  • 超稀有字符、表情符號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 可讓檔案大小最小。
  • 國際化:支援所有語言,包括表情符號。
  • 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 編碼字串,讀取時也同樣明確指定編碼。如此即可確保文字不失真地保存與還原,即使其中包含表情符號或多語系字元。

何時使用 UTF-8?

永遠——除非你有非常充足的理由選擇其他方案。這是所有現代應用程式的通用選擇。

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

UTF-16Unicode 家族中的另一種編碼。與 UTF-8 不同,它通常為每個字元使用 2 個位元組。Java 在 JVM 內部就是這樣存放字串(String)與字元(char)。但有個細節:某些字元(例如罕見漢字或表情符號)需要 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 個位元組。
  • 代理對:部分字元(例如表情符號)需要 4 個位元組。
  • Byte Order Mark (BOM):檔案開頭常加入指示位元組順序的標頭(LE — little endian,BE — big endian)。Java 通常能自動辨識 BOM
  • 與 ASCII 不相容:若你把 UTF-16 檔案用預期 ASCIIUTF-8 的編輯器開啟,會看到一堆 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); // 一切都能正確讀取!

        // 試著寫入非 Latin-1 字元(例如中文)
        String cyrillic = "你好,世界!";
        try {
            Files.write(Paths.get("badlatin1.txt"), cyrillic.getBytes(StandardCharsets.ISO_8859_1));
        } catch (Exception e) {
            System.out.println("錯誤:無法將非 Latin-1 字元寫入 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 與奇怪符號,因為位元組被錯誤解讀。

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION