字符编码-javascript中UTF-16字符的性质与计算

UTF-16是UCS-2的扩展。JavaScript引擎内部是自由的使用UCS-2或者UTF-16。大多数引擎使用的是UTF-16,无论它们使用什么方式实现,它只是一个具体的实现,这不将影响到语言的特性。 —–到时候,再探究

 

JavaScript中的字符串是以UTF-16为代码单元。通常我们使用的字符范围都在Unicode值0x10000以内,他们对应的UTF-16就是它们自身。但Unicode中也存在这个范围之外的字符,这时候就需要两个UTF-16字符来描述。这些字符在统计时会被作为两个字符。
比如下面这个字符,虽然只有一个字,但却会统计出两个字符。

<script>
alert("?".length); //2
</script>
 因为字符串的length表示的并不是字符个数,而是UTF-16的单元个数。Unicode为UTF-16预留了两块区域,称为“高位替代区”和“低位替代区”。这两个区域的字符单独使用是每一意义的,甚至在某些地方都无法单独使用他们,比如内置的URL转义函数就无法转换这样的字符

在正则表达式中,它也被作为两个字符来处理

使用charCodeAt的时候也会把它作为两个字符来处理。这些把它当做两个字符处理的情况会分别处理它的“高位替代符”和“低位替代符”。
这种四字节的UTF-16对应的Unicode值可以这样计算。这些替代符都有固定的格式,这些格式也决定了他们在Unicode中的区位。比如高位替代符的二进制表示就是 “110110xx xxxxxxxx”,低位替代符的二进制表示就是“110111yy yyyyyyyy”。把它们固定的头部去掉,取出内容部分再把这两个替代符的内容部分的二进制连接起来就可以得到“xxxx xxxxxxyy yyyyyyyy”,这是一个20位的二进制。实际上目前Unicode的最大码位是“0x10FFFF”,写成二进制就是“10000 11111111 11111111”,这是21位的。也就是说两个UTF-16的替代符中包含的数据要组成任意一个Unicode字符还差了一点。从上面二进制可以观察出,如果x和y全是1的话,还需要加上一个“0x10000”(写成二进制是“1 00000000 00000000”)才能让它达到Unicode最大码位。其实在把Unicode值转换到UTF-16时候就是减去这个值后转换过来的。因为双字节的UTF-16已经可以表示所有小于“0x10000”码位的字符了,四字节的UTF-16只要负责好大于它的部分字符就行。
另外,在发这篇文章的时候又遇到一些问题,MySQL的 utf8 general_ci 貌似不支持四字节字符。嘛,这个问题以后再研究啦。

来自:https://www.web-tinker.com/article/20468.html

字符编码-java内部编码探讨

一、Stack Overflow 上的提问

What is the Java’s internal represention for String? Modified UTF-8? UTF-16?
52down voteaccepted

Java uses UTF-16 for the internal text representation

The representation for String and StringBuilder etc in Java is UTF-16

https://docs.oracle.com/javase/8/docs/technotes/guides/intl/overview.html

How is text represented in the Java platform?

The Java programming language is based on the Unicode character set, and several libraries implement the Unicode standard. The primitive data type char in the Java programming language is an unsigned 16-bit integer that can represent a Unicode code point in the range U+0000 to U+FFFF, or the code units of UTF-16. The various types and classes in the Java platform that represent character sequences – char[], implementations of java.lang.CharSequence (such as the String class), and implementations of java.text.CharacterIterator – are UTF-16 sequences.

At the JVM level, if you are using -XX:+UseCompressedStrings (which is default for some updates of Java 6) The actual in-memory representation can be 8-bit, ISO-8859-1 but only for strings which do not need UTF-16 encoding.

http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html

and supports a non-standard modification of UTF-8 for string serialization.

Serialized Strings use UTF-8 by default.

And how many bytes does Java use for a char in memory?

char is always two bytes, if you ignore the need for padding in an Object.

Note: a code point (which allows character > 65535) can use one or two characters, i.e. 2 or 4 bytes.

二、java内部编码是utf-16

代码一:

char c = '放';
System.out.printf("The value of char %c is %d.%n", c, (int) c);

String str = String.valueOf(c);
byte[] bys;
try {
	bys = str.getBytes("Unicode");
	for (int i = 0; i < bys.length; i++) {
		System.out.printf("%X ", bys[i]);
	}
	System.out.println();

	int unicode = (bys[2] & 0xFF) << 8 | (bys[3 & 0xFF]);
	System.out.printf("The unicode value of %c is %d.%n", c, unicode);

} catch (UnsupportedEncodingException e) {
	// TODO Auto-generated catch block
	e.printStackTrace();
}

输出结果如下:

		
The value of char 放 is 25918.
FE FF 65 3E 
The unicode value of 放 is 25918.

代码二:

		
                 String a = new String("放");
		 System.out.println(strTo16(a));

/******************** another method ******************************/

public static String strTo16(String s) {
		String str = "";

		System.out.println(s.length());
		for (int i = 0; i < s.length(); i++) {
			int ch = (int) s.charAt(i);
			String s4 = Integer.toHexString(ch);
			str = str + s4;
		}
		return str;
	}

输出结果如下:

		
1
653e

UTF-16 首先用两个字节,表示Unicode编码,两个字节的情况下,编码和Unicode一模一样。2个字节能编码的字一共有2^16 = 65536个字。这种情况下,一个字符占两个字节。那么 utf-16 如何表示 大于 Unicode编码 65536的字呢。这个时候,就是采用4个字节编码了。

维基百科 Unicode已经超过13万编码了   https://zh.wikipedia.org/wiki/Unicode

三、编码举例

字符串“I am 君山”用 UTF-16 编码,下面是编码结果:

用 UTF-16 编码将 char 数组放大了一倍,单字节范围内的字符,在高位补 0 变成两个字节,中文字符也变成两个字节。从 UTF-16 编码规则来看,仅仅将字符的高位和地位进行拆分变成两个字节。特点是编码效率非常高,规则很简单,由于不同处理器对 2 字节处理方式不同,Big-endian(高位字节在前,低位字节在后)或 Little-endian(低位字节在前,高位字节在后)编码,所以在对一串字符串进行编码是需要指明到底是 Big-endian 还是 Little-endian,所以前面有两个字节用来保存 BYTE_ORDER_MARK 值,UTF-16 是用2 字节或4字节来表示的 UCS-2 或 Unicode 转换格式,通过代理对来访问 BMP 之外的字符编码。

 


参考:https://www.ibm.com/developerworks/cn/java/j-lo-chinesecoding/index.html

字符编码-教程(4)-字节序、本地序、网络序和多字节码元

一、字节序含义


字节序,又称字节顺序,其英文为Byte-Order;另外英文中还可称为Endianness,因此也翻译为端序。

Endianness这个词,源自于1726年Jonathan Swift的名著:Gulliver’s Travels(格列佛游记)。在书中有一个童话故事,大意是指Lilliput小人国的国王下了一道指令,规定其人民在剥水煮蛋时必须从little-end(小端)开始剥。这个规定惹恼了一群觉得应该要从big-end(大端)开始剥的人。事情发展到后来演变成了一场战争。后来,支持小端的人被称为little-endian,反之则被称为big-endian(在英语中后缀“-ian”表示“xx人”之意)。

1980年,Danny Cohen在他的论文“On Holy Wars and a Plea for Peace”中,第一次使用了Big-endian和Little-endian这两个术语,最终它们成为了异构计算机系统之间进行通讯、交换数据时所要考虑的极其重要的一个问题。

(注:所谓异构是指不同架构、不同结构、不同构造等,而这里的异构计算机系统,主要指的是采用不同CPU和/或不同操作系统的计算机系统。)

字节序,具体来说,就是多字节数据(大于一个字节的数据)在计算机中存储、读取时其各个字节的排列顺序。

  • 大端序BE(Big-Endian,也称高尾端序、大尾),小端序LE(Little-Endian,也称为低尾端序、小尾)  。两者是CPU处理多字节数的不同方式。例如“汉”字的Unicode编码是6C49。那么写到文件里时,究竟是将6C写在前面,还是将49写在前面?如果将6C写在前面,就是big endian。如果将49写在前面,就是little endian。
  • BOM: Byte Order Mark, Unicode(后面教程会提到)规范中定义,每一个文件的最前面分别加入一个表示编码顺序的字符,这个字符的名字叫做”零宽度非换行空格“(ZERO WIDTH NO-BREAK SPACE),用FEFF表示。这正好是两个字节,而且FF比FE大1。
  • 如果一个文本文件的头两个字节是FE FF,就表示该文件采用大头方式;如果头两个字节是FF FE,就表示该文件采用小头方式。

二、字节序分类


字节序共分为三种:

  • 大端序BE(Big-Endian,也称高尾端序);
  • 小端序LE(Little-Endian,也称为低尾端序);
  • 中间序ME(Middle-Endian,也称为混合序),不太常用,本文不作介绍。

计算机硬件有两种储存数据的方式:大端字节序(big endian)和小端字节序(little endian)。

举例来说,数值0x2211使用两个字节储存:高位字节是0x22,低位字节是0x11

  • 大端字节序:高位字节在前,低位字节在后,这是人类读写数值的方法。
  • 小端字节序:低位字节在前,高位字节在后,即以0x1122形式储存。

同理,0x1234567的大端字节序和小端字节序的写法如下图。

a) 小端序Little-Endian(低尾端序)

就是低位字节(即小端字节)存放在内存的低地址,而高位字节(即大端字节)存放在内存的高地址。

这是最符合人的直觉思维的字节序(但却不符合人的读写习惯),因为从人的第一观感来说,低位字节的值小,对应放在内存地址也小的地方,也即内存中的低位地址;反之,高位字节的值大,对应放在内存地址大的地方,也即内存中的高位地址。

b) 大端序Big-Endian(高尾端序)

就是高位字节(即大端字节)存放在内存的低地址,低位字节(即小端字节)存放在内存的高地址。

这是最符合人平时的读写习惯的字节序(但却不符合人的直觉思维),因为不用像在Little-Endian中还需考虑字节的高位、低位与内存的高地址、低地址的对应关系,只需把数值按照人通常的书写习惯,从高位到低位的顺序直接在内存中从左到右或从上到下(下图中就是从上到下)按照由低到高的内存地址,一个字节一个字节地填充进去。

 c) 中间序Middle-Endian(混合序Mixed-Endian

混合序具有更复杂的顺序。以PDP-11为例,32位的0x0A0B0C0D被存储为:

混合序较少见,常见的多为大端序和小端序。

三、字节序起因


字节序的选择:因为历史上设计不同计算机系统的人在当时基于各自的理由和原因(这里的理由和原因网上存在着各种不同的说法,但也或许根本就没有具体理由和原因,只是设计人员的个人偏好,甚至是随意决定的),在各自计算机系统的设计上作出了不同的选择。

我一直不理解,为什么要有字节序,每次读写都要区分,多麻烦!统一使用大端字节序,不是更方便吗?

上周,我读到了一篇文章,解答了所有的疑问。而且,我发现原来的理解是错的,字节序其实很简单。

1、首先,为什么会有小端字节序?

答案是,计算机电路先处理低位字节,效率比较高,因为计算都是从低位开始的。所以,计算机的内部处理都是小端字节序。

但是,人类还是习惯读写大端字节序。所以,除了计算机的内部处理,其他的场合几乎都是大端字节序,比如网络传输和文件储存。

2、计算机处理字节序的时候,不知道什么是高位字节,什么是低位字节。它只知道按顺序读取字节,先读第一个字节,再读第二个字节。

如果是大端字节序,先读到的就是高位字节,后读到的就是低位字节。小端字节序正好相反。

理解这一点,才能理解计算机如何处理字节序。

四、字节序与编码单元(码元)


在多字节编码的字符方案中(后面会介绍比如GBK编码,UTF-16编码):

有些编码方案规定了多字节编码中前面的字节是怎么样后面的字节是怎么样(比如GBK编码),这种就叫单字节码元。【计算机读取时,只能用大端序或者小端序的一种,能够解码成功,另一种解码会失败,不会两种都能解码成功】

有些编码方案没有规定前面的字节怎么样后面的字节怎么样,只规定了这个字用了几个字节。就是多字节码元。【可以用大端序或小端序两种解码方法,会产生歧义,所以需要字节序】

总结:只有多字节码元才需要字节序。
以字节为单位就是说它是一个字节一个字节来的,utf16是以一个字一个字来的。学计算机基础的时候应该有说过字节byte、字word、双字double word(dword)之间的关系。一个字节一个字节来就没这个问题,一个字一个字来就要考虑这个字是哪个字节在前。
utf8的标准说了前面的字节是怎么样后面的字节是怎么样,gbk同理。但是utf16是“字”怎么样,这个不同。编码单元简称码元,码元有可能是单字节也有可能是多字节。

五、字节序与数据类型


实际上,int、short、long等数据类型一般是编程语言层面的概念,更进一步而言,这其实涉及到了机器硬件层面(即汇编语言)中的数据类型byte字节、word字、dword双字等在硬件中的表达与处理机制(实质上字节序跟CPU寄存器的位数、存放顺序密切相关)。具体可参看附文:《本质啊本质之一:数据类型的本质》、《寄存器与字、字节》。

【附:本质啊本质之一:数据类型的本质

CSDN博客 博主:band_of_brothers 发表于:2007-10-10 22:20

研究一个层面的问题,往往要从更深的层面找寻答案。这就如C语言与汇编、汇编与机器指令,然而终究要有个底限,这个底限以能使我们心安理得为准,就好比公理之于数学、三大定律之于宏观物理。

在这里就将机器指令作为最后的底限吧,尽管再深入下去还有微指令,但那毕竟是太机器了,可以了。以下所有从C代码编译生成汇编代码用的是命令:cl xxx..c /Fa /Ze。

类型的本质

类型这个概念,好多地方都有讲,但说实话,你真的理解吗?什么是类型?类型是一个抽象的概念还是一个真实的存在?嗯?

开始:

1、“好多相同或相似事物的综合”(辞海)。

2、X86机器的数据类型有byte、word、dword、fword、tword、qword,等等。

3、“给内存块一个明确的名字,就象邮件上的收件人一样。给其一个明确的数据类型,就好象说,邮件是一封信,还是一个包裹。”

4、类型就是一次可以操作的块的大小,就是一个单位,就像克、千克、吨一样。双字一次操作32位;字,一次操作16位;如果没有各种类型,机器只有一个类型单位——字节,那么当需要一个4字节大小的块时,就需要4次操作,而如果有双字这个类型单位,那么只需要一次操作就可以了。

5、类型,是机器层面支持的,不是软的,是硬的,有实实在在的机器码为证。

类型的反汇编

W32dasm反汇编出来的东西,可以看出不同的类型,机器码不同,说明类型是机器硬件级别支持的,不是通过软件实现的,更不是一个抽象的概念。

Opcodes上关于mov的机器码讲的更清楚:

需要说明的是,一些大的类型单位,如qword等,在mov等标准指令里是没有的,在一些特殊指令里才能用到,如浮点指令:fmul qword ptr [0067FB08] 机器码:DC0D08FB6700。】

【附:寄存器与字、字节

字节:记为byte,一个字节由8个比特(bit)组成,可以直接存在一个8位寄存器里

1 0 1 0 1 0 0 1

一个字节

字:记为word,一个字由2个字节(共16比特)组成,可以直接存在一个16位寄存器里

1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0

高位字节        低位字节

一个8位寄存器用2位十六进制数表示,只能存1个字节(1个byte类型的数据)

一个16位寄存器用4位十六进制数表示,可存1个字(1个word类型的数据)或2个字节(2个byte类型的数据)

一个32位寄存器用8位十六进制数表示,可存2个字(1个dword类型的数据或2个word类型的数据)或4个字节(4个byte类型的数据)】

六、网络字节序(网络序)


网络字节顺序(network byte order)是TCP/IP中规定好的一种数据表示格式,它与具体的CPU类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释。网络字节顺序采用big endian排序方式。

不过,容易令人困惑的是,IP协议作为网络层协议,其面向的数据是报文,是没有字节的概念的,也就无关字节序了。因此,英文版wikipedia上说:

In fact, the Internet Protocol defines a standard big-endian network byte order. This byte order is used for all numeric values in the packet headers and by many higher level protocols and file formats that are designed for use over IP.

也就是说,IP协议里的字节序实际上是用在分组头里的数值上的,例如每个分组头会包含源IP地址和目标IP地址,在寻址和路由的时候是需要用到这些值的。

比如,4个字节的32 bit值以下面的次序传输:首先是高位的0~7bit,其次8~15bit,然后16~23bit,最后是低位的24~31bit。这种传输次序称作大端字节序。由于TCP/IP头部中所有的二进制整数在网络中传输时都要求以这种次序,因此它又称作网络字节序。

再比如,以太网头部中2字节的“以太网帧类型”字段,表示是的后面所跟数据帧的类型。对于ARP请求或应答的以太网帧类型来说,在网络传输时,发送的顺序是以大端方式进行的:0x08,0x06。其在内存中的映象如下所示:

内存低地址

————————

0x08 — 高位字节

0x06 — 低位字节

————————

内存高地址

该字段的值为0x0806,也是以大端方式存放在内存中的。

其实,IP报文中的很多数据都是需要做字节序转换的,比如包长度、check sum校验和等,这些值大都是short(16bit)或者long(32bit)型,所以解析IP报文时也需要做网络->主机字节序转换,而生成报文字节流时则需要进行主机->网络字节序转换(计算机中的字节序被称之为主机字节序,简称主机序;相对于网络传输中的字节序被称之为网络字节序,简称网络序)。

为了进行网络字节序与主机字节序的转换,BSD sockets(Berkeley sockets)提供了四个转换的函数:htons、ntohs、htonl、ntohl,其中h是host、n是network、s是short、l是long:

htons把unsigned short类型从主机字节序转换到网络字节序

htonl把unsigned long类型从主机字节序转换到网络字节序

ntohs把unsigned short类型从网络字节序转换到主机字节序

ntohl把unsigned long类型从网络字节序转换到主机字节序

在使用little endian的系统中,这些函数会把字节序进行转换;在使用big endian类型的系统中,这些函数会定义成空宏。

Windows系统API中也提供了类似的转换函数。而在.Net中,网络字节序与主机字节序两者之间的转换,由IPAddress类的静态方法提供:HostToNetworkOrder和NetworkToHostOrder。】

七、本地序(主机序)


其实这个就是指本机操作系使用的字节序。

Intel和AMD的X86平台,以及DEC(Digital Equipment Corporation,后与Compaq合并,之后Compaq又与HP合并)采用的是Little-Endian,而像IBM、Sun的SPARC采用的就是Big-Endian。有的嵌入式平台是Big-Endian的。JAVA字节序也是Big-Endian的。

当然,这不代表所有情况。有的CPU即能工作于小端,又能工作于大端,比如ARM、Alpha、摩托罗拉的Power PC、SPARC V9、MIPS、PA-RISC和IA64等体系结构(具体情形可参考处理器手册),其字节序是可切换的,这种可切换的特性可以提高效率或者简化网络设备和软件的逻辑。

这种可切换的字节序被称为Bi-Endian(前缀“Bi-”表示“双边的、二重的、两个的”),用于硬件上意指计算机存储时具有可以使用两种不同字节序中任意一种的能力。具体这类CPU是大端还是小端,和具体设置有关。如Power PC可支持Little-Endian字节序,但其默认配置为Big-Endian字节序。

一般来说,大部分用户的操作系统,如windows、FreeBsd、Linux是Little-Endian的;少部分,如Mac OS是Big-Endian的。

具体参见下表:

七、字节序处理模拟


举例来说,处理器读入一个16位整数。如果是大端字节序,就按下面的方式转成值。

x = buf[offset] * 256 + buf[offset+1];

上面代码中,buf是整个数据块在内存中的起始地址,offset是当前正在读取的位置。第一个字节乘以256,再加上第二个字节,就是大端字节序的值,这个式子可以用逻辑运算符改写。

x = buf[offset]<<8 | buf[offset+1];

上面代码中,第一个字节左移8位(即后面添8个0),然后再与第二个字节进行或运算。

如果是小端字节序,用下面的公式转成值。

x = buf[offset+1] * 256 + buf[offset];

32位整数的求值公式也是一样的。

/* 大端字节序 */
i = (data[3]<<0) | (data[2]<<8) | (data[1]<<16) | (data[0]<<24);

/* 小端字节序 */
i = (data[0]<<0) | (data[1]<<8) | (data[2]<<16) | (data[3]<<24);

八、字节序与联通的故事

讲到这里,我们再顺便说说一个很著名的奇怪现象:当你在 windows 的记事本里新建一个文件,输入”联通”两个字之后,保存,关闭,然后再次打开,你会发现这两个字已经消失了,代之的是几个乱码!呵呵,有人说这就是联通之所以拼不过移动的原因。

其实这是因为GB2312编码与UTF8编码产生了编码冲撞的原因。

当一个软件打开一个文本时,它要做的第一件事是决定这个文本究竟是使用哪种字符集的哪种编码保存的。软件一般采用三种方式来决定文本的字符集和编码:

检测文件头标识,提示用户选择,根据一定的规则猜测

最标准的途径是检测文本最开头的几个字节,开头字节 Charset/encoding,如下表:

1 EF BB BF UTF-8 
2    
3 FF FE UTF-16/UCS-2, little endian 
4    
5 FE FF UTF-16/UCS-2, big endian 
6    
7 FF FE 00 00 UTF-32/UCS-4, little endian. 
8    
9 00 00 FE FF UTF-32/UCS-4, big-endian.

当你新建一个文本文件时,记事本的编码默认是ANSI(代表系统默认编码,在中文系统中一般是GB系列编码), 如果你在ANSI的编码输入汉字,那么他实际就是GB系列的编码方式,在这种编码下,”联通”的内码是:

1 c1 1100 0001 
2    
3 aa 1010 1010 
4    
5 cd 1100 1101 
6    
7 a8 1010 1000

注意到了吗?第一二个字节、第三四个字节的起始部分的都是”110″和”10″,正好与UTF8规则里的两字节模板是一致的,

于是当我们再次打开记事本时,记事本就误认为这是一个UTF8编码的文件,让我们把第一个字节的110和第二个字节的10去掉,我们就得到了”00001 101010″,再把各位对齐,补上前导的0,就得到了”0000 0000 0110 1010″,不好意思,这是UNICODE的006A,也就是小写的字母”j”,而之后的两字节用UTF8解码之后是0368,这个字符什么也不是。这就是只有”联通”两个字的文件没有办法在记事本里正常显示的原因。

而如果你在”联通”之后多输入几个字,其他的字的编码不见得又恰好是110和10开始的字节,这样再次打开时,记事本就不会坚持这是一个utf8编码的文件,而会用ANSI的方式解读之,这时乱码又不出现了。

造成这个问题的关键原因是:打开记事本写联通两字时,保存好时,默认是ASCII编码,打开文件时,没有BOM作为判断依据,所以系统默认 当成utf-8 解码了。记住:平时我们保存文件时 经常能看到 save as utf-8 和 save as utf-8 + BOM 两种选择。现在应该知道区别了吧。话说,Windows 记事本里直接 打联通 然后 另存为 utf-8  。等下次打开时,都没有问题的。估计是,系统偏向 utf-8 解码的原因。因为前面 ascii 编码后,系统 犹豫 用ASCII 解码还是 utf-8解码时,倾向后者,所以没有用 BOM标记的 utf-8 文件,解码也是正确的。

补充:


当读取(或写入)需要连续读取(或写入)超过一个字节数据时才需要考虑字节序问题。

“计算机的内部处理都是小端字节序”不正确……

虽然x86是小端的,不过也有很多CPU是大端的(比如powerpc)。另外小端虽然对加法器比较友好,但除法还是大端更合适,所以这种取舍其实只是一些历史问题吧……

小端模式 :强制转换数据不需要调整字节内容,1、2、4字节的存储方式一样。
大端模式 :符号位的判定固定为第一个字节,容易判断正负。

小端模式更适合系统内部,大端模式更适合网络数据传递,加上一些历史引领的原因,
导致现在两种字节序方式并存。


参考:

http://www.ruanyifeng.com/blog/2016/11/byte-order.html

https://www.cnblogs.com/benbenalin/p/6918634.html

https://blog.csdn.net/band_of_brothers/article/details/1819228

 

字节序问题:

https://baike.baidu.com/item/%E5%AD%97%E8%8A%82%E5%BA%8F/1457160

http://imweb.io/topic/57fe263b2a25000c315a3d8a

https://www.cnblogs.com/benbenalin/p/6918634.html

Request body 问题:

https://www.cnblogs.com/chyu/p/5517788.html

字符编码-教程(5)-简体汉字编码与半角和圆角

一、单字节编码与多字节编码


1、单字节编码(SBCS)

用一个字节来编码的,比如前面提到的EBCDIC,ASCII ,EASCII,ISO-8859系列都是单字节编码。

ASCII 码

学过计算机的人都知道 ASCII 码,总共有 128 个,用一个字节的低 7 位表示,0~31 是控制字符如换行回车删除等;32~126 是打印字符,可以通过键盘输入并且能够显示出来。

ISO-8859-1

128 个字符显然是不够用的,于是 ISO 组织在 ASCII 码基础上又制定了一些列标准用来扩展 ASCII 编码,它们是 ISO-8859-1~ISO-8859-15,其中 ISO-8859-1 涵盖了大多数西欧语言字符,所以应用的最广泛。ISO-8859-1 仍然是单字节编码,它总共能表示 256 个字符。

2、多字节编码(MBCS)

用多个字节来编码的,就是多字节编码。比如下面介绍的编码方案都是。

二、简体汉字编码

英文字母再加一些其他标点字符之类的也不会超过256个,用一个字节来表示一个字符就足够了(2^8 = 256)。但其他一些文字不止这么多字符,比如中文中的汉字就多达10多万个,一个字节只能表示256个字符,肯定是不够的,因此只能使用多个字节来表示一个字符。

1、GB2312

等到中国人们得到计算机时,单字节的ASCII码 及其拓展码,没有很多状态来保存 6000多个常用汉字。但是这难不倒智慧的中国人民,我们不客气地把那些127号之后的奇异符号们直接取消掉, 规定:一个小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,前面的一个字节(他称之为高字节)从0xA1用到0xF7,后面一个字节(低字节)从0xA1到0xFE,这样我们就可以组合出大约7000多个简体汉字了。在这些编码里,我们还把数学符号、罗马希腊的字母、日文的假名们都编进去了,连在 ASCII 里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的“全角”字符,而原来在127号以下的那些就叫“半角”字符了。

中国人民看到这样很不错,于是就把这种汉字方案叫做 “GB2312”。GB2312 是对 ASCII 的中文扩展。

它的全称是《信息交换用汉字编码字符集 基本集》,它是双字节编码。它的第一个字节为128-255。系统可以据此判断,若第一个字节大于127,则把与该字节后紧接着的一个字节结合起来共两个字节组成一个中文字符。这种由多个字节存储一个字符的字符集叫多字节字符集(MultiByte Charsets),对应的象ASCII这种用一个字节存储一个字符的字符集叫单字节字符集(SingleByte Charsets)。在GB2312字符集中,ASCII字符仍然用一个字节存储,换句话说该ASCII是该字符集的子集。=========================================================

GB2312编码方案,即《信息交换用汉字编码字符集——基本集》,是由中国国家标准总局于1980年发布、1981年5月1日开始实施的一套国家标准,标准号为GB2312-1980。其中收录了6763个常用汉字和682个其它符号(6763+682=7445),并将该字符集分为94个区,每个区94位,每个位对应一个字符或零个字符(94×94=8836,8836-7745=1391说明有1391个位置是空的)。

GB2312编码适用于汉字处理、汉字通信等系统之间的信息交换,通行于中国大陆;新加坡等地也采用此编码。中国大陆几乎所有的中文系统和国际化的软件都支持GB2312。

区号 内容
01-09区 特殊符号,如:标点符号、数字序列、全角字符、日语假名、拼音音标等
10-15区
16-55区 一级汉字,按拼音排序
56-87区 二级汉字,按部首/笔画排序
88-94区

如果将GB2312看做是一种“字符集”,则可以用多种编码方式对它进行编码。比如“区位码”就是对GB2312字符集最简单的一种编码方式,它直接使用区号和位号组成一个编码值(例如:GB2312字符集中的第一个汉字“啊”,它的区号为16,位号为01,它的区位码就是1601)。这里GB2312 编码列出了GB2312区位码编码。

但通常,人们所说的GB2312指的是一种编码(并且不是指区位码),它是指通常采用EUC方法对GB2312字符集中的“区”和“位”进行处理后的编码。EUC方法的处理方式:区号和位号分别加上0xA0,结果分别作为GB2312编码的两个字节的值(例如:“啊”字的区号和位号分别为16和01,即十六进制0x10和0x01,分别加0xA0得到编码0xB0A1),这样做是为了兼容ASCII编码(GB2312编码的两字节都大于ASCII码的最大值)。这里GB2312简体中文编码表列出了通常更常用的GB2312编码。

GB 2312标准共收录6763个汉字,其中一级汉字3755个,二级汉字3008个;同时,除了汉字,GB 2312还收录了包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的682个字符

可能是出于显示上视觉美观的考虑,除汉字之外的682个字符中,甚至包括了ASCII里本来就有的数字、标点、英文字母等字符。也就是说,这些ASCII里原来就有的单字节编码的字符,又再编了两个字节长的GB2312编码版本。这682个字符就是常说的“全角”字符,而这682个字符中所对应的ASCII字符就被称之为“半角”字符

但是中国的汉字太多了,我们很快就就发现有许多人的人名没有办法在这里打出来,特别是某些很会麻烦别人的国家领导人。于是我们不得不继续把 GB2312 没有用到的码位找出来老实不客气地用上。


【附:全角、半角

全角字符是中文显示及双字节中文编码的历史遗留问题。

早期的点阵显示器上由于像素有限,原先ASCII西文字符的显示宽度(比如8像素的宽度)用来显示汉字有些捉襟见肘(实际上早期的针式打印机在打印输出时也存在这个问题),因此就采用了两倍于ASCII字符的显示宽度(比如16像素的宽度)来显示汉字。

这样一来,ASCII西文字符在显示时其宽度为汉字的一半。或许是为了在西文字符与汉字混合排版时,让西文字符能与汉字对齐等视觉美观上的考虑,于是就设计了让西文字母、数字和标点等特殊字符在外观视觉上也占用一个汉字的视觉空间(主要是宽度),并且在内部存储上也同汉字一样使用2个字节进行存储的方案。这些与汉字在显示宽度上一样的字符就被称之为全角字符。

而原来ASCII中的西文字符由于在外观视觉上仅占用半个汉字的视觉空间(主要是宽度),并且在内部存储上使用1个字节进行存储,相对于全角字符,因而被称之为半角字符。

后来,其中的一些全角字符因为比较有用,就得到了广泛应用(比如全角的逗号“,”、问号“?”、感叹号“!”、空格“ ”等,这些字符在输入法中文输入状态下的半角与全角是一样的,英文输入状态下全角跟中文输入状态一样,但半角大约为全角的二分之一宽),专用于中日韩文本,成为了标准的中日韩标点字符。而其它的许多全角字符则逐渐失去了价值(现在很少需要让纯文本的中文和西文字字对齐了),就很少再用了。

现在全球字符编码的事实标准是Unicode字符集及基于此的UTF-8UTF-16等编码实现方式。Unicode吸纳了许多遗留(legacy)编码,并且为了兼容性而保留了所有字符。因此中文编码方案中的这些全角字符也保留下来了,而国家标准也仍要求字体和软件都支持这些全角字符。

不过,半角和全角字符的关系在UTF-8UTF-16等中不再是简单的1字节和2字节的关系了。具体参见后文。】


2、GB13000

GB2312-1980共收录6763个汉字,覆盖了中国大陆99.75%的使用频率,基本满足了汉字的计算机处理需要。但对于人名、古汉语等方面出现的罕用字、生僻字,GB2312不能处理,如部分在GB2312-1980推出以后才简化的汉字(如“啰”)、部分人名用字(如前总理朱镕基的“镕”字)、台湾及香港使用的繁体字、日语及朝鲜语汉字等,并未收录在内。

1、为了便于多个文种的同时处理,国际标准化组织下属编码字符集工作组制定了新的编码字符集标准——ISO/IEC 10646(与统一联盟制定的Unicode标准兼容,两者的关系详见后文)。

该标准第一次颁布是在1993年,当时只颁布了其第一部分,即ISO/IEC 10646.1:1993,收录中国大陆、台湾、日本及韩国通用字符集的汉字,总共20,902个。制定这个标准的目的是对世界上的所有字符统一编码,以实现世界上所有字符在计算机上的统一处理。

2、中国相应的国家标准是GB13000.1-93《信息技术通用多八位编码字符集(UCS) 第一部分:体系结构与基本多文种平面》。GB 13000.1-93”等同于Unicode 1.1版本( 即 ISO/IEC 10646.1:1993)。

2010年又发布了替代标准——GB13000-2010《信息技术通用多八位编码字符集(UCS)》,此标准等同于国际标准ISO/IEC 10646:2003《信息技术通用多八位编码字符集(UCS)》。

GB13000与国际标准ISO/IEC 10646及Unicode标准目前在基本平面(即BMP,详见后文)上保持一致。

GB13000建立了一个全新的编码体系。ISO/IEC 10646被称作”多八位”编码字符集,是因为它采用四个”八位”(即8 bit)编码。这四个字节被用来分别表示组、平面、行和字位。
GB2312规定的汉字为常用汉字,包括简化汉字三千余个。由于我国汉字数量巨大(约10万字),我国又陆续增加了六个辅助集。其中,基本集与第二、第四辅助集是简化汉字集,第一(即GB 12345)、第三、第五辅助集是繁体集,且基本集与第一、第二与第三、第四与第五辅助集分别有简、繁体字一一对应关系,(个别简、繁关系为一对多的汉字除外)。第七辅助集汉字的来源是GB13000.1的CJK统一汉字部分,为日本、韩国和台湾地区使用的汉字。七个字符集包含汉字共计约49,000字(简化字和繁体字分别编码)。
可以看出,GB13000的总编码位置高达2,147,483,648个(128组×256平面×256行×256字位)。目前实现的是00组的00平面,称为”基本多文种平面”(Basic Multilingual Plane, BMP),编码位置65536个。(由于基本多文种平面所有字符代码的前两个字节都是0(00组00平面XX行XX字位),因此,目前在默认情况下,基本多文种平面按照两字节处理。

3、GBK

1、话说1993年,Unicode 1.1推出时,收录了两万多个中日韩通用字符集的汉字,同一年我国也定制了相应的GB13000,但是一直未被业界采用。后来微软利用了GB2312中未使用的编码空间,并且收录了GB13000中的全部字符,从而定制了GBK编码(虽然收录了GB13000的全部字符,但是编码方式并不相同),并且实现于Windows95中文版中。GBK自身并非国家标准,不过1995年由国标局等机构确定为“技术规范指导性文件”。【也有一说:国家主动 利用GB2312-1980未使用的码点空间,收录GB13000.1-1993的全部字符,于1995年又发布了《汉字内码扩展规范(GBK)》(Guo-Biao Kuozhan国家标准扩展码,是根据GB13000.1-1993,对GB2312-1980的扩展;英文全称Chinese Internal Code Specification)。】

2、不过,虽然GBK收录了GB13000.1-1993的全部字符,但编码方式并不相同。

GBK跟GB2312一样是双字节编码,然而,GBK只要求第一个字节即高字节是大于127就固定表示这是一个汉字的开始(0~127当然表示的还是ASCII字符),不再要求第二个字节即低字节也必须是127号之后的编码。这样,作为同样是双字节编码的GBK才可以收录比GB2312更多字符。

GBK字符集向后完全兼容GB2312,还支持GB2312-1980不支持的部分中文简体、中文繁体、日文假名,还包括希腊字母以及俄语字母等字母(不过这个编码不支持韩国文字,也是其在实际使用中与Unicode编码相比欠缺的部分),共收录汉字21003个、符号883个,并提供1894个造字码位,简、繁体字融于一体。

GBK全称叫《汉字内码扩展规范》,是国家技术监督局为 windows95 所制定的新的汉字内码规范,它的出现是为了扩展 GB2312,加入更多的汉字,它的编码范围是 8140~FEFE(去掉 XX7F)总共有 23940 个码位,它能表示 21003 个汉字,还包含繁体中文字,日文字符和朝鲜字符。它的编码是和 GB2312 兼容的,也就是说用 GB2312 编码的汉字可以用 GBK 来解码,并且不会有乱码。值得注意的是GBK只是一个规范而不是国家标准,新的国家标准是GB18030-2000,它是比GBK包含字符更多的字符集。

GBK的编码框架(Code Scheme):其中GBK1收录除GB2312符号外的增补符号,GBK2收录GB2312汉字,GBK3收录CJK汉字,GBK4收录CJK汉字和增补汉字,GBK5为非中文字符集,UDC为用户自定义字符区

3、微软早在Windows 95简体中文版中就采用了GBK编码,也就是对微软内部之前的CP936字码表(Code Page 936)进行了扩展(之前CP936和GB2312-1980一模一样)。

微软的CP936通常被视为等同于GBK,连IANA(Internet Assigned Numbers Authority互联网号码分配局)也以“CP936”为“GBK”之别名。

但事实上比较起来,GBK定义之字符较CP936多出95个(15个非汉字及80个汉字),皆为当时未收入ISO 10646 / Unicode的符号。

【注:有说是微软在GB2312的基础上扩展制订了GBK,然后GBK才成为“国家标准”(也有说GBK不是国家标准,只是“技术规范指导性文件”);但网上也有资料说是先有GBK(由全国信息技术标准化技术委员会于1995年12月1日制定),然后微软才在其内部所用的CP936代码页中以GBK为参考进行了扩展。】

简单总结:GBK是从GB2312扩展而来的,支持繁体,并且兼容GB2312。例如:“啊”字的GB2312编码和GBK编码都为0xB0A1。


5、GB18030

1、中国国家质量技术监督局于2000年3月17日推出了GB18030-2000标准,以取代GBK。GB18030-2000除保留全部GBK编码汉字之外,在第二字节再度进行扩展,增加了大约一百个汉字及四位元组编码空间。

GB18030-2000《信息交换用汉字编码字符集基本集的补充》是我国继GB2312-1980和GB13000-1993之后最重要的汉字编码标准,是我国计算机系统必须遵循的基础性标准之一。

2、2005年,GB18030编码方案又进行了扩充,于是又有了GB18030-2005《信息技术中文编码字符集》。如前所述,GB18030-2000是GBK的取代版本,它的主要特点是在GBK基础上增加了CJK中日韩统一表意文字扩充A的汉字;而GB18030-2005的主要特点是在GB18030-2000基础上又增加了CJK中日韩统一表意文字扩充B的汉字。

GB18030-2005全称是《信息交换用汉字编码字符集》,是我国的强制标准,它可能是单字节、双字节或者四字节编码,它的编码与 GB2312 编码兼容,这个虽然是国家标准,但是实际应用系统中使用的并不广泛。

GB2312和GBK都是用两个字节来编码的,就算用完所有的位(256*256=65536)也不够为所有的汉字编码。于是就有了目前最新的GB18030,它采用类似UTF-8的编码方式进行编码(每个字符的编码可以是1、2或4个字节),拥有上百万个编码空间,足以支持中日韩三国所有汉字,并且还可以支持国内少数民族的文字。

微软也为GB18030定义了代码页(Code page):CP54936,但是这个代码页实际上并没有真正使用(在Windows 7的“控制面板”-“区域和语言”-“管理”-“非Unicode程序的语言”中没有提供选项;在Windows cmd命令行中可通过命令chcp 54936更改,之后在cmd可显示中文,但却不支持中文输入)。

各汉字(中文字符)编码方案之间的关系

三、汉字编码小结:双字节编码(DBCS)

中国的程序员们看到这一系列汉字编码的标准是好的,于是通称他们叫做 “DBCS”(Double Byte Charecter Set 双字节字符集)【当然不包括GB18030编码方案】。在DBCS系列标准里,最大的特点是两字节长的汉字字符和一字节长的英文字符并存于同一套编码方案里,因此他们写的程序为了支持中文处理,必须要注意字串里的每一个字节的值,如果这个值是大于127的,那么就认为一个双字节字符集里的字符出现了。那时候凡是受过加持,会编程的计算机僧侣们都要每天念下面这个咒语数百遍:“一个汉字算两个英文字符!一个汉字算两个英文字符……”【其实这句话的本意是:在双字节中文编码中,兼容了ASCII,导致英文编码只占一个字节,中文编码占二个字节,所以一个汉字算两个英文】

四、繁体编码BIG5与日文编码SJIS

维基上说:Big5是由台湾财团法人信息产业策进会为五大中文套装软件(并因此得名Big-5)所设计的中文共通内码,在1983年12月完成公告。那个之前还没有繁体字编码,GB2312又不含繁体字,因此才有了Big-5。

传说Big5产生前,有着“中文电脑之父”之称的朱邦复也设计了一套中文编码,可容纳50000多字(包括繁体和简体),但是未被采纳。

我国的台湾地区使用的文字是繁体字,其字符集是BIG5,而日本采用的字符集则是SJIS。它们的编码方法与GB2312类似,它们的ASCII字符部分是兼容的,但扩展部分的编码则是不兼容的,比如这几种字符集中都有”中文”这两个字符,但他们在各自的字符集中的编码并不相同,这就是用GB2312写成的网页用BIG5浏览时,看到的是乱糟糟的信息的原因。(BIg5也是双字节编码)

六、CJK中日韩统一表意文字

1、CJK指的是中日韩统一表意文字(CJK Unified Ideographs),也称统一汉字(Unihan),目的是要把分别来自中文(包含壮文)、日文、韩文、越文中,起源相同、本义相同、形状一样或稍异的表意文字在Unicode标准及ISO/IEC 10646标准内赋予相同的码点值。(Unicode标准及ISO/IEC 10646标准后文有详细解释)

CJK是中文(Chinese)、日文(Japanese)、韩文(Korean)三国文字英文首字母的缩写。顾名思义,它能够支持这三种文字,但实际上,CJK能够支持包括中文(包含壮文)、日文、韩文、越文在内的多种亚洲双字节文字。

2、所谓“起源相同、本义相同、形状一样或稍异的表意文字”,主要为汉字,包括繁体字、简体字;但也有仿汉字,包括方块壮字、日本汉字(漢字/かんじ)、韩国汉字(漢字/한자)、越南的喃字(?喃/Chữ Nôm)与儒字(?儒/Chữ Nho)等。

此计划原本只包含中文、日文及韩文中所使用的汉字和仿汉字,统称中日韩(CJK)统一表意文字(Unified Ideographs)。后来,此计划才加入了越南文的喃字,所以又合称为中日韩越(CJKV)统一表意文字。

好吧,它并不是一种编码方式,而是中日韩统一表意文字(CJK Unified Ideographs)。在Unicode中,收集各国相同的汉字,并且进行合并相同的编码点(code point)上,可以避免相同文字重复编码,浪费编码空间。


参考编码:

http://www.mytju.com/classcode/tools/encode_gb2312.asp

https://www.qqxiuzi.cn/bianma/Unicode-UTF.php

https://www.qqxiuzi.cn/zh/hanzi-big5-bianma.php

参考编码:

http://www.cnblogs.com/malecrab/p/5300497.html

https://www.cnblogs.com/duguxiaobiao/p/9128817.html

https://www.cnblogs.com/malecrab/p/5300503.html

https://blog.csdn.net/garybrother/article/details/3988741

http://zzqhost.com/2017/07/05/%E5%AD%97%E7%AC%A6%E7%BC%96%E7%A0%81/