▲ Windows 메모장 인코딩에는 ANSI, 유니코드, 유니코드 빅 엔디안 및 UTF-8이 포함됩니다.
경고
이 글은 널리 사용되는 소프트웨어의 기술적 사실만을 설명할 뿐, 작성자가 해당 소프트웨어의 사용을 지지하거나 반대한다는 의미는 아닙니다.
사실 저자는 컴퓨터 프로그램 코드 작업을 위해 어떤 경우에도 Windows 메모장을 사용하지 말 것을 권장합니다.
이 문서는 64비트 Windows 7 중국어 간체 버전의 특정 인스턴스에서만 확인되었으며 참조용입니다. 동일하거나 다른 시스템에서 일관된 결과가 재현될 수 있다는 보장은 없습니다.
참고
이 문서에서는 유니코드의 인코딩과 바이트 직렬화를 엄격하게 구분합니다.
유니코드의 인코딩은 단순히 숫자(보통 16진수로 작성됨)를 사용하여 문자를 일대일로 표현하는 작업을 의미합니다. 이 숫자의 범위는 유니코드 표준에 의해서만 제한되며 컴퓨터와는 아무런 관련이 없습니다.
유니코드의 바이트 직렬화는 유니코드 표준 범위 내의 숫자를 컴퓨터 메모리에 쓸 수 있도록 N바이트로 나타내는 작업을 말합니다.
테스트 케이스
테스트 케이스는 "锟斤拷[줄바꿈]a[줄바꿈]"입니다. (锟斤拷는 일종의 신념입니다.)
모든 문자의 GBK 및 유니코드 인코딩은 다음과 같습니다:
- 锟GBK=
EFBF
Unicode=U+951F
EFBF
Unicode=U+951F
- 斤 GBK=
BDEF
Unicode=U+65A4
- 拷 GBK=
BFBD
Unicode=U+62F7
以下ASCII字符的GBK和Unicode编码与ASCII一致:
a=a=
다음 ASCII 문자의 GBK 및 유니코드 인코딩은 ASCII와 일치합니다:0x61
CR=0x0D
LF=0x0A
GBK=BDEF
유니코드=U+65A4
GBK=BFBD
유니코드=U+62F7
0x61
CR=0x0D
LF=0x0A(Windows의 개행 문자 하나는 CR+LF 두 문자를 차지합니다.)
ANSI중국어 간체 시스템에서 ANSI는 중화인민공화국의 국가 표준으로 정의된 GBK 인코딩입니다.[注A]
。可以看到这里的字节序是大端在先(big-endian)的。
但是不必特意强调“大端在先的GBK”——因为从GB2312开始,标准就规定了存储方式是大端在先的[注B]
。后来的GBK和GB18030-2000向下兼容。
ANSI的麻烦就是依赖系统——其他语言系统的ANSI就不是GBK了,打开GBK的文件必然乱码。并且GBK的字符集本身也太小。
(千万不要说“我只用中文”——少了Unicode那些符号,网上那些颜文字都打不出来)
Unicode系列
Windows Notepad所说的“Unicode”、“Unicode big endian”和UTF-8,全都是同样的Unicode编码的不同的字节序列化存储方法。
UTF-16 和 BOM
这里的Unicode指UTF-16[注C]
。UTF-16是极其简单粗暴的序列化方法——绝大多数的Unicode字符都在U+0000~U+FFFF的范围内[注D]
,那就每个字符用两个字节,把Unicode编码的原始值写盘。
注意ASCII字符也必须浪费一倍的空间存储高8位的0x00——因为如果把高8位的0略了,解析时就再也没有其他的依据去断字。
对于UTF-16就存在大端和小端的问题了——UTF-16并不规定字节的大端在前还是小端在前。但UTF-16并不包含表示字节序的信息,总不能人工看看哪个解析是不乱码的吧……
Unicode提供的解决方式是,把一个零宽无断字空格符(U+FEFF
ZERO WIDTH NO-BREAK SPACE)以UTF-16的方式序列化之后,塞到文件的最前边。这样UTF-16解析器读取文件的前两个字节,如果是FE FF
就是大端在前,FF FE
이 파일을 저장하기 위해 ANSI를 사용하는 Windows 메모장 결과는 다음과 같습니다.
EF BF BD EF BF BD 0D 0A 61 0D 0A ----- ----- ----- -- -- -- -- --
간단히 GBK 인코딩을 사용하여 모든 문자를 저장합니다. 가장 높은 비트가 1이 아니고 ASCII와 동등한 단일 바이트, 그렇지 않으면 더블 바이트입니다.
여기[참고 A]
에서 바이트 순서(Endian) 문제에 주의하세요. 여기서 바이트 순서가big-endian임을 알 수 있습니다.
하지만 "GBK with big endian first"를 강조할 필요는 없습니다. 왜냐하면 GB2312부터 표준에서는 저장 방법이 big endian 먼저[Note B]
라고 규정하고 있기 때문입니다. 나중에 GBK는 GB18030-2000과 역호환됩니다.
ANSI의 문제점은 시스템에 따라 다르다는 것입니다. 다른 언어 시스템의 ANSI는 GBK가 아니며 GBK에서 열린 파일은 필연적으로 왜곡됩니다. 그리고 GBK 자체의 문자 집합이 너무 작습니다.
("저는 중국어만 사용합니다"라고 말하지 마세요. 유니코드 기호가 없으면 인터넷의 이모티콘을 입력할 수 없습니다.)유니코드 시리즈Windows 메모장에서 "유니코드", "유니코드 빅 엔디안" 및 UTF-8이라고 부르는 것 모두 동일한 유니코드 인코딩
이지만 🎜바이트 직렬화 🎜 저장 방법이 다릅니다. 🎜🎜UTF-16 및 BOM🎜🎜유니코드는 여기서 UTF-16[Note C]
를 나타냅니다. UTF-16은 매우 간단하고 조잡한 직렬화 방법입니다. 대부분의 유니코드 문자는 U+0000~U+FFFF [참고 D]
범위에 있고 2바이트를 사용하여 유니코드의 원래 값을 씁니다. 디스크로 인코딩. 🎜🎜 또한 ASCII 문자는 0x00의 상위 8비트를 저장하기 위해 두 배의 공간을 낭비해야 한다는 점에 유의하세요. 0의 상위 8비트가 생략되면 구문 분석 중에 하이픈을 사용할 수 있는 다른 기반이 없기 때문입니다. 🎜🎜UTF-16의 경우 빅 엔디안과 리틀 엔디안의 문제가 있습니다. UTF-16은 바이트가 빅 엔디안인지 리틀 엔디안인지 먼저 지정하지 않습니다. 그런데 UTF-16에는 바이트 순서를 나타내는 정보가 없기 때문에 어떤 파싱이 왜곡되지 않았는지 수동으로 확인할 수는 없습니다... 유니코드가 제공하는 해결책은 🎜너비 하이픈이 아닌 공백 문자🎜()를 넣는 것입니다. U+FEFF ZERO WIDTH NO-Break Space)는 UTF-16으로 직렬화되어 파일 앞에 채워집니다. 이런 식으로 UTF-16 파서는 파일의 처음 2바이트를 읽습니다. FE FF
이면 큰 끝을 먼저 의미하고, FF FE
는 작은 끝을 의미합니다. 첫 번째. 🎜🎜이 안에 들어있는 것을 BOM(Byte Order Mark)이라고 합니다. 🎜🎜🎜다양한 상황에서 단어 제한을 깨기 위해 🎜너비 하이픈 없는 공백 문자🎜도 종종 유효한 문자로 사용된다는 점을 언급할 가치가 있습니다. SegmentFault의 Q&A 및 의견이 포함되어 있습니다. 🎜🎜🎜메모장의 "유니코드"와 "유니코드 빅엔디안"🎜🎜"유니코드"만 쓴다고 해서 저장방식이 완전하게 표현된 것은 아닙니다. 여기에는 🎜인코딩🎜만 포함되고 🎜바이트 직렬화🎜는 포함되지 않기 때문입니다. 🎜M$出现这种错误,我一点都不觉得奇怪。死记结论就可以了:Windows Notepad的“Unicode”就是UTF-16。
Windows Notepad使用“Unicode” = 小端在先的UTF-16,存储这个文件的结果如下:
FF FE 1F 95 A4 65 F7 62 0D 00 0A 00 61 00 0D 00 0A 00 -BOM- ----- ----- ----- ----- ----- ----- ----- ----- U+FEFF 951F 65A4 62F7 000D 000A 0061 000D 000A <p>Windows Notepad使用<strong>“Unicode big endian” = 大端在先的UTF-16</strong>,存储这个文件的结果如下:</p><pre class="brush:php;toolbar:false"> FE FF 95 1F 65 A4 62 F7 00 0D 00 0A 00 61 00 0D 00 0A -BOM- ----- ----- ----- ----- ----- ----- ----- ----- U+FEFF 951F 65A4 62F7 000D 000A 0061 000D 000A <h3>UTF-8</h3><p>UTF-8是一种用1~4个字节表示1个Unicode字符的<strong>变长的</strong>字节序列化方法。具体的实现细节看这篇文章。UTF-8的好处在于:</p><ol> <li>无论是IETF的推荐,还是实际业界的执行,UTF-8都是互联网的标准。</li> <li>向下兼容,ASCII字符UTF-8序列化后仍是原样,任何ASCII文件也是有效的UTF-8文件。</li> <li>没有字节序问题。UTF-8的字节序是由RFC3629定死的。</li> </ol><p>Windows Notepad使用UTF-8存储这个文件的结果如下:</p><pre class="brush:php;toolbar:false"> EF BB BF E9 94 9F E6 96 A4 E6 8B B7 0D 0A 61 0D 0A --BOM--- -------- -------- -------- -- -- -- -- -- U+ FEFF 951F 65A4 62F7 000D 000A 0061 000D 000A <p>注意UTF-8前边仍然塞进去了<code>U+FEFF</code>按照UTF-8序列化的结果<code>EF BB BF</code>,作为前边提到过的<strong>BOM</strong>字节顺序标记。<strong>Windows Notepad存储的UTF-8,是带有BOM标记的UTF-8</strong>。</p><p>但是如果仅仅对于UTF-8而言,字节序是没有意义的。因为UTF-8的字节序被规范写死,<code>U+FEFF</code>编码后必然得到<code>EF BB FF</code>,得不出其他的。没有二义性,BOM就失去了原本的意义。也许只有区别UTF-8文件和UTF-16文件的用处……</p><p>如何对待UTF-8文件的BOM,RFC3629的第6章有详细的规定,不加详述。</p><p>值得一提的是,BOM我想很多PHP程序员都经历过并且恨之入骨——PHP不认识文件中的BOM头并会将其作为HTTP Response的正文送出。这甚至在无缓冲的情况下,会导致<code>header()</code>等必须在Response开始前执行的函数直接失效。</p><p>所以PHP程序员总是会喜欢<strong>UTF-8 without BOM</strong>的编码方式——这基本也就宣布了Windows下的PHP开发,Windows Notepad完全的淘汰出局,哪怕是任何一星半点代码的临时修改。</p><h2>番外:Notepad++的字符编码测试</h2><p>ANSI没有区别,但Notepad++支持选择多国编码的不同ANSI编码方式(类似浏览器里选编码),可以轻松生成或读取Shift-JIS等其他字符集的文件。适合用于对付日文老游戏的<code>README</code>等文档。</p><p>UCS-2 Big Endian、UCS-2 Little Endian和前边UTF-16的两个例子一致。注意UTF-16的文件不提供“无BOM”的存储方法(提供了就坏了)。</p><p>UTF-8仍然代表“带有BOM标记的UTF-8”。但同时提供PHP程序员最爱的UTF-8 without BOM,就像:</p><pre class="brush:php;toolbar:false"> E9 94 9F E6 96 A4 E6 8B B7 0D 0A 61 0D 0A -------- -------- -------- -- -- -- -- -- U+ 951F 65A4 62F7 000D 000A 0061 000D 000A <p>Simple and clean.</p><blockquote><p><strong>注解</strong><br><code>[注A]</code> 对于一个双(多)字节的数,一定会按8位截断为1字节后写盘。那么写盘时先写最低8位还是先写最高8位,就是所谓的“字节序”(Endian)问题。例如,数<code>0x01020304</code>写盘时,是先写最低8位的<code>04 03 02 01</code>,还是先写最高8位的<code>01 02 03 04</code>?<br> 先写低8位的叫做小端在先(little-endian),先写高8位的叫做大端在先(big-endian)。实际采用何种字节序受系统环境、标准规范和软件实际编写的多方面控制,不一概而论。<br><code>[注B]</code> 字节序如果我没弄错,是GB2312采用的EUC字符编码方法控制的。<br><code>[注C]</code> 本文并不严格区分<strong>UTF-16</strong>与<strong>UCS-2</strong>。<br><code>[注D]</code> Unicode的最大值实际上达到了U+10FFFF,超出了两个字节能够存储的限度。<br> 但Unicode由于历史原因,留下了U+D800~U+DFFF这一段永久保留不用的空缺区域。<br> 因此对U+10000及以上的字符,UTF-16借助了这部分空缺区域,对这些编码超大的字符打破2字节16位的惯例,特别的用4字节32位去表示之。<br> 这一部分编码值太大的字符,超出了GBK的字符集范围,因此本文将<strong>完全忽略</strong>。如有机会再进一步测试。</p></blockquote>