喜欢大胸只是本能,喜欢贫乳才是审美。 收藏本站
登陆 / 注册 搜索

阅读: 1.2K   回复: 2

[# 系统基础] ASCII Unicode 和 UTF-8这些之间的关系

空谷幽兰 仗剑天涯 2019-12-12 17:30 |显示全部楼层

从前车马很慢,书信很远,一生只够爱一个人,但是可以纳很多妾啊!

精华达人 主题破百 以坛为家 论坛元老
今天,我突然想搞清楚字符编码 Unicode 和 UTF-8 之间的关系,就开始查资料。
& t" d; `: v& |2 O
7 V; V& ]6 O8 S这个问题比我想象的复杂,看了一下午,才算初步搞清楚。. e1 t' I2 X* r5 b$ d
; b, r. S) Q+ D& H  e% T
下面就是我的笔记,主要用来整理自己的思路。我尽量写得通俗易懂,希望能对其他朋友有用。毕竟,字符编码是计算机技术的基石,想要熟练使用计算机,就必须懂得一点字符编码的知识。9 O/ Q& p& v) t* Z8 e: d% w& E
( a1 o/ ?: ~4 Q1 \! T+ b, h7 }7 Q; A3 J
一、ASCII 码
" A2 r- ^0 q; t* M, D$ E/ @
3 ~$ ^% n9 a5 [$ S. F+ ?2 v  ^我们知道,计算机内部,所有信息最终都是一个二进制值。每一个二进制位(bit)有01两种状态,因此八个二进制位就可以组合出256种状态,这被称为一个字节(byte)。也就是说,一个字节一共可以用来表示256种不同的状态,每一个状态对应一个符号,就是256个符号,从00000000到11111111。
/ N! d5 ?- }$ z  E* V
- ]/ n6 T8 n. k8 S) L5 A上个世纪60年代,美国制定了一套字符编码,对英语字符与二进制位之间的关系,做了统一规定。这被称为 ASCII 码,一直沿用至今。
5 p0 }1 ]2 ?- z" c  u; |) c
' a+ H! x) t. D: O1 rASCII 码一共规定了128个字符的编码,比如空格SPACE是32(二进制00100000),大写的字母A是65(二进制01000001)。这128个符号(包括32个不能打印出来的控制符号),只占用了一个字节的后面7位,最前面的一位统一规定为0。
$ L! c6 z" B8 ]% H
; p2 v- p/ p! p- o# C  ?二、非 ASCII 编码
: T  V9 A( ^4 b' z' I3 z/ n9 }9 s: V& u2 \, |+ _
英语用128个符号编码就够了,但是用来表示其他语言,128个符号是不够的。比如,在法语中,字母上方有注音符号,它就无法用 ASCII 码表示。于是,一些欧洲国家就决定,利用字节中闲置的最高位编入新的符号。比如,法语中的é的编码为130(二进制10000010)。这样一来,这些欧洲国家使用的编码体系,可以表示最多256个符号。
( H# l$ g, \; h+ [1 ]4 [" g* x! D2 U
但是,这里又出现了新的问题。不同的国家有不同的字母,因此,哪怕它们都使用256个符号的编码方式,代表的字母却不一样。比如,130在法语编码中代表了é,在希伯来语编码中却代表了字母Gimel (ג),在俄语编码中又会代表另一个符号。但是不管怎样,所有这些编码方式中,0--127表示的符号是一样的,不一样的只是128--255的这一段。$ Q, z% _$ z& ~6 [" E4 I  a

, j' q+ Z) q0 b: c$ a' e6 Z& l至于亚洲国家的文字,使用的符号就更多了,汉字就多达10万左右。一个字节只能表示256种符号,肯定是不够的,就必须使用多个字节表达一个符号。比如,简体中文常见的编码方式是 GB2312,使用两个字节表示一个汉字,所以理论上最多可以表示 256 x 256 = 65536 个符号。
1 C: p! k1 u2 o# S& ~! }2 c4 O! n( x( u* h( e
中文编码的问题需要专文讨论,这篇笔记不涉及。这里只指出,虽然都是用多个字节表示一个符号,但是GB类的汉字编码与后文的 Unicode 和 UTF-8 是毫无关系的。
. b# N% B; _* Z. S4 T( W' O" v: c- Z, m- k% K+ F4 I7 O
三. Unicode5 S5 Y: Z; p! F  V$ D, C6 \

. j6 ]' G, c7 u" E8 J正如上一节所说,世界上存在着多种编码方式,同一个二进制数字可以被解释成不同的符号。因此,要想打开一个文本文件,就必须知道它的编码方式,否则用错误的编码方式解读,就会出现乱码。为什么电子邮件常常出现乱码?就是因为发信人和收信人使用的编码方式不一样。
8 R8 T: l3 U8 J/ i1 u2 r4 V4 X! F1 e- R% f: O* z- r6 f2 q5 a
可以想象,如果有一种编码,将世界上所有的符号都纳入其中。每一个符号都给予一个独一无二的编码,那么乱码问题就会消失。这就是 Unicode,就像它的名字都表示的,这是一种所有符号的编码。
" w: @5 O$ H/ U: B6 P) @/ H1 D; ]. q, r: d5 l6 j; L7 B! [' ^
Unicode 当然是一个很大的集合,现在的规模可以容纳100多万个符号。每个符号的编码都不一样,比如,U+0639表示阿拉伯字母Ain,U+0041表示英语的大写字母A,U+4E25表示汉字严。具体的符号对应表,可以查询unicode.org,或者专门的汉字对应表。
7 ]- ?3 }# ~! G+ l
% X" Z% a9 b9 E9 ~$ n四、Unicode 的问题* G8 J; ], G& I# s& l; A

" Z8 B% N4 d% W5 t9 U/ D$ y" @需要注意的是,Unicode 只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。/ Q" {9 _6 W% H8 U& B
$ F3 z/ [  W( O- f6 v
比如,汉字严的 Unicode 是十六进制数4E25,转换成二进制数足足有15位(100111000100101),也就是说,这个符号的表示至少需要2个字节。表示其他更大的符号,可能需要3个字节或者4个字节,甚至更多。1 q. r' i; c" e" Z( i

1 z3 c' w( d& y这里就有两个严重的问题,第一个问题是,如何才能区别 Unicode 和 ASCII ?计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢?第二个问题是,我们已经知道,英文字母只用一个字节表示就够了,如果 Unicode 统一规定,每个符号用三个或四个字节表示,那么每个英文字母前都必然有二到三个字节是0,这对于存储来说是极大的浪费,文本文件的大小会因此大出二三倍,这是无法接受的。4 t- j- R- `/ H8 b. [

# h+ c1 ^8 A. p! [0 f9 ]它们造成的结果是:/ U6 k' \2 p- \4 t
5 r; f& X, |) i; O5 z' J. r7 m
1.出现了 Unicode 的多种存储方式,也就是说有许多种不同的二进制格式,可以用来表示 Unicode。
2 B2 V5 w# [7 i2.Unicode 在很长一段时间内无法推广,直到互联网的出现。
. e# g7 N$ `) ?5 W
) h- E9 Y- J0 B1 w+ }. K8 Z5 u五、UTF-8
  i# S0 ]4 Q! A3 m, ]+ u, x' `
互联网的普及,强烈要求出现一种统一的编码方式。UTF-8 就是在互联网上使用最广的一种 Unicode 的实现方式。其他实现方式还包括 UTF-16(字符用两个字节或四个字节表示)和 UTF-32(字符用四个字节表示),不过在互联网上基本不用。重复一遍,这里的关系是,UTF-8 是 Unicode 的实现方式之一。
7 v* D3 N4 B9 h' d% m& t4 l% @% F, k/ P  ~, j0 s* Y* C, {5 r9 E3 L
UTF-8 最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。; t* H( u6 r; R% [- k* C. T8 K: S

( ]- R9 W1 X& n% f: l7 r. xUTF-8 的编码规则很简单,只有二条:' Z& F; [2 [. r, I2 w3 A+ S; c

2 f( a6 q1 a1 u! L/ d1.对于单字节的符号,字节的第一位设为0,后面7位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。6 C* t1 Y) L) _

* G  G1 x# |) r2 n' v; _* ]2.对于n字节的符号(n > 1),第一个字节的前n位都设为1,第n + 1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码。8 A! C1 v1 Z! Z) g
& q/ Z9 T5 t8 j# @5 `5 }( |" k( r; G
下表总结了编码规则,字母x表示可用编码的位。$ z& U' R3 x" ]1 x
& K: t* v$ y- H& b- v
  1. Unicode符号范围     |        UTF-8编码方式" \0 _. X8 r& I- C  |1 M6 _4 h
  2. (十六进制)                |              (二进制)
    % z1 c8 O7 q  M) |8 {3 l
  3. ----------------------+---------------------------------------------
      M% V4 r# V1 Q" P/ Z
  4. 0000 0000-0000 007F | 0xxxxxxx5 X2 [+ P; i. p  I8 U7 e- C* ?
  5. 0000 0080-0000 07FF | 110xxxxx 10xxxxxx: U6 o( ~" P" f6 ]8 L$ p
  6. 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
    6 c, v3 k' u8 m8 o8 L/ m; a8 b, e2 d1 K$ I
  7. 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
复制代码

6 l) F) j, }0 L: r
4 `9 q3 J3 ?/ G7 l) f' l! p  L跟据上表,解读 UTF-8 编码非常简单。如果一个字节的第一位是0,则这个字节单独就是一个字符;如果第一位是1,则连续有多少个1,就表示当前字符占用多少个字节。% c( E, f4 |/ S2 u- n+ u

  ~; W7 ~  B5 `& g3 n0 f5 ]6 e下面,还是以汉字严为例,演示如何实现 UTF-8 编码。$ W  N5 l2 K( S' p5 L- t8 w$ j8 ?
1 ~2 K. n- \# S
严的 Unicode 是4E25(100111000100101),根据上表,可以发现4E25处在第三行的范围内(0000 0800 - 0000 FFFF),因此严的 UTF-8 编码需要三个字节,即格式是1110xxxx 10xxxxxx 10xxxxxx。然后,从严的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了,严的 UTF-8 编码是11100100 10111000 10100101,转换成十六进制就是E4B8A5。/ ~! C4 c# P- y9 t

2 f$ W$ ?" D# m7 H六、Unicode 与 UTF-8 之间的转换
8 V. _- v5 W1 }& Z+ I
0 B8 ]7 o( T& u1 x通过上一节的例子,可以看到严的 Unicode 码是4E25,UTF-8 编码是E4B8A5,两者是不一样的。它们之间的转换可以通过程序实现。  U2 F! U/ W. j; ~

1 H- z) S+ @6 W3 B& J4 UWindows平台,有一个最简单的转化方法,就是使用内置的记事本小程序notepad.exe。打开文件后,点击文件菜单中的另存为命令,会跳出一个对话框,在最底部有一个编码的下拉条。
4 m4 K5 p6 `9 f$ V1 t
ASCII Unicode 和 UTF-8这些之间的关系 2.png
* Y0 [( D2 T$ W& t8 b
里面有四个选项:ANSI,Unicode,Unicode big endian和UTF-8。% A7 o4 I2 ?( t9 f2 R  ?% ^! r
0 Y6 c* V. h5 S# i& \) u
1.ANSI是默认的编码方式。对于英文文件是ASCII编码,对于简体中文文件是GB2312编码(只针对 Windows 简体中文版,如果是繁体中文版会采用 Big5 码)。
9 ]9 h' V- D% w2 q& U0 Z7 e  R  I8 i5 |/ g9 l# A
2.Unicode编码这里指的是notepad.exe使用的 UCS-2 编码方式,即直接用两个字节存入字符的 Unicode 码,这个选项用的 little endian 格式。, O, A% G9 u6 ]! r6 H' [

: _! t) ^4 H0 Y9 H; ^- Y3.Unicode big endian编码与上一个选项相对应。我在下一节会解释 little endian 和 big endian 的涵义。3 ?, C! d& `" S3 {! c

, J6 Y. z7 \4 ?1 w. N4.UTF-8编码,也就是上一节谈到的编码方法。
- {$ ~* y' _, v; E3 [$ Y0 R6 O* M, ]0 I8 u; V$ f7 B1 g( Y
选择完"编码方式"后,点击"保存"按钮,文件的编码方式就立刻转换好了。& T' h7 q) ~) w9 `6 r% B8 `5 ^6 S
: |" p, y2 L4 d: l) T
七、Little endian 和 Big endian
; |3 ^4 a+ ]" I9 u% r0 Q
3 D. G7 p& A7 m+ j上一节已经提到,UCS-2 格式可以存储 Unicode 码(码点不超过0xFFFF)。以汉字严为例,Unicode 码是4E25,需要用两个字节存储,一个字节是4E,另一个字节是25。存储的时候,4E在前,25在后,这就是 Big endian 方式;25在前,4E在后,这是 Little endian 方式。
0 ^2 j9 x, @5 a$ d( {# F
+ u4 ^$ \5 h3 U, c这两个古怪的名称来自英国作家斯威夫特的《格列佛游记》。在该书中,小人国里爆发了内战,战争起因是人们争论,吃鸡蛋时究竟是从大头(Big-endian)敲开还是从小头(Little-endian)敲开。为了这件事情,前后爆发了六次战争,一个皇帝送了命,另一个皇帝丢了王位。
, r9 A& `6 L  \0 x5 M9 Q+ r# f/ r6 W$ Y
第一个字节在前,就是"大头方式"(Big endian),第二个字节在前就是"小头方式"(Little endian)。
0 C9 O' I. h2 b
) i, J2 P0 P: l( e那么很自然的,就会出现一个问题:计算机怎么知道某一个文件到底采用哪一种方式编码?
: D# D1 Y  ~! L& h6 ~% T' r: E" ^- A
" X1 N( z) K( v0 b! q; FUnicode 规范定义,每一个文件的最前面分别加入一个表示编码顺序的字符,这个字符的名字叫做"零宽度非换行空格"(zero width no-break space),用FEFF表示。这正好是两个字节,而且FF比FE大1。
* G  d- d8 r. l& d& C$ {! c2 }
# s& m6 c" @- s7 e  v6 S如果一个文本文件的头两个字节是FE FF,就表示该文件采用大头方式;如果头两个字节是FF FE,就表示该文件采用小头方式。- K; `1 o7 P2 f8 I" o+ z; K, C

! ?  ]! C  t7 m; c* S' Y% y八、实例
, ~" G0 n; m5 L" B& M6 K% C& m
! \+ _1 h% m  s) W. }下面,举一个实例。$ g5 r0 e/ y  r4 [
( b$ {1 \7 K4 u+ d0 X4 i
打开"记事本"程序notepad.exe,新建一个文本文件,内容就是一个严字,依次采用ANSI,Unicode,Unicode big endian和UTF-8编码方式保存。
# W7 ^% B$ c4 }
3 u1 ?5 a: B0 \  K6 I$ ~. K4 c然后,用文本编辑软件UltraEdit 中的"十六进制功能",观察该文件的内部编码方式。
# s4 P- i. ?1 b$ h6 g
" @4 L6 Q6 E3 s1 p+ N9 s1. ANSI:文件的编码就是两个字节D1 CF,这正是严的 GB2312 编码,这也暗示 GB2312 是采用大头方式存储的。2 N6 D7 c0 @8 S/ i3 ]$ |
% ^0 H: ^0 ?" ^) @: U
2. Unicode:编码是四个字节FF FE 25 4E,其中FF FE表明是小头方式存储,真正的编码是4E25。& V& T/ {- v9 M6 Q7 {3 l  [2 w+ m

& V; [/ F* N2 I$ S3. Unicode big endian:编码是四个字节FE FF 4E 25,其中FE FF表明是大头方式存储。
; [0 k2 ?/ _( X& g$ |
4 R+ O# j6 A/ L7 ]2 x. ~4. UTF-8:编码是六个字节EF BB BF E4 B8 A5,前三个字节EF BB BF表示这是UTF-8编码,后三个E4B8A5就是严的具体编码,它的存储顺序与编码顺序是一致的。
' F! j  `, P2 I


luzupeng34 「初入古黑」 2019-12-12 17:30 |显示全部楼层

这个用户很懒,还没有填写自我介绍呢~

火钳刘明
心微凉 「龙战于野」 2020-1-12 10:43 |显示全部楼层

那个费劲心思逗你笑的人,终究比不上你一见面就开心的人。

赞啊,终于解开我多年的疑惑
您需要登录后才可以回帖 登录 | 免费注册  

本版积分规则

关于本站|大事记|小黑屋|古黑论 网站统计

GMT+8, 2020-10-26 11:40 , Processed in 0.041571 second(s), 22 queries , Redis On.

© 2015-2020 GuHei.Net

Powered by Discuz! X3.4

快速回复 返回列表