字符串前置知识点——编码

为什么要学习编码

个人认为,程序本质上是对数据的处理过程。所以了解计算机储存的数据与人类能够认知的数据如何进行转换(也就是编码),有助于我们更深层次的理解代码。

什么是编码

我们常说的编码格式,其实包含两个概念:字符集字符编码

  • 字符集:是一个系统支持的所有抽象字符的集合。 字符是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等,如ASCII字符集、GBXXXX字符集、BIG5字符集、Unicode字符集。
  • 字符编码:是一套法则,使用该法则能够对自然语言的字符的一个集合(如字母表或音节表),与其他东西的一个集合(如二进制或电脉冲)进行配对,如ASCII编码、GBXXXX编码、BIG5编码、Unicode编码。

Unicode字符集与char类型

Unicode,统一码联盟官方中文名称为统一码[1],又译作万国码、统一字元码、统一字符编码[2],是统一字符编码标准(The Unicode Standard)的简称,属一种信息技术领域的业界标准,其整理、编码了世界上大部分的文字系统,使得电脑可以用更为简单的方式来呈现和处理文字。

Java语言中的char类型采用的就是Unicode编码,虽然char类型被称作字符型,但实际上char类型保存的是该字符所对应的Unicode编码,如字符’A’在系统中会被保存为0x0041。

值得一提的是,1991年发布了Unicode 1.0,当时仅占用了65536个代码值中不到一半的部分,所以Java在设计时采用16位的Unicode字符集,也就是一个char字符占两个字节。但是Unicode至今仍在不断增修,每个新版本都加入更多新的字符。目前最新的版本为2022年9月公布的15.0.0[4],已经收录超过14万个字符(第十万个字符在2005年获采纳),所以说如今单个char值已经不足以表示全部的Unicode字符了。

UTF-8与UTF-16

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

在Unicode中,字符的编码是确定的,编码值使用16进制表示,并加上U+作为前缀表示,例如字符’A’的编码值表示为U+0041。

我们把U+0041这样的编码值称为码点(code point),把不同的UTF实现中的最小存储单元称为代码单元,在UTF-8中,一个代码单元由8位二进制数组成,在UTF-16中,一个代码单元有16位二进制数组成。

但并不是说代码单元的存储粒度越小,越节省空间。某些字符采用UTF-16表示,只需要一个代码单元,也就是2个字节,而采用UTF-8表示,则需要三个代码单元,也就是3个字节。所以采用什么的编码实现还需要看具体使用的字符区间。

Unicode的编码空间从U+0000到U+10FFFF,共有1,112,064个码点(code point)可用来映射字符。Unicode的编码空间可以划分为17个平面(plane),每个平面包含216(65,536)个码位。17个平面的码位可表示为从U+xx0000到U+xxFFFF,其中xx表示十六进制值从0x00到0x10,共计17个平面。第一个平面称为基本多语言平面(Basic Multilingual Plane, BMP),或称第零平面(Plane 0),其他平面称为辅助平面(Supplementary Planes)

接下来会详细介绍UTF-16和UTF-8两种编码实现方式。

UTF-16

基本多语言平面的码点范围为U+0000到U+FFFF,其中从U+D800到U+DFFF之间的码位区段是永久保留不映射到Unicode字符,被称为替代区域(surrogate area)

在UTF-16中,基本多语言平面的码点,都可以用一个代码单元来表示,而辅助平面的码点,则需要通过一对替代区域的代码单元来表示,称作代理对(Surrogate Pair)。对于代理对,U+D800 ~ U+DBFF区间用于第一个代码单元,称为前导代理(lead surrogates),U+DC00 ~ U+DFFF区间用于第二个代码单元,称作后尾代理(trail surrogates)

这样我们就可以迅速判断一个代码单元,到底是便是一个字符,还是表示前导代理或是后尾代理。

接下来我们通过八元数集的一个数学符号‘𝕆’来演示UTF-16的编码过程。

        //𝕆 的码点为U+1D546,我们通过一个int值接收。
        int symbol = 0x1D546;
        //首先,将辅助平面的码点减去 0x10000,这样就可以用一个20位的二进制数来表示所有的辅助平面码点。
        symbol -= 0x10000;
        //上面已经将码点转化为一个20位的二进制数,我们取高10位,获得一个前导代理,并使其落入U+D800 ~ U+DBFF区间
        short lead = (short) ((symbol >> 10) + 0xD800);
        //然后取低10位获得一个后尾代理,并使其落入U+DC00 ~ U+DFFF区间
        short trail = (short) ((symbol & 0x3FF) + 0xDC00);
        //最后利用String类将两个代码单元组合起来,Java中一个char值描述一个UTF-16的代码单元
        String result = new String(new char[]{(char) lead, (char) trail});
        //输出为 𝕆
        System.out.println(result);

UTF-8

UTF-8使用一至六个代码单元为每个字符编码:

  • 128个US-ASCII字符只需一个代码单元编码(码点范围由U+0000至U+007F)
  • 带有附加符号的拉丁文、希腊文、西里尔字母、亚美尼亚语、希伯来文、阿拉伯文、叙利亚文及它拿字母则需要两个代码单元编码(码点范围由U+0080至U+07FF)
  • 其他基本多语言平面(BMP)中的字元(这包含了大部分常用字,如大部分的汉字)使用三个代码单元编码(码点范围由U+0800至U+FFFF)
  • 其他极少使用的辅助平面的字元使用四至六代码单元编码(码点范围由U+10000至U+1FFFFF使用四字节,码点范围由U+200000至U+3FFFFFF使用五字节,码点范围由U+4000000至U+7FFFFFFF使用六字节)

每个使用UTF-8储存的字符,除了第一个代码单元外,其余代码单元的头两位都是以"10"开始,使文字处理器能够较快地找出每个字符的开始位置。如下表:

码点的位数码点起值码点终值代码单元数代码单元 1代码单元 2代码单元 3代码单元 4代码单元 5代码单元 6
7U+0000U+007F10xxxxxxx
11U+0080U+07FF2110xxxxx10xxxxxx
16U+0800U+FFFF31110xxxx10xxxxxx10xxxxxx
21U+10000U+1FFFFF411110xxx10xxxxxx10xxxxxx10xxxxxx
26U+200000U+3FFFFFF5111110xx10xxxxxx10xxxxxx10xxxxxx10xxxxxx
31U+4000000U+7FFFFFFF61111110x10xxxxxx10xxxxxx10xxxxxx10xxxxxx10xxxxxx

我们仍旧以数学符号‘𝕆’来演示UTF-8的编码过程。

        //𝕆 的码点为U+1D546,我们通过一个int值接收。
        int symbol = 0x1D546;
        //根据转换关系表,判断该码点长21位,使用4个代码单元表示
        byte[] utf8Bytes = new byte[4];
        //代码单元1,1111表示需要4个代码单元,0表示长度标识结束。记录完所需的代码单元数,该代码单元还有三个二进制位可以记录码点
        utf8Bytes[0] = (byte) ((0b11110 << 3) | (symbol >> 18));
        //后续的代码单元均由10标记开始,每个代码单元记录6位码点。
        utf8Bytes[1] = (byte) ((0b10 << 6) | ((symbol >> 12) & 0b111111));
        utf8Bytes[2] = (byte) ((0b10 << 6) | ((symbol >> 6) & 0b111111));
        utf8Bytes[3] = (byte) ((0b10 << 6) | (symbol & 0b111111));
        //利用String记录结果
        String result = new String(utf8Bytes, StandardCharsets.UTF_8);
        //输出为𝕆
        System.out.println(result);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值