Java的String类中提到的代码点,代码单元到底是什么?

简介: Java的String类中提到的代码点,代码单元到底是什么?

42.png

unicode

unicode是计算机科学领域里的一项业界标准,包括字符集、编码方案等。计算机采用八比特一个字节,一个字节最大整数是255,还要表示中文一个字也是不够的,至少需要两个字节,为了统一所有的文字编码,unicode为每种语言中的每个字符设定了统一并且唯一的二进制编码,通常用两个字节表示一个字符,所以unicode每个平面可以组合出65535种不同的字符,一共17个平面。


由于英文符号只需要用到低8位,所以其高8位永远是0,因此保存英文文本时会多浪费一倍的空间。


比如汉子“汉”的unicode,在java中输出


System.out.println("\u5B57");

UTF-8

unicode在计算机中如何存储呢,就是用unicode字符集转换格式,即我们常见的UTF-8、UTF-16等。

UTF-8就是以字节为单位对unicode进行编码,对不同范围的字符使用不同长度的编码。

Unicode Utf-8

000000-00007F 0xxxxxxx

000080-0007FF 110xxxxx 10xxxxxx

000800-00FFFF 1110xxxx 10xxxxxx 10xxxxxx

010000-10FFFF 11110xxx10xxxxxx10xxxxxx10xxxxxx

Java中的String对象就是一个unicode编码的字符串。


java中想知道一个字符的unicode编码我们可以通过Integer.toHexString()方法

    String str = "编";
    StringBuffer sb = new StringBuffer();
    char [] source_char = str.toCharArray();
    String unicode = null;
    for (int i=0;i<source_char.length;i++) {
        unicode = Integer.toHexString(source_char[i]);
        if (unicode.length() <= 2) {
            unicode = "00" + unicode;
        }
        sb.append("\\u" + unicode);
    }
    System.out.println(sb);
    输出\u7f16

对应的utf-8编码是什么呢?


7f16在0800-FFFF之间,所以要用3字节模板:1110xxxx 10xxxxxx 10xxxxxx。

7f16写成二进制是:0111 1111 0001 0110

按三字节模板分段方法分为0111 111100 010110,代替模板中的x,得到11100111 10111100 10010110,即“编”对应的utf-8的编码是e7 bc 96,占3个字节

codepoint

unicode的范围从000000 - 10FFFF,char的范围只能是在\u0000到\uffff,也就是标准的 2 字节形式通常称作 UCS-2,在Java中,char类型用UTF-16编码描述一个代码单元,但unicode大于0x10000的部分如何用char表示呢,比如一些emoji:😀


java的char类型占两个字节,想要表示😀这个表情就需要2个char,看如下代码


String testCode = “ab\uD83D\uDE03cd”;

int length = testCode.length();

int count = testCode.codePointCount(0, testCode.length());

//length=6

//count=5

第三个和第四个字符合起来代表😀,是一个代码点,

如果我们想取到每个代码点做一些判断可以这么写

    String testCode = "ab\uD83D\uDE03cd";
    int cpCount = testCode.codePointCount(0, testCode.length());
    for(int index = 0; index < cpCount; ++index) {
        //这里的i是字符的位置
        int i = testCode.offsetByCodePoints(0, index);
        int codepoint = testCode.codePointAt(i);
    }
  //输出
  i:0 index: 0 codePoint: 97
  i:1 index: 1 codePoint: 98
  i:2 index: 2 codePoint: 128515
  i:4 index: 3 codePoint: 99
  i:5 index: 4 codePoint: 100

也就是按照codePointindex取字符,0取到a,1取到b,2取到\uD83D\uDE03也就是😀,3取到c,4取到d;

按照String的index取字符,0取到a,1取到b,2取到\uD83D,3取到\uDE03,4取到c,5取到d。

这就是codePointIndex和char的index的区别。


取到codePoint就可以按照unicode值进行字符的过滤等操作。


如果有个需求是既可以按照unicode值过滤字符,也能按照正则表达式过滤字符,并且还有白名单,应该如何实现呢。


其实unicode过滤和正则表达式过滤并不冲突,自己实现自己的过滤就好了,如果需求加入了过滤白名单就会复杂一些,不能直接过滤,需要先检验是否是白名单的index。


我的思路是记录白名单char的index,正则表达式或其他过滤方式可以获得违规char的index,unicode黑名单的codepointIndex可以转换成char的index,在获取codePont的index时可以判断当前字符是单char字符还是双char字符,双char字符需要添加2个下标,方法如下

    //取到unicode值           
    int codepoint = testCode.codePointAt(i);
    //将unicode值转换成char数组
    char[] chars = Character.toChars(codepoint);
    charIndexs.add(pointIndex);
    if (chars.length > 1) {
        //表示不是单char字符,记录index时同时添加i+1
       charIndexs.add(pointIndex + 1);
    }

//例

String str = “ab\uD83D\uDE03汉字”;

想处理emoji,那记录的下标就是2、3,最后和白名单下标比较后统一删除


如何区别char是一对还是单个

就之前的例子ab\uD83D\uDE03cd,换种写法\u0061\u0062\uD83D\uDE0\u0063\u0064

程序是如何将\uD83D\uDE03解析成一个字符的呢。这就需要Surrogate这个概念,来自UTF-16。


UTF-16是16bit最多编码65536,那大于65536如何编码?Unicode 标准制定组想出的办法是,从这65536个编码里,拿出2048个,规定他们是「Surrogates」,让他们两个为一组,来代表编号大于65536的那些字符。

编号为 U+D800 至 U+DBFF 的规定为「High Surrogates」,共1024个。

编号为 U+DC00 至 U+DFFF 的规定为「Low Surrogates」,也是1024个。

他们组合出现,就又可以多表示1048576中字符。


看一下String.codePointAt这个方法,

static int codePointAtImpl(char[] a, int index, int limit) {
    char c1 = a[index];
    if (isHighSurrogate(c1) && ++index < limit) {
        char c2 = a[index];
        if (isLowSurrogate(c2)) {
            return toCodePoint(c1, c2);
        }
    }
    return c1;
}

其中有两个方法isHighSurrogate、isLowSurrogate。

第一个方法判断是否为高代理项代码单元,即在’\uD800’与’\uDBFF’之间,

第二个方法判断是否为低代理项代码单元,即在’\uDC00’与’\uDFFF’之间。


codePointAtImpl方法判断当前char是高代理项代码单元,下一个是低代理项代码单元,则这两个char是一个codepoint。


再来看一下unicode转UTF-16的方法


如果U<0x10000,U的UTF-16编码就是U对应的16位无符号整数(为书写简便,下文将16位无符号整数记作WORD)。

如果U≥0x10000,我们先计算U’=U-0x10000,然后将U’写成二进制形式:yyyy yyyy yyxx xxxx xxxx,U的UTF-16编码(二进制)就是:110110yyyyyyyyyy 110111xxxxxxxxxx。


还是以U+1F603这个😃为例子,U’=U-0x10000=F603

写成2进制就是1111011000000011,不足20位前面补0,

变成0000111101-1000000011,替换y和x就是1101100000111101,1101111000000011,最后UTF-16编码就是[d83d,de03] 和上面一样。


目录
相关文章
|
1月前
|
Java
在 Java 中捕获和处理自定义异常的代码示例
本文提供了一个 Java 代码示例,展示了如何捕获和处理自定义异常。通过创建自定义异常类并使用 try-catch 语句,可以更灵活地处理程序中的错误情况。
56 1
|
7天前
|
存储 JavaScript Java
Java 中的 String Pool 简介
本文介绍了 Java 中 String 对象及其存储机制 String Pool 的基本概念,包括字符串引用、构造方法中的内存分配、字符串文字与对象的区别、手工引用、垃圾清理、性能优化,以及 Java 9 中的压缩字符串特性。文章详细解析了 String 对象的初始化、内存使用及优化方法,帮助开发者更好地理解和使用 Java 中的字符串。
Java 中的 String Pool 简介
|
13天前
|
缓存 安全 Java
java 为什么 String 在 java 中是不可变的?
本文探讨了Java中String为何设计为不可变类型,从字符串池的高效利用、哈希码缓存、支持其他对象的安全使用、增强安全性以及线程安全等方面阐述了不可变性的优势。文中还通过具体代码示例解释了这些优点的实际应用。
java 为什么 String 在 java 中是不可变的?
|
25天前
|
JSON Java 关系型数据库
Java更新数据库报错:Data truncation: Cannot create a JSON value from a string with CHARACTER SET 'binary'.
在Java中,使用mybatis-plus更新实体类对象到mysql,其中一个字段对应数据库中json数据类型,更新时报错:Data truncation: Cannot create a JSON value from a string with CHARACTER SET 'binary'.
37 4
Java更新数据库报错:Data truncation: Cannot create a JSON value from a string with CHARACTER SET 'binary'.
|
20天前
|
Java
java小工具util系列4:基础工具代码(Msg、PageResult、Response、常量、枚举)
java小工具util系列4:基础工具代码(Msg、PageResult、Response、常量、枚举)
47 24
|
2天前
|
前端开发 Java 测试技术
java日常开发中如何写出优雅的好维护的代码
代码可读性太差,实际是给团队后续开发中埋坑,优化在平时,没有那个团队会说我专门给你一个月来优化之前的代码,所以在日常开发中就要多注意可读性问题,不要写出几天之后自己都看不懂的代码。
34 2
|
7天前
|
存储 Java
Java 11 的String是如何优化存储的?
本文介绍了Java中字符串存储优化的原理和实现。通过判断字符串是否全为拉丁字符,使用`byte`代替`char`存储,以节省空间。具体实现涉及`compress`和`toBytes`方法,前者用于尝试压缩字符串,后者则按常规方式存储。代码示例展示了如何根据配置决定使用哪种存储方式。
|
17天前
|
Java 编译器 数据库
Java 中的注解(Annotations):代码中的 “元数据” 魔法
Java注解是代码中的“元数据”标签,不直接参与业务逻辑,但在编译或运行时提供重要信息。本文介绍了注解的基础语法、内置注解的应用场景,以及如何自定义注解和结合AOP技术实现方法执行日志记录,展示了注解在提升代码质量、简化开发流程和增强程序功能方面的强大作用。
54 5
|
17天前
|
存储 算法 Java
Java 内存管理与优化:掌控堆与栈,雕琢高效代码
Java内存管理与优化是提升程序性能的关键。掌握堆与栈的运作机制,学习如何有效管理内存资源,雕琢出更加高效的代码,是每个Java开发者必备的技能。
45 5
|
23天前
|
存储 缓存 安全
java 中操作字符串都有哪些类,它们之间有什么区别
Java中操作字符串的类主要有String、StringBuilder和StringBuffer。String是不可变的,每次操作都会生成新对象;StringBuilder和StringBuffer都是可变的,但StringBuilder是非线程安全的,而StringBuffer是线程安全的,因此性能略低。
42 8