欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

一文解开java中字符串编码的小秘密(干货)

程序员文章站 2022-03-10 13:14:36
简介在本文中你将了解到unicode和utf-8,utf-16,utf-32的关系,同时你还会了解变种utf-8,并且探讨一下utf-8和变种utf-8在java中的应用。一起来看看吧。unicode...

简介

在本文中你将了解到unicode和utf-8,utf-16,utf-32的关系,同时你还会了解变种utf-8,并且探讨一下utf-8和变种utf-8在java中的应用。

一起来看看吧。

unicode的发展史

在很久很久以前,西方世界出现了一种叫做计算机的高科技产品。

初代计算机只能做些简单的算数运算,还要使用人工打孔的程序才能运行,不过随着时间的推移,计算机的体积越来越小,计算能力越来越强,打孔已经不存在了,变成了人工编写的计算机语言。

一切都在变化,唯有一件事情没有变化。这件事件就是计算机和编程语言只流传在西方。而西方日常交流使用26个字母加有限的标点符号就够了。

最初的计算机存储可以是非常昂贵的,我们用一个字节也就是8bit来存储所有能够用到的字符,除了最开始的1bit不用以外,总共有128中选择,装26个小写+26个大写字母和其他的一些标点符号之类的完全够用了。

这就是最初的ascii编码,也叫做美国信息交换标准代码(american standard code for information interchange)。

后面计算机传到了全球,人们才发现好像之前的ascii编码不够用了,比如中文中常用的汉字就有4千多个,怎么办呢?

没关系,将ascii编码本地化,叫做ansi编码。1个字节不够用就用2个字节嘛,路是人走出来的,编码也是为人来服务的。于是产生了各种如gb2312, big5, jis等各自的编码标准。这些编码虽然与ascii编码兼容,但是相互之间却并不兼容。

这严重的影响了国际化的进程,这样还怎么去实现同一个地球,同一片家园的梦想?

于是国际组织出手了,制定了unicode字符集,为所有语言的所有字符都定义了一个唯一的编码,unicode的字符集是从u+0000到u+10ffff这么多个编码。

那么unicode和utf-8,utf-16,utf-32有什么关系呢?

unicode字符集最后是要存储到文件或者内存里面的,直接存储的话,空间占用太大。那怎么存呢?使用固定的1个字节,2个字节还是用变长的字节呢?于是我们根据编码方式的不同,分成了utf-8,utf-16,utf-32等多种编码方式。

其中utf-8是一种变长的编码方案,它使用1-4个字节来存储。utf-16使用2个或者4个字节来存储,jdk9之后的string的底层编码方式变成了两种:latin1和utf16。

而utf-32是使用4个字节来存储。这三种编码方式中,只有utf-8是兼容ascii的,这也是为什么国际上utf-8编码方式比较通用的原因(毕竟计算机技术都是西方人搞出来的)。

unicode详解

知道了unicode的发展史之后,接下来我们详解讲解一下unicode到底是怎么编码的。

unicode标准从1991年发布1.0版本,已经发展到2020年3月最新的13.0版本。

unicode能够表示的字符串范围是0到10ffff,表示为u+0000到u+10ffff。

其中u+d800到u+dfff的这些字符是预留给utf-16使用的,所以unicode的实际表示字符个数是216 − 211 + 220 = 1,112,064个。

我们将unicode的这些字符集分成17个平面,各个平面的分布图如下:

一文解开java中字符串编码的小秘密(干货)

以plan 0为例,basic multilingual plane (bmp)基本上包含了大部分常用的字符,下图展示了bmp中所表示的对应字符:

一文解开java中字符串编码的小秘密(干货)

上面我们提到了u+d800到u+dfff是utf-16的保留字符。其中高位u+d800–u+dbff和低位u+dc00–u+dfff是作为一对16bits来对非bmp的字符进行utf-16编码。单独的一个16bits是无意义的。

utf-8

utf-8是用1到4个字节来表示所有的1,112,064个unicode字符。所以utf-8是一种变长的编码方式。

utf-8目前是web中最常见的编码方式,我们看下utf-8怎么对unicode进行编码:

一文解开java中字符串编码的小秘密(干货)

最开始的1个字节可以表示128个ascii字符,所以utf-8是和ascii兼容的。

接下来的1,920个字符需要两个字节进行编码,涵盖了几乎所有拉丁字母字母表的其余部分,以及希腊语,西里尔字母,科普特语,亚美尼亚语,希伯来语,阿拉伯语,叙利亚语,thaana和n'ko字母,以及组合变音符号标记。bmp中的其余部分中的字符需要三个字节,其中几乎包含了所有常用字符,包括大多数中文,日文和韩文字符。unicode中其他平面中的字符需要四个字节,其中包括不太常见的cjk字符,各种历史脚本,数学符号和表情符号(象形符号)。

下面是一个具体的utf-8编码的例子:

一文解开java中字符串编码的小秘密(干货)

utf-16

utf-16也是一种变长的编码方式,utf-16使用的是1个到2个16bits来表示相应的字符。

utf-16主要在microsoft windows, java 和 javascript/ecmascript内部使用。

不过utf-16在web上的使用率并不高。

接下来,我们看一下utf-16到底是怎么进行编码的。

首先:u+0000 to u+d7ff 和 u+e000 to u+ffff,这个范围的字符,直接是用1个16bits来表示的,非常的直观。

接着是:u+010000 to u+10ffff

这个范围的字符,首先减去0x10000,变成20bits表示的0x00000–0xfffff。

然后高10bits位的0x000–0x3ff加上0xd800,变成了0xd800–0xdbff,使用1个16bits来表示。

低10bits的0x000–0x3ff加上0xdc00,变成了0xdc00–0xdfff,使用1个16bits来表示。

u' = yyyyyyyyyyxxxxxxxxxx // u - 0x10000

w1 = 110110yyyyyyyyyy // 0xd800 + yyyyyyyyyy

w2 = 110111xxxxxxxxxx // 0xdc00 + xxxxxxxxxx

这也是为什么在unicode中0xd800–0xdfff是utf-16保留字符的原因。

下面是一个utf-16编码的例子:

一文解开java中字符串编码的小秘密(干货)

utf-32

utf-32是固定长度的编码,每一个字符都需要使用1个32bits来表示。

因为是32bits,所以utf-32可以直接用来表示unicode字符,缺点就是utf-32占用的空间太大,所以一般来说很少有系统使用utf-32.

null-terminated string 和变种utf-8

在c语言中,一个string是以null character ('\0')nul结束的。

所以在这种字符中,0x00是不能存储在string中间的。那么如果我们真的想要存储0x00该怎么办呢?

我们可以使用变种utf-8编码。

在变种utf-8中,null character (u+0000) 是使用两个字节的:11000000 10000000 来表示的。

所以变种utf-8可以表示所有的unicode字符,包括null character u+0000。

通常来说,在java中,inputstreamreader 和 outputstreamwriter 默认使用的是标准的utf-8编码,但是在对象序列化和datainput,dataoutput,jni和class文件中的字符串常量都是使用的变种utf-8来表示的。

补充知识:java基础之字符串的编码(encode)和解码(decode)

废话不多说,看代码~

以上这篇一文解开java中字符串编码的小秘密(干货)就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。