Skip to content

Latest commit

 

History

History
686 lines (477 loc) · 41.8 KB

Encoding.md

File metadata and controls

686 lines (477 loc) · 41.8 KB

Table of Contents generated with DocToc

0. 总览

主流字符编码的一切,如果遇到新东西,也会不断补充。包括常见字符集、常见字符编码、常见语言中的字符编码,不同操作系统下的字符编码、ASCII,Latin1,MBCS,UniCode,utf-8,GB2312,GBK,大端与小端,码点,代理对,多文种平面,BOM等内容。

相关阅读:

1. 编码与字符集定义

计算机中储存的信息都是用二进制数表示的;而我们在屏幕上看到的英文、汉字等字符是二进制数转换之后的结果。通俗的说,按照何种规则将字符存储在计算机中,如'a'用什么表示,称为"编码";反之,将存储在计算机中的二进制数解析显示出来,称为"解码",如同密码学中的加密和解密。在解码过程中,如果使用了错误的解码规则,则导致'a'解析成'b'或者乱码。

1.1 字符集(Charset)

是一个系统所支持的所有抽象字符的集合,字符是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等。

常见字符集:ASCII字符集、GB2312字符集、BIG5字符集、GB18030字符集、Unicode字符集等。计算机要准确的处理各种字符集文字,需要进行字符编码,以便计算机能够识别和存储各种文字。

1.2 字符编码(Character Encoding)

是一套法则,使用该法则能够对自然语言的字符的一个集合(如字母表或音节表),与其他东西的一个集合(如号码或电脉冲)进行配对。即在符号集合与数字系统之间建立对应关系,它是信息处理的一项基本技术。通常人们用符号集合(一般情况下就是文字)来表达信息。而以计算机为基础的信息处理系统则是利用元件(硬件)不同状态的组合来存储和处理信息的。元件不同状态的组合能代表数字系统的数字,因此字符编码就是将符号转换为计算机可以接受的数字系统的数,称为数字代码。

2. 大端与小端

对于单字节编码比如ASCII和Latin1来说,不存在大端和小端的说法。但对于多字节编码就存在这个概念。

大端(Big Endian):高字节存储在低地址,低字节存储在高地址,更符合我们的日常使用习惯。 小端(Little Endian):高字节存储在高地址,低字节存储在低地址。

3. 常见的字符集与编码

3.1 ASCII编码

ASCII ((American Standard Code for Information Interchange): 美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。它是最通用的信息交换标准,并等同于国际标准ISO/IEC 646。ASCII第一次以规范标准的类型发表是在1967年,最后一次更新则是在1986年,到目前为止共定义了128个字符。这128个符号(包括32个不能打印出来的控制符号),只占用了一个字节的后面7位,最前面的一位统一规定为0,从0x00-0x7F

学习C语言的时候都会讲的东西,ISO页面

控制字符:

二进制 十进制 十六进制 缩写 Unicode表示法 脱出字符表示法 名称/意义
0000 0000 0 00 NUL ^@ 空字符(Null)
0000 0001 1 01 SOH ^A 标题开始
0000 0010 2 02 STX ^B 本文开始
0000 0011 3 03 ETX ^C 本文结束
0000 0100 4 04 EOT ^D 传输结束
0000 0101 5 05 ENQ ^E 请求
0000 0110 6 06 ACK ^F 确认回应
0000 0111 7 07 BEL ^G 响铃
0000 1000 8 08 BS ^H 退格
0000 1001 9 09 HT ^I 水平定位符号
0000 1010 10 0A LF ^J 换行键
0000 1011 11 0B VT ^K 垂直定位符号
0000 1100 12 0C FF ^L 换页键
0000 1101 13 0D CR ^M CR (字符)
0000 1110 14 0E SO ^N 取消变换(Shift out)
0000 1111 15 0F SI ^O 启用变换(Shift in)
0001 0000 16 10 DLE ^P 跳出数据通讯
0001 0001 17 11 DC1 ^Q 设备控制一(XON 激活软件速度控制)
0001 0010 18 12 DC2 ^R 设备控制二
0001 0011 19 13 DC3 ^S 设备控制三(XOFF 停用软件速度控制)
0001 0100 20 14 DC4 ^T 设备控制四
0001 0101 21 15 NAK ^U 确认失败回应
0001 0110 22 16 SYN ^V 同步用暂停
0001 0111 23 17 ETB ^W 区块传输结束
0001 1000 24 18 CAN ^X 取消
0001 1001 25 19 EM ^Y 连线介质中断
0001 1010 26 1A SUB ^Z 替换
0001 1011 27 1B ESC ^[ 退出键
0001 1100 28 1C FS ^\ 文件分割符
0001 1101 29 1D GS ^] 组群分隔符
0001 1110 30 1E RS ^^ 记录分隔符
0001 1111 31 1F US ^_ 单元分隔符
0111 1111 127 7F DEL ^? Delete字符

可显示字符:

二进制 十进制 十六进制 图形
0010 0000 32 20 (space)
0010 0001 33 21 !
0010 0010 34 22 "
0010 0011 35 23 #
0010 0100 36 24 $
0010 0101 37 25 %
0010 0110 38 26 &
0010 0111 39 27 '
0010 1000 40 28 (
0010 1001 41 29 )
0010 1010 42 2A *
0010 1011 43 2B +
0010 1100 44 2C ,
0010 1101 45 2D -
0010 1110 46 2E .
0010 1111 47 2F /
0011 0000 48 30 0
0011 0001 49 31 1
0011 0010 50 32 2
0011 0011 51 33 3
0011 0100 52 34 4
0011 0101 53 35 5
0011 0110 54 36 6
0011 0111 55 37 7
0011 1000 56 38 8
0011 1001 57 39 9
0011 1010 58 3A :
0011 1011 59 3B ;
0011 1100 60 3C <
0011 1101 61 3D =
0011 1110 62 3E >
0011 1111 63 3F ?
0100 0000 64 40 @
0100 0001 65 41 A
0100 0010 66 42 B
0100 0011 67 43 C
0100 0100 68 44 D
0100 0101 69 45 E
0100 0110 70 46 F
0100 0111 71 47 G
0100 1000 72 48 H
0100 1001 73 49 I
0100 1010 74 4A J
0100 1011 75 4B K
0100 1100 76 4C L
0100 1101 77 4D M
0100 1110 78 4E N
0100 1111 79 4F O
0101 0000 80 50 P
0101 0001 81 51 Q
0101 0010 82 52 R
0101 0011 83 53 S
0101 0100 84 54 T
0101 0101 85 55 U
0101 0110 86 56 V
0101 0111 87 57 W
0101 1000 88 58 X
0101 1001 89 59 Y
0101 1010 90 5A Z
0101 1011 91 5B [
0101 1100 92 5C |
0101 1101 93 5D ]
0101 1110 94 5E ^
0101 1111 95 5F _
0110 0000 96 60 `
0110 0001 97 61 a
0110 0010 98 62 b
0110 0011 99 63 c
0110 0100 100 64 d
0110 0101 101 65 e
0110 0110 102 66 f
0110 0111 103 67 g
0110 1000 104 68 h
0110 1001 105 69 i
0110 1010 106 6A j
0110 1011 107 6B k
0110 1100 108 6C l
0110 1101 109 6D m
0110 1110 110 6E n
0110 1111 111 6F o
0111 0000 112 70 p
0111 0001 113 71 q
0111 0010 114 72 r
0111 0011 115 73 s
0111 0100 116 74 t
0111 0101 117 75 u
0111 0110 118 76 v
0111 0111 119 77 w
0111 1000 120 78 x
0111 1001 121 79 y
0111 1010 122 7A z
0111 1011 123 7B {
0111 1100 124 7C
0111 1101 125 7D }
0111 1110 126 7E ~

ASCII中的控制字符:

ASCII 编码中第 0~31 个字符(开头的 32 个字符)以及第 127 个字符(最后一个字符)都是不可见的(无法显示),但是它们都具有一些特殊功能,所以称为控制字符( Control Character)或者功能码(Function Code)。

这 33 个控制字符大都与通信、数据存储以及老式设备有关,有些在现代电脑中的含义已经改变了。

下面列出了部分控制字符的具体功能:

  • NUL (0)

NULL,空字符。空字符起初本意可以看作为 NOP(中文意为空操作,就是啥都不做的意思),此位置可以忽略一个字符。

之所以有这个空字符,主要是用于计算机早期的记录信息的纸带,此处留个 NUL 字符,意思是先占这个位置,以待后用,比如你哪天想起来了,在这个位置在放一个别的啥字符之类的。

后来呢,NUL 被用于C语言中,表示字符串的结束,当一个字符串中间出现 NUL 时,就意味着这个是一个字符串的结尾了。这样就方便按照自己需求去定义字符串,多长都行,当然只要你内存放得下,然后最后加一个\0,即空字符,意思是当前字符串到此结束。

  • SOH (1)

Start Of Heading,标题开始。如果信息沟通交流主要以命令和消息的形式的话,SOH 就可以用于标记每个消息的开始。

1963年,最开始 ASCII 标准中,把此字符定义为 Start of Message,后来又改为现在的 Start Of Heading。

现在,这个 SOH 常见于主从(master-slave)模式的 RS232 的通信中,一个主设备,以 SOH 开头,和从设备进行通信。这样方便从设备在数据传输出现错误的时候,在下一次通信之前,去实现重新同步(resynchronize)。如果没有一个清晰的类似于 SOH 这样的标记,去标记每个命令的起始或开头的话,那么重新同步,就很难实现了。

  • STX (2) 和 ETX (3)

STX 表示 Start Of Text,意思是“文本开始”;ETX 表示 End Of Text,意思是“文本结束”。

通过某种通讯协议去传输的一个数据(包),称为一帧的话,常会包含一个帧头,包含了寻址信息,即你是要发给谁,要发送到目的地是哪里,其后跟着真正要发送的数据内容。

而 STX,就用于标记这个数据内容的开始。接下来是要传输的数据,最后是 ETX,表明数据的结束。

而中间具体传输的数据内容,ASCII 并没有去定义,它和你所用的传输协议有关。 |帧头|数据或文本内容|

帧头 数据或文本内容
SOH(表明帧头开始) ......(帧头信息,比如包含了目的地址,表明你发送给谁等等) STX(表明数据开始) ......(真正要传输的数据) ETX(表明数据结束
  • BEL (7)

BELl,响铃。在 ASCII 编码中,BEL 是个比较有意思的东西。BEL 用一个可以听得见的声音来吸引人们的注意,既可以用于计算机,也可以用于周边设备(比如打印机)。

注意,BEL 不是声卡或者喇叭发出的声音,而是蜂鸣器发出的声音,主要用于报警,比如硬件出现故障时就会听到这个声音,有的计算机操作系统正常启动也会听到这个声音。蜂鸣器没有直接安装到主板上,而是需要连接到主板上的一种外设,现代很多计算机都不安装蜂鸣器了,即使输出 BEL 也听不到声音,这个时候 BEL 就没有任何作用了。

  • BS (8)

BackSpace,退格键。退格键的功能,随着时间变化,意义也变得不同了。

退格键起初的意思是,在打印机和电传打字机上,往回移动一格光标,以起到强调该字符的作用。比如你想要打印一个 a,然后加上退格键后,就成了 aBS^。在机械类打字机上,此方法能够起到实际的强调字符的作用,但是对于后来的 CTR 下时期来说,就无法起到对应效果了。

而现代所用的退格键,不仅仅表示光标往回移动了一格,同时也删除了移动后该位置的字符。

  • HT (9)

Horizontal Tab,水平制表符,相当于 Table/Tab 键。

水平制表符的作用是用于布局,它控制输出设备前进到下一个表格去处理。而制表符 Table/Tab 的宽度也是灵活不固定的,只不过在多数设备上制表符 Tab 都预定义为 4 个空格的宽度。

水平制表符 HT 不仅能减少数据输入者的工作量,对于格式化好的文字来说,还能够减少存储空间,因为一个Tab键,就代替了 4 个空格。

  • LF (10)

Line Feed,直译为“给打印机等喂一行”,也就是“换行”的意思。LF 是 ASCII 编码中常被误用的字符之一。

LF 的最原始的含义是,移动打印机的头到下一行。而另外一个 ASCII 字符,CR(Carriage Return)才是将打印机的头移到最左边,即一行的开始(行首)。很多串口协议和 MS-DOS 及 Windows 操作系统,也都是这么实现的。

而C语言和 Unix 操作系统将 LF 的含义重新定义为“新行”,即 LF 和 CR 的组合效果,也就是回车且换行的意思。

从程序的角度出发,C语言和 Unix 对 LF 的定义显得更加自然,而 MS-DOS 的实现更接近于 LF 的本意。

现在人们常将 LF 用做“新行(newline)”的功能,大多数文本编辑软件也都可以处理单个 LF 或者 CR/LF 的组合了。

  • VT (11)

Vertical Tab,垂直制表符。它类似于水平制表符 Tab,目的是为了减少布局中的工作,同时也减少了格式化字符时所需要存储字符的空间。VT 控制符用于跳到下一个标记行。

说实话,还真没看到有些地方需要用 VT,因为一般在换行的时候都是用 LF 代替 VT 了。

  • FF (12)

Form Feed,换页。设计换页键,是用来控制打印机行为的。当打印机收到此键码的时候,打印机移动到下一页。

不同的设备的终端对此控制符所表现的行为各不同,有些会清除屏幕,有些只是显示^L字符,有些只是新换一行而已。例如,Unix/Linux 下的 Bash Shell 和 Tcsh 就把 FF 看做是一个清空屏幕的命令。

  • CR (13)

Carriage return,回车,表示机器的滑动部分(或者底座)返回。

CR 回车的原意是让打印头回到左边界,并没有移动到下一行的意思。随着时间的流逝,后来人们把 CR 的意思弄成了 Enter 键,用于示意输入完毕。

在数据以屏幕显示的情况下,人们按下 Enter 的同时,也希望把光标移动到下一行,因此C语言和 Unix 重新定义了 CR 的含义,将其表示为移动到下一行。当输入 CR 时,系统也常常隐式地将其转换为LF。 SO (14) 和 SI (15) SO,Shift Out,不用切换;SI,Shift In,启用切换。

早在 1960s 年代,设计 ASCII 编码的美国人就已经想到了,ASCII 编码不仅仅能用于英文,也要能用于外文字符集,这很重要,定义 Shift In 和 Shift Out 正是考虑到了这点。

最开始,其意为在西里尔语和拉丁语之间切换。西里尔语 ASCII(也即 KOI-7 编码)将 Shift 作为一个普通字符,而拉丁语 ASCII(也就是我们通常所说的 ASCII)用 Shift 去改变打印机的字体,它们完全是两种含义。

在拉丁语 ASCII 中,SO 用于产生双倍宽度的字符(类似于全角),而用 SI 打印压缩的字体(类似于半角)。

  • DLE (16)

Data Link Escape,数据链路转义。

有时候我们需要在通信过程中发送一些控制字符,但是总有一些情况下,这些控制字符被看成了普通的数据流,而没有起到对应的控制效果,ASCII 编码引入 DLE 来解决这类问题。

如果数据流中检测到了 DLE,数据接收端会对数据流中接下来的字符另作处理。但是具体如何处理,ASCII 规范中并没有定义,只是弄了个 DLE 去打断正常的数据流,告诉接下来的数据要特殊对待。

  • DC1 (17)

Device Control 1,或者 XON – Transmission on。

这个 ASCII 控制符尽管原先定义为 DC1, 但是现在常表示为 XON,用于串行通信中的软件流控制。其主要作用为,在通信被控制符 XOFF 中断之后,重新开始信息传输。

用过串行终端的人应该还记得,当有时候数据出错了,按 Ctrl+Q(等价于XON)有时候可以起到重新传输的效果。这是因为,此 Ctrl+Q 键盘序列实际上就是产生 XON 控制符,它可以将那些由于终端或者主机方面,由于偶尔出现的错误的 XOFF 控制符而中断的通信解锁,使其正常通信。

  • DC3 (19)

Device Control 3,或者 XOFF(Transmission off,传输中断)。

  • EM (25)

End of Medium,已到介质末端,介质存储已满。

EM 用于,当数据存储到达串行存储介质末尾的时候,就像磁带或磁头滚动到介质末尾一样。其用于表述数据的逻辑终点,即不必非要是物理上的达到数据载体的末尾。

  • FS(28)

File Separator,文件分隔符。FS 是个很有意思的控制字符,它可以让我们看到 1960s 年代的计算机是如何组织的。

我们现在习惯于随机访问一些存储介质,比如 RAM、磁盘等,但是在设计 ASCII 编码的那个年代,大部分数据还是顺序的、串行的,而不是随机访问的。此处所说的串行,不仅仅指的是串行通信,还指的是顺序存储介质,比如穿孔卡片、纸带、磁带等。

在串行通信的时代,设计这么一个用于表示文件分隔的控制字符,用于分割两个单独的文件,是一件很明智的事情。

  • GS(29)

Group Separator,分组符。

ASCII 定义控制字符的原因之一就是考虑到了数据存储。

大部分情况下,数据库的建立都和表有关,表包含了多条记录。同一个表中的所有记录属于同一类型,不同的表中的记录属于不同的类型。

而分组符 GS 就是用来分隔串行数据存储系统中的不同的组。值得注意的是,当时还没有使用 Excel 表格,ASCII 时代的人把它叫做组。

  • RS(30)

Record Separator,记录分隔符,用于分隔一个组或表中的多条记录。

  • US(31)

Unit Separator,单元分隔符。

在 ASCII 定义中,数据库中所存储的最小的数据项叫做单元(Unit)。而现在我们称其字段(Field)。单元分隔符 US 用于分割串行数据存储环境下的不同单元。

现在的数据库实现都要求大部分类型都拥有固定的长度,尽管有时候可能用不到,但是对于每一个字段,却都要分配足够大的空间,用于存放最大可能的数据。

这种做法的弊端就是占用了大量的存储空间,而 US 控制符允许字段具有可变的长度。在 1960s 年代,数据存储空间很有限,用 US 将不同单元分隔开,能节省很多空间。

  • DEL (127)

Delete,删除。

有人也许会问,为何 ASCII 编码中其它控制字符的值都很小(即 0~31),而 DEL 的值却很大呢(为 127)?

这是由于这个特殊的字符是为纸带而定义的。在那个年代,绝大多数的纸带都是用7个孔洞去编码数据的。而 127 这个值所对应的二进制值为111 1111(所有 7 个比特位都是1),将 DEL 用在现存的纸带上时,所有的洞就都被穿孔了,就把已经存在的数据都擦除掉了,就起到了删除的作用。

3.2 Latin1/ISO-8859-1

ASCII只使用7位二进制编码,只包含了英文字母,如果用一个字节进行编码那还剩下128个位置没有用完。所以后来ISO组织又制定了ISO-8859-1编码,向下兼容ASCII,也被称为Latin1、Latin-1、扩展ASCII码。将128-255的码点也用上了。编码范围是0x00-0xFF,0x00-0x7F之间完全和ASCII一致,0x80-0x9F之间是控制字符,0xA0-0xFF之间是文字符号。

相关链接:

0xA0 - 0xFF之间的字符一览:

 ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ

Latin1可见字符如下:

ISO/IEC 8859-1
 	x0	x1	x2	x3	x4	x5	x6	x7	x8	x9	xA	xB	xC	xD	xE	xF
0x	 
1x	 
2x	SP	!	"	#	$	%	&	'	(	)	*	+	,	-	.	/
3x	0	1	2	3	4	5	6	7	8	9	:	;	<	=	>	?
4x	@	A	B	C	D	E	F	G	H	I	J	K	L	M	N	O
5x	P	Q	R	S	T	U	V	W	X	Y	Z	[	\	]	^	_
6x	`	a	b	c	d	e	f	g	h	i	j	k	l	m	n	o
7x	p	q	r	s	t	u	v	w	x	y	z	{	|	}	~	 
8x	 
9x	 
Ax	NBSP	¡	¢	£	¤	¥	¦	§	¨	©	ª	«	¬	SHY	®	¯
Bx	°	±	²	³	´	µ	¶	·	¸	¹	º	»	¼	½	¾	¿
Cx	À	Á	Â	Ã	Ä	Å	Æ	Ç	È	É	Ê	Ë	Ì	Í	Î	Ï
Dx	Ð	Ñ	Ò	Ó	Ô	Õ	Ö	×	Ø	Ù	Ú	Û	Ü	Ý	Þ	ß
Ex	à	á	â	ã	ä	å	æ	ç	è	é	ê	ë	ì	í	î	ï
Fx	ð	ñ	ò	ó	ô	õ	ö	÷	ø	ù	ú	û	ü	ý	þ	ÿ

Latin1除ASCII收录的字符外,还包括西欧语言、希腊语、泰语、阿拉伯语、希伯来语对应的文字符号。欧元符号出现的比较晚,没有被收录在ISO-8859-1当中。

ISO-8859-1对应于ISO/IEC 10646即Unicode的前256个码位。 此字符集支持部分于欧洲使用的语言,包括阿尔巴尼亚语、巴斯克语、布列塔尼语、加泰罗尼亚语、丹麦语、荷兰语、法罗语、弗里西语、加利西亚语、德语、格陵兰语、冰岛语、爱尔兰盖尔语、意大利语、拉丁语、卢森堡语、挪威语、葡萄牙语、里托罗曼斯语、苏格兰盖尔语、西班牙语及瑞典语。 英语虽然没有重音字母,但仍会标明为ISO/IEC 8859-1编码。除此之外,欧洲以外的部分语言,如南非荷兰语、斯瓦希里语、印尼语及马来语、菲律宾他加洛语等也可使用ISO/IEC 8859-1编码。 法语及芬兰语本来也使用ISO/IEC 8859-1来表示。但因它没有法语使用的 œ、Œ、Ÿ 三个字母及芬兰语使用的 Š、š、Ž、ž ,故于1998年被ISO/IEC 8859-15所取代。(ISO 8859-15同时加入了欧元符号)。

其中编码0xA9比较常见,表示版权符号©。0x20是空格、0xA0是不换行空格、0xAD是选择性连接号。

因为Latin1编码范围使用了单字节内的所有空间,在支持Latin1编码的系统中传输和存储其他任何编码的字节流都不会被抛弃。换言之,把其他任何编码的字节流当作Latin1编码看待都没有问题。这是个很重要的特性,MySQL数据库默认编码是Latin1就是利用了这个特性。

3.3 GB2312

《信息交换用汉字编码字符集》是由中国国家标准总局1980年发布,1981年5月1日开始实施的一套国家标准,标准号是GB 2312-1980,百度百科维基百科

该文档可以在国家标准全文公开系统中找到,注意现在是在推荐性国家标准,而不是强制性国家标准中。显示的标准号也是GB/T 2312-1980。(自2017年3月23日起,该标准转化为推荐性标准,不再强制执行。)

GB 2312标准共收录6763个汉字,其中一级汉字3755个,二级汉字3008个;同时,GB 2312收录了包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的682个全角字符。所以一共7445个图形字符。GB 2312的出现,基本满足了汉字的计算机处理需要,它所收录的汉字已经覆盖中国大陆99.75%的使用频率。

对于人名、古汉语等方面出现的罕用字,GB 2312不能处理,这导致了后来GBKGB 18030汉字字符集的出现。

GB 2312中对所收汉字进行了分区处理,每区含有94个汉字/符号。这种表示方式也称为区位码。

  • 01-09区为特殊符号。
  • 16-55区为一级汉字,按拼音排序。
  • 56-87区为二级汉字,按部首/笔画排序。
  • 10-15区及88-94区则未有编码。

在GB 2312中,每个汉字字符使用2个字节来表示。第一个字节称为高位字节(也称区字节),第二个字节为低位字节(也称位字节)。

其中高位字节使用0xA1-0xF7(01-87区号加上0xA1),低位字节使用0xA1-0xFE(01-94位号加上0xA0)。如果知道了一个汉字的区号和位号就能知道其GB 2312编码。要查看所有GB2312编码可以访问这里

示例:奈芙莲 在GB 2312的编码为 C4CE DCBD C1AB,每个汉字两个字节。

GB2312是向下兼容ASCII的(或者应该叫可以共存?),因为GB2312的所有编码都是两个字节并且每个字节的第一位都是1(因为从0xA1开始的),但肯定就不可能兼容Latin1了。所以一般ANSI编码的文档中汉字和英文同时存在时英文只占一个字节,中文则会占2个字节(在GB2312编码中的话)。

值得注意的是01-09区定义了特殊符号,其中在03区是包括了完整了英文大小写字符,不过这些字符是全角的,和ASCII的字符显示并不一样。

下列两行字母前者为全角也就是GB2312中的字母,后者为ASCII的字母(也就是半角符号)。全角的英文字母可能在某些场合有特殊用途吧。一般的中文输入法比如搜狗输入法提供了切换全角半角的功能,全角输入模式下输入的英文字母就是前者,半角下就是后者。其实最常见的还是全角和半角空格问题,刚开始学编程时很容易输入全角空格导致编不过,全角的字母和符号倒是非常好区分。

A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

上述全角英文大写字母的GB2312编码为从0xA3C1-0xA3DA,而半角大写英文字母就是其ASCII编码为0x41-0x5A。

3.4 BIG5

也成大五码,台湾地区繁体中文标准字符集,采用双字节编码,共收录13053个中文字,1984年实施。

3.5 GBK

GBK编码:1995年12月发布的汉字编码国家标准,是对GB2312编码的扩充,对汉字采用双字节编码。GBK字符集共收录21003个汉字,包含国家标准GB13000-1(已废止的国标)中的全部中日韩汉字,和BIG5编码中的所有汉字。

GBK向下完全兼容GB2312-80编码。支持GB2312-80编码不支持的部分中文姓,中文繁体,日文假名,还包括希腊字母以及俄语字母等字母。

GBK共收录21886个汉字和图形符号,其中汉字(包括部首和构件)21003个,图形符号883个。

GBK和GB 2312仍是目前广泛使用的中文编码。

3.6 GB 18030

也就是GB 18030-2005 (国家标准全文公开系统不过下载到的PDF不能直接打开,需要Adobe Reader并且安装FileOpen插件,联网才能解密,这...),维基百科

  • 向下完美兼容GB 2312,基本兼容GBK。
  • GB 18030在其标准中以码表形式定义了除去代理对外的全部Unicode码位的定义,因此算得上是一种Unicode的变换格式(UTF)。
  • GB 18030包含三种长度的编码:单字节的ASCII、双字节的GBK(略带扩展)、以及用于填补所有Unicode码位的四字节UTF区块。

具体信息略过不谈。

4. UniCode字符集与相关编码

世界上存在多种字符编码方式,一串文本或者一个文本文件,如果保存和打开者、发送和接收方使用不同的编码,那么可能就会按照错误的方式解读从而出现乱码。而且面对一个需要混合多种语言的文本需求时单一的编码就会显得比较无力。

4.1 UniCode字符集

因此,在上世纪80年代末,Xerox、Apple 等公司开始研究,是否能制定一套多语言的统一编码系统。后来,多个机构成立了 Unicode 联盟,在 1991 年释出 Unicode 1.0,收录了 24 种语言共 7161 个字符。在四分之一个世纪后的 2016年,Unicode 已释出 9.0 版本,收录 135 种语言共 128237 个字符。

这些字符被收录为统一字符集(Universal Coded Character Set, UCS),每个字符映射至一个整数码点code point),码点的范围是 0 至 0x10FFFF,码点又通常记作 U+XXXX,当中 XXXX 为 16 进位数字。例如 奈 → U+5938、芙 → U+8299、莲 → U+83B2。很明显,UCS 中的字符无法像 ASCII 般以一个字节存储。

因此,Unicode 还制定了各种储存码点的方式,这些方式称为 Unicode 转换格式(Uniform Transformation Format, UTF)。现时流行的 UTF 为 UTF-8、UTF-16 和 UTF-32。每种 UTF 会把一个码点储存为一至多个编码单元(code unit)。例如 UTF-8 的编码单元是 8 位的字节、UTF-16 为 16 位、UTF-32 为 32 位。除 UTF-32 外,UTF-8 和 UTF-16 都是可变长度编码。

注意,为了区分UniCode和其他编码比如ASCII,并不能直接将码点按照二进制存储,而要对其进行编码,也就是确定每个码点的存储方式(二进制格式)。从UniCode码点到具体的编码会经过一次转换。

4.2 UniCode字符平面映射

参见UniCode字符平面映射,目前的UniCode字符被编排为17组,每组成为一个平面(Plane),每个平面65536个码点。目前只使用了少量平面。

平面 始末字符值 中文名称 英文名称
0号平面 U+0000 - U+FFFF 基本多文种平面 Basic Multilingual Plane,简称BMP
1号平面 U+10000 - U+1FFFF 多文种补充平面 Supplementary Multilingual Plane,简称SMP
2号平面 U+20000 - U+2FFFF 表意文字补充平面 Supplementary Ideographic Plane,简称SIP
3号平面 U+30000 - U+3FFFF 表意文字第三平面 Tertiary Ideographic Plane,简称TIP
4号平面至 13号平面 U+40000 - U+DFFFF (尚未使用)
14号平面 U+E0000 - U+EFFFF 特别用途补充平面 Supplementary Special-purpose Plane,简称SSP
15号平面 U+F0000 - U+FFFFF 保留作为私人使用区(A区)[1] Private Use Area-A,简称PUA-A
16号平面 U+100000 - U+10FFFF 保留作为私人使用区(B区)[1] Private Use Area-B,简称PUA-B

3号平面到14号平面目前还没有使用,TIP(Plane 3) 准备用来映射甲骨文、金文、小篆等表意文字。

第4到第13平面尚无使用计划。

第15、16辅助平面都是私人使用区,可以用来存储自定义的字符。

4.3 UTF-8

UTF-8是互联网上使用最广的一种Unicode实现方式,有如下特点。

  • 它采用字节为编码单元,不会有字节序(endianness)的问题。
  • 每个 ASCII 字符只需一个字节去储存。
  • 如果程序原来是以字节方式储存字符,理论上不需要特别改动就能处理 UTF-8 的数据。

UTF-8的编码规则如下表:

码点范围 码点位数 字节1 字节2 字节3 字节4
U+0000 ~ U+007F 7 0xxxxxxx
U+0080 ~ U+07FF 11 110xxxxx 10xxxxxx
U+0800 ~ U+FFFF 16 1110xxxx 10xxxxxx 10xxxxxx
U+10000 ~ U+10FFFF 21 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

UTF-8是向下兼容ASCII编码的,U+0000 ~ U+007F与ASCII编码完全一致。

比如编码汉字莲,码点为U+83B2,在U+0800~U+FFFF范围内所有最终结果应该为三个字节,码点转换为二进制10000011 10110010,将16个二进制位一次填充得到UTF-8编码为11101000 10001110 10110010,转化为16进制即是E88EB2

在以英语为中心的互联网世界中广泛采用,因为英文只需要一个字节编码。而Plane 0中的常用汉字则需要3个字节。

4.4 UTF-16

UTF-16采用2个或者4个字节来编码(或者说由1个或2个编码单元来编码,每个编码单元16位),编码关系如下表:

平面 码点 编码
Plane 0 U+0000 ~ U+FFFF xxxxxxxx xxxxxxxx
Plane 1 ~ Plane 16 U+10000 ~ U+10FFFF 110110yy yyyyyyyy 110111xx xxxxxxxx

其中Plane 0中的字符直接将码点转化为16位二进制,编码为两个字节。而扩展平面上的字符则使用 代理对(Surrogate Pair) 编码。

代理对由一个 高位代理(High Surrogate) 和一个 低位代理(Low Surrogate) 来表示。

110110yy yyyyyyyy(0xd800 - 0xdbff)是高位代理,110111xx xxxxxxxx(0xdc00 - 0xdfff)是低位代理。为了能够区分2字节的UTF-16编码和代理对,Unicode将U+D800 ~ U+DFFF段预留了出来,不会有正常的Unicode码点处于这个范围。

代理对的编码方法:

  • 将码点位于U+10000 ~ U+10FFFF内的码点值减去0x10000,会得到一个0x00000 ~ 0xFFFFF之间的20位二进制数。
  • 取左边10位yy yyyyyyyy,加上0xD800(或者直接其二进制位前加上110110),得到16位的高位代理110110yy yyyyyyyy
  • 取右边10位xx xxxxxxxx,加上0xDC00(或者直接其二进制位前加上110111),得到16位的高位代理110111xx xxxxxxxx
  • 组合之后得到32位的代理对。

解析时反之即可,解析时如果代理不成对,计算机通常不显示该代理字符。

可以看出对于ASCII编码也会使用2个字节,也会存在一定空间浪费。

4.5 UTF-32

UTF-32非常简单,使用一个32位的编码单元表示,就是将码点转化为32位二进制数存储,位数不够的话左边补0。

但这样编码会非常浪费空间,U+0000~U+00FF只有一个字节的有效内容,利用率仅四分之一,基本多文种平面(BMP, Plane 0)内的字符只会占用2个字节,利用率仅一半。而平时使用最多的英文字符都只有一个字节,最常用的汉字都在Plane 0中。所以UTF-32并不利于网络传输,存储效率也不高。实际很少使用。

4.6 BOM

BOM即字节顺序标记(Byte Order Mark)。放在文件开始标记大端还是小端。

相应的UTF-16和UTF-32都有两种编码方式也就是BE大端和LE小端。

UTF Encoding BOM
UTF-8 EF BB BF
UTF-16 LE FF FE
UTF-16 BE FE FF
UTF-32 LE FF FE 00 00
UTF-32 BE 00 00 FE FF

其实BOM就是U+FEFF这个码点在对应UTF实现下的编码,小端时低字节在前所以二进制位是FF FE,大端时则是FE FF,UTF-32则需要在高两个字节补0。

而对应的另一个码点U+FFFE则被Unicode定义为非字符,不应该出现在文本中,如果用错误的字节序读出了这个字符,那么就代表字节序反了,也就意味着FF FE只能被解释为小端序中的U+FEFF

在UTF-8中,虽然在 Unicode 标准上允许字节顺序标记的存在,但实际上并不一定需要。UTF-8编码过的字节顺序标记则被用来标示它是UTF-8的文件。它只用来标示一个UTF-8的文件,而不用来说明字节顺序。

Unicode标准允许在UTF-8中使用BOM,但并不要求或推荐使用它。字节顺序在UTF-8中没有任何意义,所以它在UTF-8中的唯一用途是在开始时发出信号,表明文本流是用UTF-8编码的,或者表明它是从包含可选BOM的文本流转换到UTF-8的。该标准也不建议在有BOM的情况下将其删除,以便在不同的编码之间往返不会丢失信息,并使依赖BOM的代码继续工作。

通常我们称呼UTF-8时,一般是指没有BOM的UTF-8编码,如果有BOM的话一般称为UTF-8 BOM或者UTF-8 With BOM,中文叫做带BOM的UTF-8。

4.7 UCS-2与UCS-4

UCS-2用两个字节来表示Plane 0中的字符,在0000到FFFF的码位范围内,它和UTF-16基本一致,为什么说基本一致,因为在UTF-16中从U+D800到U+DFFF的码点不对应于任何字符,而在使用UCS-2的时代,U+D800到U+DFFF内的值被占用,所以不能表示扩展平面的码点。BOM机制同UTF-16。

UCS-4同UTF-32一致。大端和小端的BOM定义完全一致。

需要注意区分UCS-2和UTF-16。

4.8 UTF实现对比

UTF-8表示ASCII只需要一个字节,Plane 0的汉字(汉字都在U+0800 ~ U+FFFF内)会被编码到3个字节。而UTF-16则将Plane 0内的无论是ASCII还是汉字都编码到2个字节。所以如果英文字母更多UTF-8会更节约存储空间,如果汉字更多则是UTF-16更节约。

而UTF-32则效率较低,实际中也比较少使用。

UTF-8不需要考虑字节序,大部分网页都采用UTF-8编码传输。

5. 编程语言中的字符

5.1 C/C++

语言特性

C++中用宽字符wchar_t提供对Unicode和多字节编码的支持,char * 字符串有专门的封装类 std::string 来处理,标准输入输出流是 std::cin 和 std::cout 。对于 wchar_t * 字符串,其封装类是 std::wstring,标准输入输出流是 wcin 和 wcout。

C++标准规定了宽字符,但并没有规定宽字符占用几个字节。Windows 系统里的宽字符是两个字节,就是 UTF-16;而 Unix/Linux 系统里为了更全面的国际码支持,其宽字符是四个字节,即 UTF-32 编码。这为程序的跨平台带来一定的混乱,除了 Windows 程序开发常用 wchar_t* 字符串表示 UTF-16 ,其他情况下 wchar_t* 都用得比较少。

MFC 一般用自家的 TCHAR 和 CString 类支持国际化,当没有定义 _UNICODE 宏时,TCHAR = char,当定义了 _UNICODE宏 时,TCHAR = wchar_t,CString 内部也是类似的。Qt 则用 QChar 和 QString 类(内部恒定为 UTF-16),一般的图形开发库都用自家的字符串类库。

在新标准 C++11 中,对国际码的支持做了明确的规定:

  • char * 对应 UTF-8 编码字符串(字面值表示为 u8"多种文字"),封装类为 std::string;
  • 新增 char16_t * 对应 UTF-16 编码字符串(字面值表示为 u"多种文字"),封装类为 std::u16string ;
  • 新增 char32_t * 对应 UTF-32 编码字符串(字面值表示为 U"多种文字"),封装类为 std::u32string 。

源文件编码

在 Windows 系统里最常用的文本字符编码格式是 ANSI (简体是 GBK,繁体是 Big5)和 Unicode (UTF-16LE)格式,Windows 命令行默认的输入输出格式是 ANSI 的。在 Linux 系统里统统都是 UTF-8。

Windows命令窗口Cmd都有对应的输入输出的字符编码,就是代码页,用chcp命令查看和修改。Windows默认代码页936,也就是GBK编码。

windows下:

  • Windows下GCC/G++不会对源文件中的字符串做转码。源文件是什么编码,那你得到的字符串中存储的就是对应的什么编码的字符串。如果源文件用GBK编码,并且在936的命令窗口运行,那么就是正常的,源文件如果用UTF-8,但命令窗口是GBK,那么就会乱码。
  • 而用MSVC的话如果是char*的字符串字面值,则会将不同编码的源文件转码为操作系统的ANSI多字节编码,wchar_t * 字符串一律转成 Unicode(UTF-16LE)。
  • 如果是Qt的话,官方推荐使用UTF-8。

Linux下:

  • 请一律使用UTF-8编码源文件。

5.2 java

java字符串使用Unicode字符集,char使用Latin1或者UTF-16 BE。如果整个字符串都能用单字节表示,也就是Unicode码点在0 ~ 255,那么就用Latin1,否则就用UTF-16 BE。所以如果不在基本多文种平面的字符会使用代理对也就是两个字符来表示。把这样的字符字面值赋给单个字符是编不过的。

6. 操作系统对字符的支持与处理

6.1 Windows代码页

Windows用代码也来管理字符编码,通常用 ANSI 用来表示本机的编码。这样不同地区的不同编码都能被叫做ANSI。 常见代码页: Latin1 1252 GBK 936 GB 18030 54936 Big5 950 UTF-8 65001

6.2 Linux

用UTF-8就完事了。