您好、欢迎来到现金彩票网!
当前位置:秒速快3官网 > 数字字符集 >

Java中的字符集编码

发布时间:2019-07-03 03:11 来源:未知 编辑:admin

  Unicode 与 UCS 的历史恩怨 ASCII 及相关标准 地球人都知道 ASCII 就是美国标准信息交换码的缩写,也知道 ASCII 规定用 7 位二进制数字来表示英文字符,ASCII 被定为国际标准之后的代号为 ISO-646.由于 ASCII 码只使用了7 个二进制位,也就是说一个字节可以表示的 256 个数字中,它仅使用了 0~127 这 128 个码位,剩下的 128 个码位便可以用来做扩展,用来表示一些特定语言所独有的字符,因此对这多余的 128 个码位的不同扩展,就形成了一系列 ISO-8859-*的标准。例如为英语作了专门扩展的字符集编码标准编号为 ISO-8859-1,也叫做 Latin-1,为希腊语所作的扩展编号...

  Unicode 与 UCS 的历史恩怨 ASCII 及相关标准 地球人都知道 ASCII 就是美国标准信息交换码的缩写,也知道 ASCII 规定用 7 位二进制数字来表示英文字符,ASCII 被定为国际标准之后的代号为 ISO-646.由于 ASCII 码只使用了7 个二进制位,也就是说一个字节可以表示的 256 个数字中,它仅使用了 0~127 这 128 个码位,剩下的 128 个码位便可以用来做扩展,用来表示一些特定语言所独有的字符,因此对这多余的 128 个码位的不同扩展,就形成了一系列 ISO-8859-*的标准。例如为英语作了专门扩展的字符集编码标准编号为 ISO-8859-1,也叫做 Latin-1,为希腊语所作的扩展编号为ISO-8859-7 等,完整的列表可以参考《Java Internationalization》一书。 Unicode 与 UCS 整个 Unicode 项目是由多家计算机软件公司,还包括一些出版行业的公司共同发起的,从上世纪八十年代就已经开始。地球人都知道,对于日文,汉字来说,256 个码位是远远不够用的(当然,在当时并不是地球人都知道,起码设计计算机的老美们就不知道,甚至直到今天,还有老美以为米国是世界上唯一的国家)。解决方法很直观也很明显,那就是采用码位多到足够包含所需字符数量的编码方案(即俗话说的头痛医头,脚痛医脚嘛)。这也是 Unicode的目标之一,能够包含世界上所有语言的字符(包括汉字,日文,数学符号,音乐符号,还包括各种奇奇怪怪看也看不懂的东西比如象形文字,甲骨文 ,三个代表,科学发展观等等,笑),这个理想,可以说很远大,但很快被发现仅靠 Unicode 原先的设计无法实现。Unicode的另一个设计目标,对今天影响深远,那就是对所有字符都采用 16 位编码(即用一个大小不超过 2 的 16 次方的整数数字给每个字符编号,注意从这个意义上也可以看出,Unicode 是一种编码字符集,而非字符集编码)。说这个设计目标对现今影响深远,完全不是表扬,因为到后来连 Unicode 的设计者也发现,16 位编码仅有 65536 个码位,远远不能容纳世界上所有的字符,但当意识到这个问题的时候,Unicode 大部分的规范已经制定完毕,也有相当程度的普及,完全推倒重来是不现实的。这成了一个遗留问题,也是 surrogate pair 这种蹩脚解决方案的发端。 无独有偶,在 1984 年,喜欢以繁多的编号糊弄群众的国际标准化组织 ISO 也开始着手制定解决不同语言字符数量太大问题的解决方案,这一方案被称为 Universal Character Set(UCS),正式的编号是 ISO-10646 (记得么,ASCII 是 ISO-646,不知这种安排是否是故意的)。还是 ISO 高瞻远瞩,一开始就确定了 UCS 是一个 31 位的编码字符集(即用一个大小不超过 2的 31 次方的整数数字为每个字符编号),这回真的足以容纳古往今来所有国家,所有语言所包含的字符了(是的,任何国家,任何小语种都包括,也不管这些国家是与台湾建交还是与中国大陆建交,是拥护民主制度还是实行,所以说科学无国界)。虽然后来他们意识到,2 的 31 次方个码位又实在太多了 天下大势,分久必合。无论 Unicode 还是 UCS,最初的目的都是杜绝各种各样名目繁多形式各异互不兼容老死不相往来的私用扩展编码(好啰嗦的一句话),结果两方确立标准的同时(最初时这两个标准是不兼容的),又形成了割据,这对建设和谐社会是不利的,违反当今世界和平与发展的主旋律,中国政府一向反对任何形式的霸权主义和强权政治,对以米国为首的发达国家扯远了扯远了。1991 年,Unicode 联盟与 ISO 的工作组终于开始讨论 Unicode 与 UCS 的合并问题,虽然其后的合并进行了很多年,Unicode 初版规范中的很多编码都需要被改写,UCS 也需要对码空间的使用进行必要限制,但成果是喜人的。最终,两者统一了抽象字符集(即任何一个在 Unicode 中存在的字符,在 UCS 中也存在),且最靠前的 65535个字符也统一了字符的编码。对于码空间,两者同意以一百一十万为限(即两者都认为虽然65536 不够,但 2 的 31 次方又太大,一百一十万是个双方都可接受的码空间大小,也够用,当然,这里说的一百一十万只是个约数),Unicode 将码空间扩展到了一百一十万,而 UCS 将永久性的不使用一百一十万以后的码位。也就是说,现在再讲 Unicode 只包含 65536 个字符是不对的。除了对已经定义的字符进行统一外,Unicode 联盟与 ISO 工作组也同意今后任何的扩展工作两者均保持同步,因此虽然从历史的意义上讲 Unicode 与 UCS 不是一回事(甚至细节上说也不是一回事),但现在提起 Unicode,指代两者均无不妥。何的扩展工作两者均保持同步,因此虽然从历史的意义上讲 Unicode 与 UCS 不是一回事(甚至细节上说也不是一回事),但现在提起 Unicode,指代两者均无不妥。 编码字符集与字符集编码的区别 需要再一次强调的是,无论历史上的 UCS 还是现如今的 Unicode,两者指的都是编码字符集,而不是字符集编码。花费一点时间来理解好这件事,然后你会发现对所有网页的,系统的,编码标准之间的来回转换等等繁杂事务都会思路清晰,手到擒来。 首先说说最一般意义上的字符集。 一个抽象字符集其实就是指字符的集合,例如所有的英文字母是一个抽象字符集,所有的汉字是一个抽象字符集,当然,把全世界所有语言的符号都放在一起,也可以称为一个抽象字符集,所以这个划分是相当人为的。之所以说“抽象”二字,是因为这里所提及的字符不是任何具体形式的字符,拿汉字中的“汉”这个字符来说,您在这篇文章中看到的这个“汉”其实是这个字符的一种具体表现形式,是它的图像表现形式,而且它是用中文(而非拼音)书写而成,使用宋体外观;而当人们用嘴发出“汉”这个音的时候,他们是在使用“汉”的另一种具体表现形式声音,但无论如何,两者所指的字符都是“汉”这个字。同一个字符的表现形式可能有无数种(点阵表示,矢量表示,音频表示,楷体,草书等等等等),把每一种表现形式下的同一个字符都纳入到字符集中,会使得集合过于庞大,冗余高,也不好管理。因此抽象字符集中的字符,都是指唯一存在的抽象字符,而忽略它的具体表现形式。 抽象字符集中的诸多字符,没有顺序之分,谁也不能说哪个字符在哪个字符前面,而且这种抽象字符只有人能理解。在给一个抽象字符集合中的每个字符都分配一个整数编号之后(注意这个整数并没有要求大小),这个字符集就有了顺序,就成为了编码字符集。同时,通过这个编号,可以唯一确定到底指的是哪一个字符。当然,对于同一个字符,不同的字符集编码系统所制定的整数编号也不尽相同,例如“儿”这个字,在 Unicode 中,它的编号是0x513F,(为方便起见,以十六进制表示,但这个整数编号并不要求必须是以十六进制表示)意思是说它是 Unicode 这个编码字符集中的第 0x513F 个字符。而在另一种编码字符集比如Big5 中,这个字就是第 0xA449 个字符了。这种情况的另一面是,许多字符在不同的编码字符集中被分配了相同的整数编号,例如英文字母“A”,在 ASCII 及 Unicode 中,均是第 0x41个字符。我们常说的 Unicode 字符集,指的就是这种被分配了整数编号的字符集合,但要澄清的是,编码字符集中字符被分配的整数编号,不一定就是该字符在计算机中存储时所使用 的值,计算机中存储的字符到底使用什么二进制整数值来表示,是由下面将要说到的字符集编码决定的。 字符集编码决定了如何将一个字符的整数编号对应到一个二进制的整数值,有的编码方案简单的将该整数值直接作为其在计算机中的表示而存储,例如英文字符就是这样,几乎所有的字符集编码方案中,英文字母的整数编号与其在计算机内部存储的二进制形式都一致。但有的编码方案,例如适用于 Unicode 字符集的 UTF-8 编码形式,就将很大一部分字符的整数编号作了变换后存储在计算机中。以“汉”字为例,“汉”的 Unicode 值为 0x6C49,但其编码为 UTF-8 格式后的值为 0xE6B189(注意到变成了三个字节)。这里只是举个例子,关于UTF-8 的详细编码规则可以参看《Mapping codepoints to Unicode encoding forms》一文,URL 为 ?site_id=nrsi&item_id=IWS-AppendixA#sec3.我们经常听说的另一种编码方案 UTF-16,则对Unicode 中的前 65536 个字符编号都不做变换,直接作为计算机存储时使用的值(对 65536以后的字符,仍然要做变换),例如“汉”字的 Unicode 编号为 0x6C49,那么经过 UTF-16 编码后存储在计算机上时,它的表示仍为 0x6C49!。我猜,正是因为 UTF-16 的存在,使得很多人认为 Unicode 是一种编码(实际上,是一个字符集,再次重申),也因此,很多人说 Unicode的时候,他们实际上指的是 UTF-16.UTF-16 提供了 surrogate pair 机制,使得 Unicode 中码位大于 65536 的那些字符得以表示。 Surrogate pair 机制在目前来说实在不常用,甚至连一些 UTF-16 的实现都不支持,所以我不打算在这里多加讨论,其基本的思想就是用两个 16 位的编码表示一个字符(注意,只对码位超过 65536 的字符这么做)。Unicode 如此死抱着 16 这个数字不放,有历史的原因,也有实用的原因。 当然还有一种最强的编码,UTF-32,他对所有的 Unicode 字符均不做变换,直接使用编号存储!(俗称的以不变应万变),只是这种编码方案太浪费存储空间(就连 1 个字节就可以搞定的英文字符,它都必须使用 4 个字节),因而尽管使用起来方便(不需要任何转换),却没有得到普及。 记得当初 Unicode 与 UCS 还没成家之时,UCS 也是需要人爱,需要人疼的,没有自己的字符集编码怎么成。UCS-2 与 UCS-4 就扮演了这样的角色。UCS-4 与 UTF-32 除了名字不同以外,思想完全一样。而 UCS-2 与 UTF-16 在对前 65536 个字符的处理上也完全相同,唯一的区别只在于 UCS-2 不支持 surrogate pair 机制,即是说,UCS-2 只能对前 65536 个字符编码,对其后的字符毫无办法。不过现在再谈起字符编码的时候,UCS-2 与 UCS-4 早已成为计算机史学家才会用到的词汇,就让它们继续留在故纸堆里吧。 GB2312,GBK 与中文网页 GB2312 是对中国的开发人员来说很重要的一个词汇,它的来龙去脉并不需要我在这里赘述,随便 Goolge 之便明白无误。我只是想提一句,记得前一节说到编码字符集和字符集编码不是一回事,而有的字符集编码又实际上没有做任何事,GB2312 正是这样一种东西! GB2312 最初指的是一个编码字符集,其中包含了 ASCII 所包含的英文字符,同时加入了 6763 个简体汉字以及其他一些 ASCII 之外的符号。与 Unicode 有 UTF-8 和 UTF-16 一样(当然, UTF-8 和 UTF-16 也没有被限定只能用来对 Unicode 进行编码,实际上,你用它对视频进行编码都是可以的,只是编出的文件没有播放器支持罢了,哈哈),GB2312 也有自己的编码方案,但这个方案直接使用一个字符在 GB2312 中的编号作为存储值(与 UTF-32 的做法类似),也因此,这个编码方案甚至没有正式的名称。我们日常说起 GB2312 的时候,常常即指这个字符集,也指这种编码方案。 GBK 是 GB2312 的后续标准,添加了更多的汉字和特殊符号,类似的是,GBK 也是同时指他的字符集和他的编码。 GBK 还是现如今中文 Windows 操作系统的系统默认编码(这正是几乎所有网页上的,文件里的乱码问题的根源)。 我们可以这样来验证,使用以下的 Java 代码: String encoding=System.getProperty(file.encoding); System.out.println(encoding); 输出结果为 GBK (什么?你的输出不是这样?怎么可能?完了,我的牌子要砸了,等等,你用的繁体版XP?我说你这同志在这里捣什么乱?去!去!) 说到 GB2312 和 GBK 就不得不提中文网页的编码。尽管很多新开发的 Web 系统和新上线的注重国际化的网站都开始使用 UTF-8,仍有相当一部分的中文媒体坚持使用 GB2312 和 GBK,例如新浪的页面。其中有两点很值得注意。 第一,页面中 meta 标签的部分,常常可以见到 charset=GB2312 这样的写法,很不幸的是,这个“charset”其实是用来指定页面使用的是什么字符集编码,而不是使用什么字符集。例如你见到过有人写“charset=UTF-8”,见到过有人写“charset=ISO-8859-1”,但你见过有人写“charset=Unicode”么?当然没有,因为 Unicode 是一个字符集,而不是编码。 然而正是 charset 这个名称误导了很多程序员,真的以为这里要指定的是字符集,也因而使他们进一步的误以为 UTF-8 和 UTF-16 是一种字符集!(万恶啊)好在 XML 中已经做出了修改,这个位置改成了正确的名称:encoding.第二,页面中说的 GB2312,实际上并不线(惊讶么?)。我们来做个实验,例如找一个 GB2312 中不存在的汉字“亸”(这个字确实不在 GB2312 中,你可以到 GB2312 的码表中去找,保证找不到),这个字在 GBK 中。然后你把它放到一个 html 页面中,试着在浏览器中打开它,然后选择浏览器的编码为“GB2312”,看到了什么?它完全正常显示! 结论不用我说你也明白了,浏览器实际上使用的是 GBK 来显示。 新浪的页面中也有很多这样的例子,到处都写 charset=GB2312,却使用了无数个 GB2312 中并不存在的字符。这种做法对浏览器显示页面并不成问题,但在需要程序抓取页面并保存的时候带来了麻烦,程序将不能依据页面所“声称”的编码进行读取和保存,而只能尽量猜测正确的编码 一个网页要想在浏览器中能够正确显示,需要在三个地方保持编码的一致:网页文件,网页编码声明和浏览器编码设置。 首先是网页文件本身的编码,即网页文件在被创建的时候使用什么编码来保存。这个完全取决于创建该网页的人员使用了什么编码保存,而进一步的取决于该人员使用的操作系统。例如我们使用的中文版 WindowsXP 系统,当你新建一个文本文件,写入一些内容,并按下ctrl+s进行保存的那一刻,操作系统就替你使用GBK编码将文件进行了保存(没有使用UTF-8,也没有使用 UTF-16)。而使用了英文系统的人,系统会使用 ISO-8859-1 进行保存,这也意味着,在英文系统的文件中如果输入一个汉字,是无法进行保存的(当然,你甚至都无法输入)。 一个在创建 XML 文件时(创建 HTML 的时候倒很少有人这么做)常见的误解是以为只要在页面的 encoding 部分声明了 UTF-8,则文件就会被保存为 UTF-8 格式。这实在是怎么说呢,不能埋怨大家。实际上 XML 文件中 encoding 部分与 HTML 文件中的 charset 中一样,只是告诉“别人”(这个别人可能是浏览你的页面的人,可能是浏览器,也可能是处理你页面的程序,别人需要知道这个,因为除非你告诉他们,否则谁也猜不出你用了什么编码,仅通过文件的内容判断不出使用了什么编码,这是真的)这个文件使用了什么编码,唯独操作系统不会搭理,它仍然会按自己默认的编码方式保存文件(再一次的,在我们的中文 WindowsXP系统中,使用 GBK 保存)。至于这个文件是不是真的是 encoding 或者 charset 所声明的那种编码保存的呢?答案是不一定! 例如新浪的页面就“声称”他是用 GB2312 编码保存的,但实际上却是 GBK,也有无数的二把刀程序员用系统默认的 GBK 保存了他们的 XML 文件,却在他们的 encoding 中信誓旦旦的说是 UTF-8 的。 这就是我们所说的第二个位置,网页编码声明中的编码应该与网页文件保存时使用的编码一致。 而浏览器的编码设置实际上并不严格,就像我们第三节所说的那样,在浏览器中选择使用 GB2312 来查看,它实际上仍然会使用 GBK 进行。而且浏览器还有这样一种好习惯,即它会尽量猜测使用什么编码查看最合适。 我要重申的是,网页文件的编码和网页文件中声明的编码保持一致,这是一个极好的建议(值得遵循,会与人方便,与己方便),但如果不一致,只要网页文件的编码与浏览器的编码设置一致,也是可以正确显示的。 例如有这样一个页面,它使用 GBK 保存,但声明自己是 UTF-8 的。这个时候用浏览器打开它,首先会看到乱码,因为这个页面“告诉”浏览器用 UTF-8 显示,浏览器会很尊重这个提示,于是乱码一片。但当手工把浏览器设为 GBK 之后,显示正常。 说了以上四节这么多,后面我们就来侃侃 Java 里的字符编码,你会发现有意思且挠头的 事情很多,但一旦弄通,天下无敌(不过不要像东方不败那样才好)。 Java 代码中的字符编码转换 Part 1 如果你是 JVM 的设计者,让你来决定 JVM 中所有字符的表示形式,你会不会允许使用各种编码方式的字符并存? 我想你的答案是不会,如果在内存中的 Java 字符可以以 GB2312,UTF-16,BIG5 等各种编码形式存在,那么对开发者来说,连进行最基本的字符串打印、连接等操作都会寸步难行。例如一个 GB2312 的字符串后面连接一个 UTF-8 的字符串,那么连接后的最终结果应该是什么编码的呢?你选哪一个都没有道理。 因此牢记下面这句话,这也是 Java 开发者的共同意志:在 Java 中,字符只以一种形式存在,那就是 Unicode(注意到我们没有选择特定的编码,直接使用它们在字符集中的编号,这是统一的唯一方法)。 但“在 Java 中”到底是指在哪里呢?就是指在 JVM 中,在内存中,在你的代码里声明的每一个 char,String 类型的变量中。例如你在程序中这样写 char han=汉; 在内存的相应区域,这个字符就表示为 0x6C49.可以用下面的代码证明一下: char han=汉; System.out.format(%x,(short)han); 输出是:6c49 反过来用 Unicode 编号来指定一个字符也可以,像这样: char han=0x6c49; System.out.println(han); 输出是:汉 这其实也是说,只要你正确的读入了“汉”这个字,那么它在内存中的表示形式一定是0x6C49,没有任何其他的值能代表这个字(当然,如果你读错了,那结果是什么就不知道了,范伟说:读,读错了呀,那还等于好几亿呢;本山大哥说:好几亿你也没答上,请听下一题)。 JVM 的这种约定使得一个字符存在的世界分为了两部分:JVM 内部和 OS 的文件系统。在JVM 内部,统一使用 Unicode 表示,当这个字符被从 JVM 内部移到外部(即保存为文件系统中的一个文件的内容时),就进行了编码转换,使用了具体的编码方案(也有一种很特殊的情况,使得在 JVM 内部也需要转换,不过这个是后话)。 因此可以说,所有的编码转换就只发生在边界的地方,JVM 和 OS 的交界处,也就是你的各种输入输出流(或者 Reader,Writer 类)起作用的地方。 话头扯到这里就必须接着说 Java 的 IO 系统。 尽管看上去混乱繁杂,但是所有的 IO 基本上可以分为两大阵营:面向字符的 Reader 啊Wrtier 啊,以及面向字节的输入输出流。 下面我来逐一分解,其实一点也不难。 面向字符和面向字节中的所谓“面向”什么,是指这些类在处理输入输出的时候,在哪个意义上保持一致。如果面向字节,那么这类工作要保证系统中的文件二进制内容和读入 JVM内部的二进制内容要一致。不能变换任何 0 和 1 的顺序。因此这是一种非常“忠实于原著”的做法(偶然间让我想起郭敬明抄袭庄羽的文章,那家伙,太忠实于原著了,笑)。 这种输入输出方式很适合读入视频文件或者音频文件,或者任何不需要做变换的文件内容。 而面向字符的 IO 是指希望系统中的文件的字符和读入内存的“字符”(注意和字节的区别)要一致。例如我们的中文版 WindowsXP 系统上有一个 GBK 的文本文件,其中有一个“汉”字,这个字的 GBK 编码是 0xBABA(而 Unicode 编号是 0x6C49),当我们使用面向字符的 IO把它读入内存并保存在一个 char 型变量中时,我希望 IO 系统不要傻傻的直接把 0xBABA 放到这个 char 型变量中,我甚至都不关心这个 char 型变量具体的二进制内容到底是多少,我只希望这个字符读进来之后仍然是“汉”这个字。 从这个意义上也可以看出,面向字符的 IO 类,也就是 Reader 和 Writer 类,实际上隐式的为我们做了编码转换,在输出时,将内存中的 Unicode 字符使用系统默认的编码方式进行了编码,而在输入时,将文件系统中已经编码过的字符使用默认编码方案进行了还原。我两次提到“默认”,是说 Reader 和 Writer 的聪明也仅此而已了,它们只会使用这个默认的编码来做转换,你不能为一个 Reader 或者 Writer 指定转换时使用的编码。这也意味着,如果你使用中文版 WindowsXP 系统,而上面存放了一个 UTF-8 编码的文件,当你使用 Reader 类来读入的时候,它会傻傻的使用 GBK 来做转换,转换后的内容当然驴唇不对马嘴! 这种笨,有时候其实是一种傻瓜式的功能提供方式,对大多数初级用户(以及不需要跨平台的高级用户)来说反而是件好事。 但我们不一样啦,我们都是国家栋梁,肩负着赶英超美的责任,必须师夷长技以治夷,所以我们总还要和 GBK 编码以外的文件打交道。 说了上面这些内容,想必聪明的读者已经看出来,所谓编码转换就是一个字符与字节之间的转换,因此 Java 的 IO 系统中能够指定转换编码的地方,也就在字符与字节转换的地方,那就是(读者:InputStreamReader 和 OutputStreamWriter!作者:太强了,都会抢答了!) 这两个类是字节流和字符流之间的适配器类,因此他们肩负着编码转换的任务简直太自然啦!要注意,实际上也只能在这两类实例化的时候指定编码,是不是很好记呢? 下面来写一段小程序,来把“汉”字用我们非常崇拜的 UTF-8 编码写到文件中! try{ PrintWriter out=new PrintWriter(new OutputStreamWriter(new FileOutputStream(c:/utf-8.txt),UTF-8)); try{ out.write(汉); }finally{ out.close(); } }catch(IOException e){ throw new RuntimeException(e); } 运行之后到 c 盘下去找 utf-8.txt 这个文件,用 UltraEdit 打开,使用 16 进制查看,看到了什么?它的值是 0xE6B189!噢耶!(读者:这,这有什么好高兴的) Java 号称对 Unicode 提供天然的支持,这话在很久很久以前就已经是假的了(不过曾经是真的),实际上,到 JDK5.0 为止,Java 才算刚刚跟上 Unicode 的脚步,开始提供对增补字符的支持。 现在的 Unicode 码空间为 U+0000 到 U+10FFFF,一共 1114112 个码位,其中只有 1,112,064 个码位是合法的(我来替你做算术,有 2048 个码位不合法),但并不是说现在的 Unicode就有这么多个字符了,实际上其中很多码位还是空闲的,到 Unicode 4.0 规范为止,只有 96,382 个码位被分配了字符(但无论如何,仍比很多人认为的 65536 个字符要多得多了)。其中U+0000 到 U+FFFF 的部分被称为基本多语言面(Basic Multilingual Plane,BMP)。U+10000及以上的字符称为补充字符。在 Java 中(Java1.5 之后),补充字符使用两个 char 型变量来表示,这两个 char 型变量就组成了所谓的 surrogate pair(在底层实际上是使用一个 int进行表示的)。第一个 char 型变量的范围称为“高代理部分”(high-surrogates range,从uD800 到uDBFF,共 1024 个码位), 第二个 char 型变量的范围称为 low-surrogates range(从uDC00 到uDFFF,共 1024 个码位),这样使用 surrogate pair 可以表示的字符数一共是 1024 的平方计 1048576 个,加上 BMP 的 65536 个码位,去掉 2048 个非法的码位,正好是1,112,064 个码位。 关于 Unicode 的码空间实际上有一些稍不小心就会让人犯错的地方。比如我们都知道从U+0000 到 U+FFFF 的部分被称为基本多语言面(Basic Multilingual Plane,BMP),这个范围内的字符在使用 UTF-16 编码时,只需要一个 char 型变量就可以保存。仔细看看这个范围,应该有 65536 这么大,因此你会说单字节的 UTF-16 编码能够表示 65536 个字符,你也会说Unicode 的基本多语言面包含 65536 个字符,但是再想想刚才说过的 surrogate pair,一个UTF-16 表示的增补字符(再一次的,需要两个 char 型变量才能表示的字符)怎样才能被正确的识别为增补字符,而不是两个普通的字符呢?答案你也知道,就是通过看它的第一个char 是不是在高代理范围内,第二个 char 是不是在低代理范围内来决定,这也意味着,高代理和低代理所占的共 2048 个码位(从 0xD800 到 0xDFFF)是不能分配给其他字符的。 但这是对 UTF-16 这种编码方法而言,而对 Unicode 这样的字符集呢?在 Unicode 的编号中,U+D800 到 U+DFFF 是否有字符分配?答案是也没有!这是典型的字符集为方便编码方法而做的安排(你问他们这么做的目的?当然是希望基本多语言面中的字符和一个 char 型的UTF-16 编码的字符能够一一对应,少些麻烦,从中我们也能看出 UTF-16 与 Unicode 间很深的渊源与结合)。也就是说,无论 Unicode 还是 UTF-16 编码后的字符,在 0x0000 至 0xFFFF这个范围内,只有 63488 个字符。这就好比最初的 CPU 被勉强拿来做多媒体应用,用得多了,CPU 就不得不修正自己从硬件上对多媒体应用提供支持了。 尽管不情愿,但说到这里总还得扯扯相关的概念:代码点和代码单元。 代码点(Code Point)就是指 Unicode 中为字符分配的编号,一个字符只占一个代码点,例如我们说到字符“汉”,它的代码点是 U+6C49.代码单元(Code Unit)则是针对编码方法而言,它指的是编码方法中对一个字符编码以后所占的最小存储单元。例如 UTF-8 中,代码单元是一个字节,因为一个字符可以被编码为 1 个,2 个或者 3 个 4 个字节;在 UTF-16 中,代码单元变成了两个字节(就是一个 char),因为一个字符可以被编码为 1 个或 2 个 char(你找不到比一个 char 还小的 UTF-16 编码的字符,嘿嘿)。说得再罗嗦一点,一个字符,仅仅对应一个代码点,但却可能有多个代码单元(即可能被编码为 2 个 char)。 以上概念绝非学术化的绕口令,这意味着当你想以一种统一的方式指定自己使用什么字符的时候,使用代码点(即你告诉你的程序,你要用 Unicode 中的第几个字符)总是比使用代码单元更好(因为这样做的话你还得区分情况,有时候提供一个 16 进制数字,有时候要提供两个)。 例如我们有一个增补字符???(哈哈,你看到了三个问号对吧?因为我的系统显示不出这个字符),它在 Unicode 中的编号是 U+2F81A,当在程序中需要使用这个字符的时候,就可以这样来写: String s=String.valueOf(Character.toChars(0x2F81A)); char[]chars=s.toCharArray(); for(char c:chars){ System.out.format(%x,(short)c); } 后面的 for 循环把这个字符的 UTF-16 编码打印了出来,结果是 d87edc1a 注意到了吗?这个字符变成了两个 char 型变量,其中 0xd87e 就是高代理部分的值,0xdc1a 就是低代理的值。

http://alsunah.net/shuzizifuji/366.html
锟斤拷锟斤拷锟斤拷QQ微锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷微锟斤拷
关于我们|联系我们|版权声明|网站地图|
Copyright © 2002-2019 现金彩票 版权所有