Windows 的文字编码与换行符 - 乱码与 CRLF/LF 的基础

· · Windows, 文字编码, 乱码, 换行符, UTF-8, CP932, PowerShell, Unicode

在 Windows 文本相关的咨询中,经常会一下子把下面这些话题混在一起。

  • Shift_JISUTF-8 有什么区别
  • 为什么会出现乱码
  • CRLFLF 有什么区别
  • 已经改成 UTF-8 了,为什么还是读不了
  • 为什么同一个 file,在 editor、console、Excel、Git 里看到的样子却不一样

这些问题的发生,并不是因为日文难处理。大多数情况下,原因是 用不同的前提读取了同一组 bytes,或者 把读错的内容原样保存了下来

而且在 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. 先要把握的要点
  2. 拆解术语
    • 2.1 Unicode / UTF-8 / UTF-16 / CP932 有什么区别
    • 2.2 如何理解 Shift_JISCP932
    • 2.3 ANSIUnicodeUTF-8N 这些说法的陷阱
  3. 为什么会出现乱码
    • 3.1 乱码的本质
    • 3.2 显示错乱与数据损坏是两件事
    • 3.3 落到无法表示的字符上就回不去了
  4. 换行符的区别
    • 4.1 CRLF / LF / CR
    • 4.2 换行符是与文字编码不同的问题
    • 4.3 \n 与 file 上的换行 bytes 不一定相同
  5. 为什么在 Windows 上特别容易混乱
    • 5.1 Unicode 与 legacy code page 并存
    • 5.2 标签不统一
    • 5.3 只有 ASCII 时问题会被隐藏
    • 5.4 file 内容、file 名、console、source file 是不同的层
    • 5.5 BOM 与换行符也是独立的轴
    • 5.6 工具会自作主张地改变
  6. 常见的事故模式
  7. 在实务中减少事故的规则
  8. 乱码与行末差异的调查用这 5 个问题来推进
  9. 总结
  10. 相关文章
  11. 参考资料

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 名称本身

比如同样是 这一个字,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,最好当作 CP932,或者 Windows 系日文 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 周边,词语的标签本身也是造成混乱的原因。

特别容易混淆的是以下这 3 个:

  • 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 的前提

如果用一句话来概括乱码,就是这样:

用不同的 encoding 读取了同一组 bytes。

3.2 显示错乱与数据损坏是两件事

这里重要的是,把 还能挽回的阶段已经难以挽回的阶段 区分开。

比如下面这个流程,还有挽回的可能:

  1. 把 UTF-8 的 file 当作 CP932 打开
  2. 画面上看起来像 縺�
  3. 还没有保存

在这个阶段,原本的 bytes 仍然是 UTF-8 保持不变。用正确的 encoding 重新打开,有时是能还原的。

危险的是下面这个流程:

  1. 把 UTF-8 的 file 误读为 CP932
  2. 把看起来已经损坏的内容直接保存
  3. 原本的 UTF-8 bytes 就丢失了

到了这一步,就 不再是显示错乱,而是数据损坏 了。

在实务中,不要用「乱码了」这一句话就概括过去,至少要把下面两点分开来看:

  • bytes 本身是否还是正确的
  • 误读后的内容是否已经被重新保存

3.3 落到无法表示的字符上就回不去了

另一个危险的情况,是把 Unicode 字符串落到 CP932 这类范围较窄的 code page 时。

此时,如果其中包含了对方不存在的字符,就会出现以下情况之一:

  • 被替换成 ?
  • 插入替换字符
  • 变成转换错误
  • 被归并到别的相近字符

比如,部分表情符号或扩展汉字无法直接落到 CP932 中。 这类事故不应该用「能不能读」来判断,而应该用 往返转换后能不能还原 来考虑。

一旦丢失的信息,即便之后知道了正确的 encoding,也无法还原。

4. 换行符的区别

4.1 CRLF / LF / CR

换行符也是 bytes。

  • CR = carriage return = 0D
  • LF = line feed = 0A
  • Windows 的 text file 传统上使用 CRLF0D 0A
  • Linux / Unix 系一般使用 LF0A
  • 单独的 CR 有时会出现在旧 Mac 系等古老的语境中

列成表格就是这样:

换行 bytes 主要语境
CRLF 0D 0A Windows 传统的 text file、legacy 工具
LF 0A Linux / macOS / 许多开发工具
CR 0D 相当古老的 legacy data

4.2 换行符是与文字编码不同的问题

这一点相当重要。

换行符是与 encoding 不同的问题

同一个 UTF-8 的 file,换行既可能是 CRLF,也可能是 LF。 比如内容是 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。 在某些语言、运行时(runtime)或 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 也经常出现。

也就是说,在同一台 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 看起来没问题
  • 一旦加入一行日文就崩坏
  • 一直潜伏着的问题,在运营过程中才第一次爆发

所以文字编码事故很容易看起来像是「昨天还能用,今天突然就坏了」。 实际上,大多是 地雷早就埋在那里了,只是非 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,

  • 有些 Windows 工具要有 BOM 才能读
  • 有些 Unix 系处理,一旦有 BOM,首列就会多出一个多余的字符
  • 只有 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 定为第一候选是妥当的。 不过,仅此还是不够。

至少把下面这些也确定下来会比较安全:

  • 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 容易成为第一候选。 另一方面,如果需要配合 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 不要止步于「乱码了」,要说清楚哪里出现了偏差

在现场,这种换个说法的方式往往很管用。

  • 不好的说法:「乱码了」
  • 好的说法:「看起来是按 CP932 的前提打开了 UTF-8 no BOM 的 file」
  • 不好的说法:「行末不对」
  • 好的说法:「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 文化同居一室。

特别想记住的是以下 6 点:

  • 乱码是把同一组 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

共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。

常见问题

汇总了咨询这一主题时常见的问题。

为什么会出现乱码?
原因是用和写入时不同的文字编码去读取了同一组 bytes。比如「あ」用 UTF-8 保存后会变成 E3 81 82 这样的 bytes,但如果按 CP932 的前提去读取,就会看成「縺」这样完全不同的字符串。这时候出问题的不是日文本身,而是 decode 的前提。不过,如果把误读后的内容直接重新保存,原本的 bytes 就会丢失,从显示错乱变成数据损坏,所以在保存前用正确的 encoding 重新打开是很重要的。
CRLF 和 LF 有什么区别?
区别在于用于分行的 bytes 不同。CRLF 是 0D 0A 两个字节,是 Windows 文本文件传统上使用的方式;LF 是 0A 一个字节,在 Linux / macOS 及许多开发工具中很常见。重要的是,换行符和文字编码是两个不同的问题。同一个 UTF-8 文件的换行既可能是 CRLF,也可能是 LF,所以出现「已经改成 UTF-8 了却还是不对」的情况时,有可能不是 encoding 的问题,而只是换行符没对上。
Shift_JIS 和 CP932 是同一种东西吗?
在口头交流中说得通,但在实务中区分开会更安全。如果要准确地表达日文 Windows 上的 legacy 文本,应该理解为 CP932,或者 Windows 系日文 code page。同样,Windows 中的「ANSI」大多数情况下指的是该机器的 active code page,编辑器菜单里的「Unicode」有时指的是 UTF-16LE。在规格书或调查笔记中,写成「UTF-8 no BOM, LF」这种具体的形式,会让沟通不容易出现偏差。
文本文件的规格应该确定哪些内容?
只说「改成 UTF-8 了」是不够的,只有连同 BOM 的有无、换行符都确定下来,才算成为运营规则。对于 CSV、日志这类交接用的文件,最好把 encoding、BOM 有无、换行、有无表头、分隔符规格都写清楚。以跨平台为前提的源代码或配置文件,UTF-8 no BOM + LF 通常是首选;而如果需要兼容 Windows 的老旧工具,有时仍然需要 UTF-8 with BOM 或 CP932 + CRLF。

作者简介

本文作者的个人简介页面。

Go Komura

小村软件有限公司 代表

以 Windows 软件开发、技术咨询与故障排查为中心,擅长难以复现的故障调查,以及既有资产仍在运行的项目。

返回博客列表