Java的字符集和编码

本来以为自己对Java的字符集和编码有一定了解,不过昨天和shiweidong同学讨论了几个问题,还真把我问住了。所以抽空搜索和整理了一下,有什么问题请大家指正。

首先是一些基本概念和术语

字符集(character set)

顾名思义,字符集就是字符的集合。比如26个英文字母的集合,比如Ascii字符集,另外所有汉字的集合也是字符集【当然谁也不能说把包含所有汉字了,只能说包含常见汉字】

编码字符集(coded character set)

我们知道,计算机内部所有数据都是二进制的表示,一般机器最小处理单元是8个位一个字节。计算机根本不知道A是什么,所以需要一种方法把字符集映射成整数,这也可以

说是一种编码方法,所以叫做编码字符集。最常用的Ascii把A编码成0x41(十进制为65),把编码了英文的52个字母和数组以及一些标点符号等等。

Ascii码字符集总共包括128个字符,用7位就能编码下来,但这显然不能满足英语之外的很多语言。对于非英语国家,尤其是亚洲很多国家,语言的字符数量成千上万,

所以各自定义了自己的一些编码字符集,比如中国最早是国家标准GB2312,定义了常见的汉字,但是微软觉得不够,所以在windows的操作系统下扩展了GB2312,定义

GBK,它完全反向兼容GB2312(也就是说原来GB2312的字符的编码在GBK中保持不变),在Windows平台取得了广泛应用,后来又出了一个国家标准GB18030。增加了更多

的汉字,不过这些估计都是康熙词典都不定能找到的字,所以一般GBK就够用了。

当然,中国定义自己的编码字符集,全世界所有国家也有定义自己的字符集,而且他们编码时都不会考虑别人的字符集,那么就出现了很多问题,比如同样一个字符在不同编码中

是不同的,有些编码字符集很大,有的很小,但只能表示少量的字符。一般都包含ascii,但是可能GBK就没法表示希伯来语了(我没验证过,我只是举个例子)。

所以需要一个国际组织来一桶浆糊,Unicode于是诞生了。

字符编码方法(character encoding schema)

上面的编码字符集只是定义了一种映射方式,比如把A映射成65,但是怎么在计算机里表示65呢,当然可以用定长或者变长的方式来表示,比如可以用32位表示目前所以的Unicode字符,

但是这样也很浪费,而且一般的程序只会用到少部分字符,比如中文的程序,一般不会用到希伯来语吧。所以有了各种变长的表示方式。

GBK是变长的表示方式,英文字母一个字节,汉字两个字节。

Unicode 字符集和UTF-8,UTF-16,UTF-32编码

首先需要明确,Unicode 是一个编码字符集,它试图把全世界所有的字符都包含在内。UTF-8,UTF-16,UTF-32是编码方式。

GBK既是编码字符集,同时也是编码方法。

UTF-8 是一种变长编码方法,基本单元是一个字节。U+0000 - U+007F(0-127)用一个字节表示,U+0080-U+07FF用两个字节表示,...

具体编码方法可以参考wikipedia的词条。这种编码完全兼容Ascii。它的优点是:对英文很好,非常紧凑。但是对于非英文语言,大部分字符都超个一个字节(一般2个字节),那么解码

时每次都要判断第一个位是0还是1,这样还是挺麻烦的,也不高效。

UTF-16 也是变长编码,基本单元是两个字节,一般的字符用2个字节,也有一些需要4个字节。它的优点是对某些非英语国家有利,缺点是英文挺浪费。

UTF-32 是定长编码,所以字符都用32位4个字节表示,这确实很浪费,那它的优点是:因为是定长,所以可以随机定位到一个字符。比如有一串字符,我想定位到第3个,那么(3-1)*4就是

开始位置,如果是变长的编码,那么你的一个一个顺序遍历才行。

中文:UTF-8 3个字节;UTF-16 4个字节。所以非常遗憾,中文常见的Unicode字符范围是U+4E00 and U+9FFF,用UTF-8就3个字节了。

所以从节约空间的角度来说,中文使用GBK能节省大约33%的空间,如果是海量数据的话,这个量是非常大的。据说百度的引擎就是GBK编码的。不过在JVM里,

所有的字符都是UTF-16的【具体细节还有些需要说明的,参考下文】这还是挺浪费的。

JVM里的表示

Java里字符的原生类型是char,可以理解为一个16位的无符号整数【不要和c/c+的char混淆,c/c+的char等价于java的byte,此外java没有unsigned整数】

因为Unicode字符集在不断变化(主要是增加),最早的Unicode字符集小于65536个字符,用16位就可以表示下来。所以Java语言定义的char是个16位的数,使用UTF-16表示,

但是后来Unicode标准发生变化,出现了24位的字符,这就给Java带来了问题。下面是相关的几个术语。

code points

不知道怎么翻译,就用英文吧。

Code points就是一个编码字符集可以使用的整数。一个编码字符集定义了合法的code points的范围,但是不需要用完【也就是说中间有空洞】Unicode合法的code points是U+0000 - U+10FFFF,这有21位,最大表示大于2百万个字符。Unicode 4.0定义了96,382个。

Code Unit

编码基本单元,UTF8是字节,UTF16是两个字节。所以UTF8的字符可能占用1,2,3个字节;而UTF16要么2个字节,要么4个字节。

增补字符(Supplementary characters)

早期版本的Unicode定义了65536个字符,原来可以用16位U+0000 - U+FFFF表示,这些字符集也叫Basic Multilingual Plane (BMP)。

而那些新增加的就叫增补字符啦。

surrogate ranges

U+D800- U+DFFF之间的code point

UTF-16使用一个Code Unit来编码U+0000 - U+FFFF之间的code points,这之间的U+D800- U+DFFF code points是非法的字符,留给增补字符使用。

一个增补字符使用2个16位的code unit表示,它会把增补字符编码成两部分,第一部分的范围落在U+D800 - U+DBFF,第二部分落在U+DC00 to U+DFFF。

这样我看到一个16位的数字,根据它是否落在U+D800 to U+DFFF之间就可以判断它是一个16位的字符还是32位的字符。

Character和String类

    所以Character里出现的同一个方法有的会有两类参数,一类是char,一类是int。
    如果一个方法只接受一个char做为参数,那么这个方法是不支持增补字符的。他们把U+D800- U+DFFF这段字符当成未定义的字符。比如
         Character.isLetter('\uD840')将返回false,即使它是某个增补字符的一部分。
    如果一个方法使用int做为参数,则支持所有的Unicode字符,包括增补字符。比如 Character.isLetter(0x2F81A)将返回true

    String.length()的返回值,就不一定代表String中有多少个字符了,如果增补字符存在的话。
    而 Sring的codePointCount()方法,返回该String中的code point数量,也就是正确的字符数量。
    int codePointAt(int index)方法返回第index个code point的unicode编码。
    int codePointBefor(int index)方法返回第index前一个code point的unicode编码

参考文献

[1]    java.sun.com/javase/technologies/core/basic/intl/faq.jsp#text-representationBasic Multilingual Plane (BMP)

[2]    http://docs.oracle.com/javase/6/docs/api/java/lang/Character.html

[3]    http://www.unicode.org/

[4]    http://hi.baidu.com/zhaojianlinbj/blog/item/d9b87bd1d9aaa789a1ec9c34.html

[5]    http://stackoverflow.com/questions/2281646/whats-the-difference-between-encoding-and-charset

[6]    http://stackoverflow.com/questions/2533097/java-unicode-encoding

[7]    http://stackoverflow.com/questions/496321/utf8-utf16-and-utf32

[8]    http://www.unicode.org/faq/han_cjk.html

[9]    http://stackoverflow.com/questions/1366068/whats-the-complete-range-for-chinese-characters-in-unicode

[10] http://www.iteye.com/topic/558050

[11] http://stackoverflow.com/questions/3678752/are-all-kanji-characters-utf8-3-byte-long

[12] http://java.sun.com/developer/technicalArticles/Intl/Supplementary/


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值