Java中的char只能表示部分字符

小bit最近在敲代码的时候,碰到了一个奇怪的问题。一个表情复制给char竟然报编译,难道是被我发现了java的重大漏洞吗?提交漏洞会不会出名了?是不是可以走上人生巅峰了?赶紧上网上查查。

char ch = '😁'; // 编译错误

一查才发现,这个是一个已知的问题。Java中的char使用的是2字节的UTF-16的编码方式,不能表示这种emoji表情。为什么呢?我们先来看下UTF-16是如何编码unicode的。

Unicode最多可以1114112个字符,数字0-0x10FFFF来映射这些字符。这些字符被分配到了17个平面,一个基本多文种平面BMP,基本平面对应的是0 ~ 0xFFFF,包含了世界上正在使用的常用字符,因此,平常使用的字符一般都位于BMP平面上。16个辅助平面SP,留作扩展之用或用来表示一些特殊的字符。BMP里存在一种特殊区域: 代理区(Surrogate)。Unicode标准规定U+D800 - U+DFFF的值不对应于任何字符。后面可以看到,UTF-16就巧妙地利用了这一段空白区域进行了编码的转换。

现在我们来看下UTF-16是怎么进行编码的,如果是字符在基本平面的,UTF-16就使用两个字节来标识,对应的值就是字符对应的码点。如果是辅助平面的字符,两个字节已经无法表示,UTF-16使用四个字节来标识这部分的字符。这就就需要用到BMP中的代理区。如果一个辅助平面的字符,则对应的码点先减去 0x10000,因为辅助平面字符的范围在0x10000 ~ 0x10FFFF,这样换算后范围就变为了 0 ~ 0xFFFFF,对应的二进制为yyyyyyyyyyxxxxxxxxxx,然后把前面10位固定拼接110110,110110yyyyyyyyyy,使用两个字节表示,作为高位代理,后面10位拼接110111,使用两个字节来标识低位代理,通过高位代理和低位代理,使用4个字节来表示一个辅助平面的字符。因为高位代理和低位代理都是在BMP的代理区,所以不会和其他BMP中的正常字符弄混。

下面我们看一个例子。😁,对应的UNICODE码点是 128513,对应16进制是0x1f601,减去0x10000后为 0xf601,对应的2进制是1111011000000001,分割为高位代理是1101100000111101 对应是 0xD83D, 低位代理是 1101111000000001 对应16进制是 0xDE01,所以对应的UTF-16编码是\uD83D\uDE01 。

上面介绍了这么多,我们回来再看下我们今天碰到的问题。Java中的char为啥不能表示笑脸呢?因为char是两个字节的,只能表示UNICDOE中的BMP,但是笑脸不在BMP,需要4个字节来表示,所以才会报错的。

Java中提供了很多对对辅助字符的支持。

//利用codePoint直接构造String
        String s1 = new String(new int[]{128513 } , 0 , 1);
        System.out.println("s1 : " + s1);
        //输出 s1 : 😁

        //利用Character.toChars返回对应的char数组,从而构造string
        String s2 = new String(Character.toChars(128513));
        System.out.println("s2 : " + s2);
        //输出 s2 : 😁

        String s3 = "😁123中文😁";
        System.out.println(s3.length());
        //输出 9 ,对应的是string中char数组的长度
        System.out.println(s3.codePointCount(0 , s3.length()));
        //输出7, 对应的是string中unicode字符的个数

        int codePoint1 = s3.codePointAt(0);
        System.out.println("codePoint1  : " + codePoint1);
        //输出是 codePoint1  : 128513,对应的是 😁 的unicode码点
        int codePoint2 = s3.codePointAt(2);
        System.out.println("codePoint2  : " + codePoint2);
        //输出是 codePoint2  : 49,对应的是 1 的unicode码点

        System.out.println(Character.isBmpCodePoint(codePoint1));
        //输出false,因为 码点 128513 不属于基本平面BMP的
        System.out.println(Character.isSupplementaryCodePoint(codePoint1));
        //输出true, 因为 码点 128513 属于辅助平面SP的

        System.out.println(Character.isBmpCodePoint(codePoint2));
        //输出true 因为 码点 49 属于基本平面BMP的
        System.out.println(Character.isSupplementaryCodePoint(codePoint2));
        //输出false , 因为 码点 49 不属于辅助平面BMP的

        System.out.println(Integer.toHexString((int)Character.highSurrogate(codePoint1)));
        //d83d,对应我们在文章上面算出来的高位代理
        System.out.println(Integer.toHexString((int)Character.lowSurrogate(codePoint1)));
        //de01,对应我们在文章上面算出来的低位代理

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值