Unicode编码

本文介绍了ASCII编码的历史和局限性,引出Unicode编码的诞生及其解决的问题。详细讲解了Unicode编码的不同表示方式,包括UTF-8和UTF-16的编码规则,并通过实例解释了Java中String类的charAt()方法在处理多字节字符时的行为。最后,通过代码展示了charAt()方法在处理UTF-16编码的字符时返回的是代码单元而非完整字符的情况。
摘要由CSDN通过智能技术生成

Unicode编码

最近在学习Java的过程中,学到String类时,String类的charAt(index)方法让我产生了很大
的疑问。因此去查阅了unicode编码的相关知识整理如下。
以下内容参考百度百科,整理到这里作为自己的学习笔记。

编码发展史

说到Unicode编码,我们就不得说一说编码的发展史。首先,让我们深层次的了解一下Unicode编码的由来,了解它到底为我们的编码问题解决了什么问题,做出了什么改进。这对我们后面理解Unicode编码有很大的帮助。

ASCII

ASCII码的产生

在计算机中,所有的数据在存储和运算时都要使用二进制数表示,每一个二进制位有0和1两种状态。例如,像a,b,c这样的字母,以及0,1等数字还有一些常用的符号(例如@,*,#等)在计算机中存储时也要使用二进制数来表示。而具体用哪些二进制数表示哪个符号,每个人都可以预定自己的一套规则,这就叫编码。为了能够相互之间进行通信,而又不造成混乱,那么大家就必须使用相同的编码规则。于是美国有关的标准化组织就出台了ASCII编码,统一规定了上述常用符号用哪些二进制数来表示。

表述方式

在计算机中,1字节对应8位二进制数,最初的ASCII码仅用一个字节进行编码。标准ASCII码定义了128个字符,这128个字符只使用了8位2进制数中的后面七位,
最前面的一位统一规定为0,后用作奇偶校验位。
这里就顺便的介绍一下什么是奇偶校验位:
所谓奇偶校验,是指在代码传送过程中用来检验是否出现错误的一种方法,一般分奇校验和偶校验两种。奇校验规定:正确的代码一个字节中1的个数必须是奇数,若非奇数,则在最高位添1;偶校验规定:正确的代码一个字节中1的个数必须是偶数,若非偶数,则在最高位添1。

ASCII码存在的问题

ASCII是美国标准,他只实现了他们需要用到的字符。所以他不能良好满足其他讲英语国家的需要。例如英国的英镑符号(£)在哪里?更不用说中国汉字,拉丁语等字符了。1967年,国际标准化组织推荐了一个ASCII的变种,代码0x40、0x5B、0x5C、0x5D、0x7B、0x7C和0x7D"为国家使用保留",而代码0x5E、0x60和0x7E标为"当国内要求的特殊字符需要8、9或10个空间位置时,可用于其它图形符号"。他的意思就是在0-127号字符上我们达成一致,但是对于128-255号字符可以根据自己国家的需要进行编码。但是这又产生了新的问题,每一个国家的编码方式有可能不同,那么在国家通信之间就会发生很大的问题,这不是我们想要看到的。后来,美国又提出了内码表的概念,每一个编码方式对应一个内码表,想要显示相应语言的字符时,只需要切换到相应语言的内码表就可以了。但是在国家通信中,我们就不得不频繁在内码表内进行切换,这显然是很麻烦的。


Unicode

为了保证一致性,而又不想频繁的切换内码表,那就需要把世界上所有语言中的所有字符都整合起来。因此,Unicode诞生了。

Unicode编码方式

在这里我们先了解几个概念

  • 码点:码点是指一个编码表中的某个字符对应的代码值。
  • 代码平面:Unicode字符分为17组编排,0x0000至0x10FFFF,每组称为代码平面。
  • 基本多语言平面:第一个代码平面称为基本多语言平面,包括码点从U+0000到U+FFFF的“经典”Unicode代码。

在1991年发布了unicode 1.0,当时仅占用65536个代码值中不到一半的部分。在设计Java时决定采用16位的unicode字符集。但是经过一段时间后,不可避免的事情发生了。unicode字符超过了65536个,其主要原因是增加了大量的汉语,日语和韩语中的表意文字。现在,16位二进制数已经不能满足描述所有unicode字符的需要了。
现在,在计算机里,有只需要1个字节编码的字符,也有2个,3个,甚至4个字节编码的字符。那么计算机如何识别呢?
于是,UTF-8 和 UTF-16 两种当前比较流行的编码方式诞生了。

UTF-8

UTF-8以字节为单位对Unicode进行编码。从Unicode到UTF-8的编码方式如下:

Unicode编码(16进制)UTF-8字节流(二进制)
000000-00007F0xxxxxxx
0000080-0007FF110xxxxx 10xxxxxx
000800-00FFFF1110xxxx 10xxxxxx 10xxxxxx
010000-10FFFF11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

UTF-8的特点是对不同范围的字符使用不同长度的编码。对于0x00-0x7F之间的字符,UTF-8编码与ASCII编码完全相同。UTF-8编码的最大长度是4个字节。从上表可以看出,4字节模板有21个x,即可以容纳21位二进制数字。Unicode的最大码位0x10FFFF也只有21位。

例1:"汉"字的Unicode编码是0x6C490x6C490x0800-0xFFFF之间,使用3字节模板:1110xxxx 10xxxxxx 10xxxxxx。将0x6C49写成二进制是:0110 1100 0100 1001, 用这个比特流依次代替模板中的x,得到:11100110 10110001 10001001,即E6 B1 89

例2:Unicode编码0x20C300x010000-0x10FFFF之间,使用4字节模板:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx。将0x20C30写成21位二进制数字(不足21位就在前面补0):0 0010 0000 1100 0011 0000,用这个比特流依次代替模板中的x,得到:11110000 10100000 10110000 10110000,即F0 A0 B0 B0

UTF-16

UTF-16编码以16位无符号整数为单位。我们把Unicode编码记作U。编码规则如下:

  • 如果U<0x10000,则U的UTF-16编码就是U对应的16位无符号整数
  • 如果U≥0x10000,我们先计算U’=U-0x10000,然后将U’写成二进制形式:yyyy yyyy yyxx xxxx xxxx,U的UTF-16编码(二进制)就是:110110yyyyyyyyyy 110111xxxxxxxxxx。

为什么U’可以被写成20个二进制位?Unicode的最大码位是0x10FFFF,减去0x10000后,U’的最大值是0xFFFFF,所以肯定可以用20个二进制位表示。

例如:Unicode编码0x20C30,减去0x10000后,得到0x10C30,写成二进制是:0001 0000 1100 0011 0000。用前10位依次替代模板中的y,用后10位依次替代模板中的x,就得到:1101100001000011 1101110000110000,即0xD843 0xDC30。

按照上述规则,Unicode编码0x10000-0x10FFFF的UTF-16编码有两个16位无符号整数,第一个16位无符号整数的高6位是110110,第二个16位无符号整数的高6位是110111。可见,第一个16位无符号整数的取值范围(二进制)是11011000 00000000到11011011 11111111,即0xD800-0xDBFF。第二个16位无符号整数的取值范围(二进制)是11011100 00000000到11011111 11111111,即0xDC00-0xDFFF。

为了将一个16位无符号整数的UTF-16编码与两个16位无符号整数的UTF-16编码区分开来,Unicode编码的设计者将0xD800-0xDFFF保留下来,并称为代理区:

D800-DB7FHigh Surrogates高位替代
DB80-DBFFHigh Private Use Surrogates高位专用替代
DC00-DFFFLow Surrogates低位替代

高位替代就是指这个范围的码位是两个16位无符号整数的UTF-16编码的第一个16位无符号整数。低位替代就是指这个范围的码位是两个16位无符号整数的UTF-16编码的第二个16位无符号整数。那么,高位专用替代是什么意思?我们来解答这个问题,顺便看看怎么由UTF-16编码推导Unicode编码。

如果一个字符的UTF-16编码的第一个16位无符号整数在0xDB80到0xDBFF之间,那么它的Unicode编码在什么范围内?我们知道第二个16位无符号整数的取值范围是0xDC00-0xDFFF,所以这个字符的UTF-16编码范围应该是0xDB80 0xDC00到0xDBFF 0xDFFF。我们将这个范围写成二进制:

1101101110000000 11011100 00000000 - 1101101111111111 1101111111111111

按照编码的相反步骤,取出高低16位无符号整数的后10位,并拼在一起,得到

1110 0000 0000 0000 0000 - 1111 1111 1111 1111 1111即0xE0000-0xFFFFF,按照编码的相反步骤再加上0x10000,得到0xF0000-0x10FFFF。这就是UTF-16编码的第一个16位无符号整数在0xdDB0到0xDBFF之间的Unicode编码范围,即平面15和平面16。因为Unicode标准将平面15和平面16都作为专用区,所以0xDB80到0xDBFF之间的保留码位被称作高位专用替代。

我的问题

String类的charAt(index)方法将返回位置index的代码单元
但是类似🍺的字符需要两个代码单元
当我们使用"🍺".charAt(0)时,返回的并不是🍺字符,而是🍺字符的第一个代码单元。

public class UnicodeTest {
    public static void main(String[] args) {
        String s = "🍺";
        char str1 = s.charAt(0);//返回🍺的第一个代码单元
        char str2 =s.charAt(1);//返回🍺的第二个代码单元
        System.out.println(str1);//?
        System.out.println("\ud83c");//等同于System.out.println(str1);
        System.out.println(str2);//?
        System.out.println("\udf7a");//等同于System.out.println(str2);
        new UnicodeTest().toBinary(s);//调用toBinary方法
        System.out.println("\ud83c"+"\udf7a");//输出🍺
    }
    public void toBinary(String str){//将字符串str转换为二进制数输出
        char[] strChar=str.toCharArray();//将字符串str存入数组char[]
        String result="";//存放结果
        for(int i=0;i<strChar.length;i++){
            result +=Integer.toBinaryString(strChar[i])+ " ";//转换二进制写入结果
        }
        System.out.println(result);
    }
}

运行代码如下:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

难啊楠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值