整理 Windows 的字元編碼與換行符 - Shift_JIS / UTF-8 / UTF-16、亂碼、CRLF / LF,為何混亂

· · Windows, 字元編碼, 亂碼, 換行符, UTF-8, CP932, PowerShell, Unicode

Windows 的文字周邊的諮詢中相當頻繁以下話題一起混在。

  • Shift_JISUTF-8 有什麼不同
  • 為何發生亂碼
  • CRLFLF 有什麼不同
  • 明明已用 UTF-8,為何有讀不了的情況
  • 為何同一個 file 在 editor、console、Excel、Git 看起來不同

這個話題不是因日文或中文難而發生。大部分是因 把同一位元組序列以不同前提讀把誤讀的內容原樣儲存

再加上 Windows 上 Unicode 的世界與 code page 的世界至今共存。在那上面再疊 BOM、換行符、editor 的自動判定、console 的 code page、Git 的換行轉換,話題看起來複雜。

本文以實務向整理 Windows 常混在一起的 Shift_JIS / UTF-8 / UTF-16、為何發生亂碼、CRLFLF 的差異,以及為何這個話題容易混亂。

內容以 2026 年 4 月時點的 Microsoft Learn、PowerShell、Git、W3C / Unicode 系的公開資訊為前提。詳情請參照末尾的參考資料。

1. 先掌握的

先排結論,重要的是以下 7 點。

  • text file 不是 文字本身,而是由 bytes + 字元編碼 + 換行符 組成。有時還會加 BOM。
  • 亂碼發生在 把同一 bytes 以不同字元編碼 decode 時。
  • 換行問題發生在 字元編碼正確但行分隔前提偏離 時。
  • UnicodeUTF-8 不同義。Unicode 是字元集合的話,UTF-8UTF-16 是 encoding 的話。
  • Windows 上被稱為「Shift_JIS」的,實務上意識為 CP932 / Windows 系的日文 code page 比較不易偏離對話。
  • 光說 改成 UTF-8 作為規格還不夠。要到決定 BOM 的有無換行符 才變成運營規則。
  • 混亂的根源不是 日文本身,而是 不同歷史的多個前提留在同一 Windows 上

簡言之,處理 Windows 的 text 時,出發點是分開想以下 4 個。

  1. 該 file 的 bytes 是什麼
  2. 以哪個 encoding 寫入
  3. 以哪個 encoding 讀取
  4. 換行是 CRLF / LF 哪個

光分開就會相當不易迷惘。

2. 拆解用語

2.1 Unicode / UTF-8 / UTF-16 / CP932 有什麼不同

先把用語拆解一次比較快。

用語 指什麼 常見的混淆
Unicode 用編號表示字元的框架 U+3042 () 以為與 UTF-8 相同
UTF-8 把 Unicode 變成 bytes 的 encoding E3 81 82 以為是 Unicode 本身
UTF-16LE 把 Unicode 變成 bytes 的 encoding 42 30 與 menu 上的 Unicode 混在
CP932 Windows 日文系的 legacy code page 82 A0 以為與 Shift_JIS 完全相同
CRLF / LF 行的分隔 bytes 0D 0A / 0A 以為是 encoding 的一種
BOM file 開頭的識別用 bytes EF BB BF 以為是 encoding 名本身

例如 這 1 個字元,bytes 每個 encoding 也不同。

字元: あ

UTF-8    : E3 81 82
CP932    : 82 A0
UTF-16LE : 42 30

重要的是 字元和 bytes 是不同物。應用程式在畫面上處理看起來是「字元」的東西,但儲存或通訊最終交換的是 bytes。事故通常發生在那個轉換的邊界。

2.2 Shift_JISCP932 如何思考

現場把日文 Windows 的 text file 統稱為「Shift_JIS」的情況相當多。對話上通,但實務上稍微粗略。

想準確意識 Windows 的日文 legacy text 的話,看作 CP932Windows 系的日文 code page 比較安全。

這裡粗略,以下對話會偏離。

  • 以 Shift_JIS 儲存,但對方想的是 Windows 側的 CP932
  • Linux / macOS 側當 shift_jis 處理,但 Windows 源 file 的一部分重現不合
  • 聽到 ANSI 儲存,但是哪個 code page 依環境而定

所以規格或調查備忘錄盡量如下寫比較安全。

  • 不是 Shift_JIS 而是 CP932
  • 不是 ANSI 而是 ACP (active code page) / 日文環境通常是 CP932
  • 不是 text 而是像 UTF-8 no BOM, LF 這樣具體化

2.3 ANSIUnicodeUTF-8N 用語的陷阱

Windows 周邊中用語標籤也是混亂的原因。

特別多的如以下。

  • ANSI 在 Windows 的 UI 或舊說明中出現,但不是 ASCII。多數情況指 該機器的 active code page
  • Unicode 在部分 editor 或工具中 menu 上的 Unicode 可能意味 UTF-16LE。聽到 以 Unicode 儲存 未必是 UTF-8
  • UTF-8N 在日文圈的 editor 常見,但這通常是為了區別 UTF-8 no BOM 的 UI 標籤。不是 encoding 的正式名稱。

也就是 同一用語每個工具意義會偏離 就是 Windows。這是最初大的混亂點。

3. 為何發生亂碼

3.1 亂碼的真面目

亂碼的真面目相當單純。

  1. 把字串以某 encoding 變成 bytes
  2. 把那個 bytes 以不同 encoding 還原為字串
  3. 前提不一致就變成不同字串

例如把 以 UTF-8 儲存,bytes 如下。

E3 81 82

以 UTF-8 讀就是 ,但以 CP932 側的前提讀看起來是 縺� 這類的不同字串。 這時壞的不是「日文」而是 decode 的前提

一行說亂碼的話如下。

把同一 bytes 當不同 encoding 讀。

3.2 顯示崩壞與資料損壞是不同的

這裡重要的是分開 還能還原的階段已經難以還原的階段

例如以下流程還有還原的可能。

  1. 把 UTF-8 的 file 以 CP932 打開
  2. 畫面上看起來是 縺�
  3. 還沒儲存

這階段原本的 bytes 還是 UTF-8 留著。以正確 encoding 重新開可能還原。

危險的是以下。

  1. 把 UTF-8 的 file 誤讀為 CP932
  2. 把看起來壞的內容原樣儲存
  3. 原本的 UTF-8 bytes 消失

到這裡 不是顯示崩壞而是資料損壞

實務上不要以「亂碼」這 1 句結束,至少要分開以下。

  • bytes 本身還正確嗎
  • 誤讀的內容是否已再儲存

3.3 掉到無法表示的字元就回不來

另一個危險是把 Unicode 字串掉到 CP932 這類狹窄 code page 時。

這時若對方不存在的字元包含在內,會發生以下之一。

  • 被替換為 ?
  • 放入替換字元
  • 變成轉換錯誤
  • 被靠到不同的相近字元

例如一部分 emoji 或擴展漢字無法直接掉到 CP932。 這個事故不是以「能否讀」,而是以 來回轉換能否回原 來思考。

一旦丟失的資訊,之後知道正確 encoding 也無法還原。

4. 換行字元的差異是什麼

4.1 CRLF / LF / CR

換行也是 bytes。

  • CR = carriage return = 0D
  • LF = line feed = 0A
  • Windows 的 text file 傳統上是 CRLF (0D 0A)
  • Linux / Unix 系一般是 LF (0A)
  • CR 單獨在舊 Mac 系等舊脈絡中出現

表格如下。

換行 bytes 主要脈絡
CRLF 0D 0A Windows 的傳統 text file、legacy 工具
LF 0A Linux / macOS / 多數開發工具
CR 0D 相當舊的 legacy data

4.2 換行與字元編碼是不同問題

這裡相當重要。

換行符與 encoding 是不同問題

同一個 UTF-8 的 file 換行也能是 CRLFLF。 例如 A、換行、B 這樣的內容 bytes 會如下變化。

UTF-8 + LF   : 41 0A 42
UTF-8 + CRLF : 41 0D 0A 42

也就是,

  • UTF-8 但只換行不同
  • CP932 但換行是 LF
  • UTF-16LE 但換行是 CRLF

普通可能。

所以 改成 UTF-8 了但還不一樣 時,實際可能 不是 encoding 而是只換行偏離

4.3 \n 和 file 上的換行 bytes 未必相同

程式設計師視角中樸素容易混亂的是這裡。

source code 中寫 \n 不代表 file 上一定只出 0A。 在語言或執行環境、I/O API 的 text mode 中,在 Windows 上 \n 可能被轉換為 CRLF

也就是以下可能偏離。

  • 原始碼上的換行表現
  • 執行時的字串
  • file 上儲存的 bytes
  • editor 上可見的換行

因此「我以為寫了 LF,但 file 是 CRLF」的事故會發生。

最近的 editor 一般能處理 LF 單獨,但周邊工具、legacy 應用程式、業務運營還殘留 CRLF 前提。 所以換行問題不是「舊話」,現在也在實務中普通出現。

5. 為何 Windows 特別容易混亂

5.1 Unicode 和 legacy code page 共存

Windows 最麻煩的最大理由是這個。

Windows 上,

  • 使用 Unicode 的路
  • 以 code page 為基礎處理的路

兩者都留著。

較新的應用程式、web、cross-platform 系資產容易靠向 UTF-8,另一方面舊 CSV、TXT、日誌、Excel 周邊、業務系統連動仍保留 CP932。 此外一部分輸出或 API 周邊也普通地會出 UTF-16LE。

也就是說,1 台 Windows 中多個 text 文化同居

5.2 標籤未對齊

增加混亂的比起技術本身更是標籤的偏離。

  • Shift_JIS,實態是 CP932
  • ANSI,實態是 active code page
  • Unicode,實態是 UTF-16LE
  • UTF-8,實際上 BOM 有無未確定
  • 出現 UTF-8N 這類 editor 獨自標籤

這裡曖昧,對話看起來通,但實體未對齊。

5.3 只有 ASCII 問題會隱藏

這也大。

UTF-8 與 ASCII 範圍相容,所以只有英數字和符號的 file 即使前提錯也可能「好像能讀」。 CP932 側 ASCII 相當範圍外觀也不易壞,問題不表面化。

結果如下狀態。

  • 只英文的 config 看起來沒問題
  • 放入 1 行日文的瞬間壞
  • 一直潛藏的問題在運營中才首次發火

所以字元編碼事故容易看起來「昨天還動,今天突然壞」。 實際上 以前就有地雷,只是非 ASCII 字元放入的瞬間看見 的情況多。

5.4 file 內容、file 名、console、source file 是不同層

Windows 中把以下合稱為「字元編碼」會迷。

  • file 名 / path
  • file 的內容
  • console 的顯示
  • source code file 本身的 encoding
  • 執行時的字串形式
  • clipboard 或 GUI 部件上的顯示

例如日文 file 名普通可見,但 file 內容可能以 CP932 儲存。 反之 file 本身是 UTF-8,但 console 的 code page 不合,只有顯示壞。

chcp 65001 這類操作基本上對 console 側的前提有效,不是變既有 file 的 bytes 的話。

此外 source code file 是 UTF-8,執行時寫出的 log file 未必是 UTF-8。 在談哪層的 encoding 每次都需要分開。

另外日文 Windows 中 \ 看起來像日元符號,這也容易與字元編碼的話混在。 但多數情況這是 顯示字型或 glyph 的問題,path 分隔或轉義的意義沒變。

5.5 BOM 和換行符也在不同軸發揮作用

光說 UTF-8 還決定一半。

實務上以下也有效。

  • 有無 BOM
  • 換行是 CRLF 還是 LF

例如同樣 UTF-8,

  • 有 BOM 能讀的 Windows 工具存在
  • 有 BOM 會讓首欄加多餘字元的 Unix 系處理存在
  • 只有 LF 的 legacy 側難處理的工具存在
  • CRLF 會讓 shell script 或 diff 變粗的情況

這樣 就算 encoding 合了還會出事

5.6 工具擅自改

更麻煩的是手邊工具隱性改。

  • editor auto-detect
  • save 時加 / 去 BOM
  • Git 轉換 CRLF / LF
  • shell 或 command 以預設 encoding 儲存
  • CSV export 用預料外的 code page
  • 不同 version 的 PowerShell 或 tool 預設不同

也就是作業者沒明示,某層擅自加前提 就是 Windows 實務。

這就是「我什麼都沒改卻壞」的真面目。 實際上不是人,是 工具的預設值 改的情況不少。

6. 常見事故模式

把典型事故列表如下。

場面 實際偏離的 典型症狀
legacy Windows tool 把 UTF-8 no BOM 的設定 file 當作 ANSI / CP932 decode 前提 只日文亂碼
把 CP932 的 CSV 傳給以 UTF-8 為前提的處理系 decode 前提 、decode error、意義不明的日文
把 UTF-16LE 的 log 傳給 Unix 系 text tool encoding 前提 混入 NUL byte,看起來像二進位
LF 的 source file 在別環境轉換為 CRLF 換行前提 巨大行末差異、script 的缺陷
把誤讀的內容原樣儲存 bytes 本身變成別物 無法還原的資料損壞
規格只寫 輸出 CSV interface 未定義 Excel 能讀但別工具壞
只定 統一為 UTF-8 BOM / 換行未定義 只部分 tool 失敗

特別危險的是 看到顯示崩壞後原樣儲存確定事故 的模式。

7. 實務上減少事故的規則

從這裡開始是以運營規則決定什麼能減少事故。

7.1 決定新 text 的基本線

新 file 中先以 UTF-8 為第 1 候補是妥當的。 但光這樣不夠。

至少決定以下比較安全。

  • UTF-8 with BOM 還是 UTF-8 no BOM
  • 換行是 CRLF 還是 LF
  • 誰讀的 file
  • 是否需要 legacy Windows 工具相容
  • Linux / macOS / CI / container 也讀嗎

例如以 cross-platform 為前提的 source code 或 config,UTF-8 no BOM + LF 容易是第 1 候補。 另一方面,需要配合 Windows 的舊工具或既有運營的話,UTF-8 with BOMCP932 + CRLF 還需要的情況也有。

重要的是比起「什麼是對的」的通論,以 和誰交換 來決定。

7.2 既有 legacy file 不要擅自改

既有 file 是 CP932 的話,日常的小修正順便不要 UTF-8 化比較安全。

安全的是以下運營。

  • 既有 file 維持原 encoding / BOM / 換行
  • encoding 轉換當成別的遷移任務分開
  • 確認轉換對象和下游 consumer 後一併轉換

亂碼事故在善意的「順便現代化」下發生相當多。

7.3 把 encoding 和換行當成 interface 的一部分處理

CSV、TXT、log、設定 file、簡易 protocol 不只是內容,text 格式本身就是 interface

規格至少寫以下比較安全。

  • encoding
  • BOM 的有無
  • newline style
  • header 的有無
  • quote / delimiter 的規格
  • 用哪個工具驗證過

例如光 CSV 這 3 個字不夠。 到寫 UTF-8 with BOM, CRLF, comma delimiter, 有 header 才讓對話不易偏離。

7.4 在讀寫的邊界明示

程式碼側也不要靠隱性預設值比較安全。

  • file read / write 時明示 encoding
  • process 間的 text 傳遞也意識 encoding
  • export / import 處理也把換行作為規格固定
  • 不要把 shell 的粗略 redirect 當正式路徑

特別 Windows「能儲存」和「能以正確 bytes 儲存」不同。

7.5 Git 和 editor 的規則也共享

Git 不是自動修正 encoding 的工具。 另一方面 line endings 可能會轉換。

所以以 repository 為單位決定以下比較安全。

  • source code 以 LF 為基本嗎
  • Windows 專用 text 允許 CRLF
  • .gitattributes 如何固定
  • 如何共享 editor 設定

分開思考 encoding 和 line endings 很重要。 Git 能對齊換行,encoding 事故仍留。

7.6 不要停在「亂碼了」,說清楚哪裡偏離

現場中以下的改說法相當有效。

  • 壞說法: 亂碼了
  • 好說法: 看起來 UTF-8 no BOM 的 file 被以 CP932 前提打開
  • 壞說法: 行末怪怪的
  • 好說法: LF 的 file 被轉為 CRLF 導致差異增加

光能說出哪裡偏離,調查速度就相當不同。

8. 亂碼・行末差異的調查用這 5 問推進

調查迷惘時回到以下 5 問最快。

  1. 現在這個 file 的 bytes 是什麼
    • UTF-8 嗎
    • UTF-8 with BOM 嗎
    • CP932 嗎
    • UTF-16LE 嗎
  2. 最初誰以哪種前提寫
    • editor
    • legacy app
    • Excel export
    • shell / script
    • batch / middleware
  3. 現在誰以哪種前提讀
    • editor 的 auto-detect
    • console 的 code page
    • library 的 default encoding
    • import 側的規格
  4. BOM 和換行是什麼
    • 有 / 無 BOM
    • CRLF / LF
  5. 誤讀的內容是否已儲存
    • 還只是顯示嗎
    • 已再儲存 bytes 消失嗎

這 5 問填完,通常原因會浮現。

9. 總結

Windows 的字元編碼與換行符看起來複雜,不是因日文本身難。 是因 bytes、encoding、BOM、newline、tool 的預設值 分別存在,加上 Windows 上新舊 text 文化同居。

特別想記住以下。

  • 亂碼是把同一 bytes 當不同 encoding 讀的結果
  • 換行問題與 encoding 是不同軸的話
  • Shift_JISCP932ANSIUnicode 這些詞不要過度相信
  • 光說 改成 UTF-8 不夠,還需 BOM 和換行
  • 顯示崩壞與已再儲存的資料損壞分開思考
  • 規格不寫 text,要像 UTF-8 no BOM, LF 這樣寫

簡言之,處理 Windows 的 text 時不是「字串的話」,而是 如何對齊 bytes 的約定 的話比較實務。

10. 相關文章

11. 參考資料

  1. Microsoft Learn, Code Page Identifiers - Win32 apps https://learn.microsoft.com/en-us/windows/win32/intl/code-page-identifiers
  2. Microsoft Learn, about_Character_Encoding - PowerShell https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_character_encoding?view=powershell-7.6
  3. Microsoft Learn, Understanding file encoding in VS Code and PowerShell https://learn.microsoft.com/en-us/powershell/scripting/dev-cross-plat/vscode/understanding-file-encoding?view=powershell-7.6
  4. W3C Internationalization, Character encodings: Essential concepts https://www.w3.org/International/articles/definitions-characters/
  5. Git documentation, gitattributes https://git-scm.com/docs/gitattributes
  6. Git documentation, git-config https://git-scm.com/docs/git-config

共用相同標籤的最新文章。能以相近的主題延伸理解。

與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。

本文連結到以下服務頁面,歡迎從最接近的入口查看。

作者檔案

本文作者的個人檔案頁面。

Go Komura

小村軟體有限公司 代表

以 Windows 軟體開發、技術諮詢與故障調查為中心,在難以重現的故障調查與既有資產仍在運作的專案上具有優勢。

回到部落格一覽