关于Unicode 和 UTF-8 的思考

参考资料  

http://baike.baidu.com/view/25412.htm

http://blog.sina.com.cn/s/blog_4dfbd13401000co5.html

http://zh.wikipedia.org/wiki/Unicode

http://www.cnblogs.com/graphics/archive/2011/04/22/2010662.html


其中有几段非常重要的话,我摘出来了,分享给大家,另外这几篇参考文献非常重要,请一定读一下。

Unicode的编码和实现

大概来说,Unicode编码系统可分为编码方式和实现方式两个层次。

[编辑]编码方式

统一码的编码方式与ISO 10646通用字符集概念相对应。目前实际应用的统一码版本对应于UCS-2,使用16的编码空间。也就是每个字符占用2个字节。这样理论上一共最多可以表示216(即65536)个字符。基本满足各种语言的使用。实际上当前版本的统一码并未完全使用这16位编码,而是保留了大量空间以作为特殊使用或将来扩展。

上述16位统一码字符构成基本多文种平面。最新(但未实际广泛使用)的统一码版本定义了16个辅助平面,两者合起来至少需要占据21位的编码空间,比3字节略少。但事实上辅助平面字符仍然占用4字节编码空间,与UCS-4保持一致。未来版本会扩充到ISO 10646-1实现级别3,即涵盖UCS-4的所有字符。UCS-4是一个更大的尚未填充完全的31位字符集,加上恒为0的首位,共需占据32位,即4字节。理论上最多能表示231个字符,完全可以涵盖一切语言所用的符号。

基本多文种平面的字符的编码为U+hhhh,其中每个h 代表一个十六进制数字,与UCS-2编码完全相同。而其对应的4字节UCS-4编码后两个字节一致,前两个字节则所有位均为0。

关于统一码和ISO 10646及UCS的详细关系 ,请参看通用字符集

[编辑]实现方式

Unicode的实现方式不同于编码方式。一个字符的Unicode编码是确定的。但是在实际传输过程中,由于不同系统平台的设计不一定一致,以及出于节省空间的目的,对Unicode编码的实现方式有所不同。Unicode的实现方式称为Unicode转换格式(Unicode Transformation Format,简称为UTF)

例如,如果一个仅包含基本7位ASCII字符的Unicode文件,如果每个字符都使用2字节的原Unicode编码传输,其第一字节的8位始终为0。这就造成了比较大的浪费。对于这种情况,可以使用UTF-8编码,这是一种变长编码,它将基本7位ASCII字符仍用7位编码表示,占用一个字节(首位补0)。而遇到与其他Unicode字符混合的情况,将按一定算法转换,每个字符使用1-3个字节编码,并利用首位为0或1进行识别。这样对以7位ASCII字符为主的西文文档就大大节省了编码长度(具体方案参见UTF-8)。类似的,对未来会出现的需要4个字节的辅助平面字符和其他UCS-4扩充字符,2字节编码的UTF-16也需要通过一定的算法进行转换。

再如,如果直接使用与Unicode编码一致(仅限于BMP字符)的UTF-16编码,由于每个字符占用了两个字节,在麦金塔电脑 (Mac)机和个人电脑上,对字节顺序的理解是不一致的。这时同一字节流可能会被解释为不同内容,如某字符为十六进制编码4E59,按两个字节拆分为4E和59,在Mac上读取时是从低字节开始,那么在Mac OS会认为此4E59编码为594E,找到的字符为“奎”,而在Windows上从高字节开始读取,则编码为U+4E59的字符为“乙”。就是说在Windows下以UTF-16编码保存一个字符“乙”,在Mac OS环境下打开会显示成“奎”。此类情况说明UTF-16的编码顺序若不加以人为定义就可能发生混淆,于是在UTF-16编码实现方式中使用了大端序(Big-Endian, 简写为UTF-16 BE)、小端序(Little-Endian,简写为UTF-16 LE)的概念,以及可附加的字节顺序记号解决方案,目前在PC机上的Windows系统和Linux系统对于UTF-16编码默认使用UTF-16 LE。(具体方案参见UTF-16

此外Unicode的实现方式还包括UTF-7PunycodeCESU-8SCSUUTF-32GB18030等,这些实现方式有些仅在一定的国家和地区使用,有些则属于未来的规划方式。目前通用的实现方式是UTF-16小端序(LE)、UTF-16大端序(BE)和UTF-8。在微软公司Windows XP附带的记事本(Notepad)中,“另存为”对话框可以选择的四种编码方式除去非Unicode编码的ANSI(对于英文系统即ASCII编码,中文系统则为GB2312Big5编码) 外,其余三种为“Unicode”(对应UTF-16 LE)、“Unicode big endian”(对应UTF-16 BE)和“UTF-8”。


UTF-8字符集

如果UNICODE 字符由2个 字节表示,则编码成UTF-8很可能需要3个 字节。而如果UNICODE 字符由4个字节表示,则编码成UTF-8可能需要6个字节。用4个或6个 字节去编码一个UNICODE 字符可能太多了,但很少会遇到那样的UNICODE字符。 UTF-8转换表表示如下:
UNICODE
bit数
UTF-8
byte数
备注
0000 0000 ~
0000 007F
0~7
0XXX XXXX
1
0000 0080 ~
0000 07FF
8~11
110X XXXX
10XX XXXX
2
0000 0800 ~
0000 FFFF
12~16
1110 XXXX
10XX XXXX
10XX XXXX
3
基本定义范围:0~FFFF
0001 0000 ~
001F FFFF
17~21
1111 0XXX
10XX XXXX
10XX XXXX
10XX XXXX
4
Unicode6.1定义范围:0~10 FFFF
0020 0000 ~
03FF FFFF
22~26
1111 10XX
10XX XXXX
10XX XXXX
10XX XXXX
10XX XXXX
5
0400 0000 ~
7FFF FFFF
27~31
1111 110X
10XX XXXX
10XX XXXX
10XX XXXX
10XX XXXX
10XX XXXX
6
实际表示ASCII 字符的UNICODE字符,将会编码成1个 字节,并且UTF-8表示与ASCII字符表示是一样的。所有其他的UNICODE 字符转化成UTF-8将需要至少2个 字节。每个 字节由一个 换码序列开始。第一个 字节由唯一的 换码序列,由n位连续的1加一位0组成, 首字节连续的1的个数表示字符编码所需的字节数。
示例
UNICODE uCA(11001010) 编码成UTF-8将需要2个 字节
uCA -> C3 8A
UNICODE uF03F (11110000 00111111) 编码成UTF-8将需要3个 字节:
u F03F -> EF 80 BF
Unicode 16进制
Unicode 2进制
bit数
UTF-8 2进制
UTF-8 16进制
CA
1100 1010
8
1100 0011  1000 1010
C3 8A
F0 3F
1111 0000 0011 1111
16
1110 1111  1000 0000  1011 1111
EF 80 BF

由上分析可以看到,UNICODE到UTF-8的转换就是先确定编码所需要的UTF-8编码字节数,然后用UNICODE编码位从低位到高位依次填入上面表示为x的位上,不足的高位以0补充。


------------------------------------------------------------------------------------------

1. 注意:我加红的两个关键词,什么是编码方式?什么又是实现方式?

我认为如果把编码方式,换个词来表述则更为清楚,叫编码方案,通俗解释就是对于汉字“兔” 对应的16进制数值为 0x5154,转换为十进制就是20820 ,也就是说传递信息的双方只要知道你传给我的这个字符对应数值20820,那个也就明白是兔子的兔这个字

实现方式常见的有UTF-8 UTF-16 UTF-32

字符能够被存储,传输,一定会以某种形态存在在内存中,或者是光电信号的形式存在在光缆,网线中。这种形态我们可以理解为实现方式

其中UTF-16,UTF-32 是等长编码,所有字符在内存中占用相同字节数。对于UTF-16,每个字符占用2个字节,UTF-32,每个字符占用4个字节

其中UTF-8是不等长编码, ASCII字符使用一个字节,中文通常是3个字节

下面来做个试验

首先注意上文引用百度百科中,UTF-8转换表标为红色的部分

0000 0800 ~
0000 FFFF
12~16
1110  XXXX
10 XX XXXX
10 XX XXXX
3
基本定义范围:0~FFFF
汉字“兔”对应的Unicode编码的数值为20820,转换为16进制为0x5154 ,在0x0800到0xFFFF之间,那么编码时应该才用这种方式,

黑色加粗部分对于传递这个字符的数值信息,没有意义。

0x5154 展开成二进制


转换完成为0xE58594

下面用程序验证一下

>>> ss = u"兔"
>>> ss2 = ss.encode("utf-8")
>>> len(ss2)
3
>>> ss2
'\xe5\x85\x94'
>>> 

反过来验证一下

package cn.zw;

import java.io.UnsupportedEncodingException;

public class Test {

	public static void main(String[] args) throws UnsupportedEncodingException {
		System.out.println("\u5154");
		byte tt[] ={(byte)0xe5,(byte)0x85,(byte)0x94};
		System.out.println(new String(tt,"UTF-8"));
	}

}
输出结果:


做完上面的实验,我们了解到,对于中文,起到真正作用的(表达字符对应数值信息的)只有两个字节,所以对于东亚的语言,每传一个字符,都要多发一个字节。

对于英文而言,由于数值范围在0x00 ~ 0x7F ,所以每次只用发一个字节就行了

UNICODE
bit数
UTF-8
byte数
备注
0000 0000 ~
0000 007F
0~7
0XXX XXXX
1

唏嘘一下,制定标准就是好,占便宜。

2. 最后谈谈我对unicode编码在java以及python 中具体应用的理解

资料来源:http://docs.oracle.com/javase/7/docs/api/java/lang/String.html

String represents a string in the UTF-16 format in which supplementary characters are represented by surrogate pairs (see the section Unicode Character Representations in the Character class for more information). Index values refer to char code units, so a supplementary character uses two positions in a String.

翻译成中文:

String 表示一个 UTF-16 格式的字符串,其中的增补字符代理项对 表示(有关详细信息,请参阅Character 类中的Unicode 字符表示形式)。索引值是指 char 代码单元,因此增补字符在String 中占用两个位置。 


资料来源:http://docs.python.org/2/c-api/unicode.html?highlight=unicode#Py_UNICODE

These are the basic Unicode object types used for the Unicode implementation in Python:

Py_UNICODE

This type represents the storage type which is used by Python internally as basis for holding Unicode ordinals. Python’s default builds use a 16-bit type for Py_UNICODE and store Unicode values internally as UCS2. It is also possible to build a UCS4 version of Python (most recent Linux distributions come with UCS4 builds of Python). These builds then use a 32-bit type for Py_UNICODE and store Unicode data internally as UCS4. On platforms where wchar_t is available and compatible with the chosen Python Unicode build variant, Py_UNICODE is a typedef alias for wchar_t to enhance native platform compatibility. On all other platforms, Py_UNICODE is a typedef alias for either unsignedshort (UCS2) or unsigned long (UCS4).

Note that UCS2 and UCS4 Python builds are not binary compatible. Please keep this in mind when writing extensions or interfaces.

这段文字我不翻译了,里面有些专业术语,需要查一下才能明白


特别说明:以下是我的理解,不一定正确,想了解具体情况,还是得看源码。

总结一下: 

Java的内部对字符串的实现采用的是UTF-16 (1个字符占两个字节,包括中文和英文)

python 内部对字符串的实现也采用UTF-16,但是文档中也同时说明也可能在未来使用UTF-32 ( 1个字符,占4个字节,包括中文和英文)

未来为了收录更多不同字符,同时CPU处理起来更方便,而采用UTF-32,是很有可能的。


1)java 虚拟机为什么采用了Unicode编码?

因为Unicode编码中包含世界上多数语言的常用字符,具有国际通用性,GBK包含日语吗?显然没有。


2)为什么在实现上采用了等长的UTF-16,而没有使用UTF-8不等长实现方式?

像python 和java这样的编程语言虽然也提供了对字节的操作能力(这方面java比python稍好点),但是它们更注重的是开发效率。设计者在设计之初,恐怕就希望屏蔽这种技术细节。另外在字符串操作上,等长实现更容易操作。

比如有一段文字 “张三A,李四B,王五C”,需要把姓名和成绩解析出来,录入数据库。

显然等长实现,更容易操作

>>> ss = u"兔子"
>>> ss1 = u"兔子A兔子2"
>>> len(ss)
2
>>> len(ss1)
6
显然设计者希望字符串在内存中的具体存在形式对开发者透明

3) gb2312,GBK,Unicode编码的区别

我的理解,如果有错误请提出。

gb2312,GBK,Unicode是三种编码方案。并且gb2312还是GBK的一个子集。

gb2312和GBK 只有一种实现方式(一个字符占两个字节), 主要包含中文字符

Unicode编码方案有多种实现方式 UTF-8 ,UTF-16,UTF-64 等等,包含世界上多数语言的常用字符

>>> ss = u"兔子"
>>> s1 = ss.encode("UTF-8")
>>> len(s1)
6
>>> s2 = ss.encode("GBK")
>>> len(s2)
4
>>> 






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值