char: The char data type is a single 16-bit Unicode character. It has a minimum value of '\u0000' (or 0) and a maximum value of '\uffff' (or 65,535 inclusive).

从java的文档中这句话我们可以看出,java中的字符内部是以UTF-16编码方式表示的,最小值是 \u0000 (0),最大值是\uffff(65535), 也就是一个字符以2个字节来表示。那这个意思是Java最多只能表示 65535 个字符?当然这肯定是不可能的,现在16位的Char类型已经不能满足描述所有Unicode字符的需要了,java采用了新的方法来解决这个问题

先引出两个概念,代码点,代码单元。

1.代码点(code point):与编码表中的某个字符对应的代码值

在Unicode标准中,代码点采用十六进制书写,并加上前缀U+。例U+0041就是字母A的代码点Unicode的代码点可以分成17个代码级别(code plane)。第一个代码级别成为基本的多语言级别,代码点从U+0000到U+FFFF,其中包括了经典的Unicode代码
其余的16个附加级别,代码点从U+10000到U+10FFFF,其中包括了一些辅助字符(增补字符)

上述提到了增补字符,这个我们后面在进行叙述。

2.代码单元(code unit):在第一代码级别中,每个字符用16位表示(UTF-16的代码单元就是两个字节),辅助字符在UTF-16中就需要采用两个连续的代码单元进行编码

UTF-16编码采用不同长度的编码表示所有的Unicode代码点.

通俗理解 :
代码点 : Unicode中编码的各个字符
代码单元 :
在具体编码形式中的最小单位。比如 UTF-16 中一个 code unit 为 16 bits,UTF-8 中一个 code unit 为 8 bits。一个 code point 可能由一个或多个 code unit(s) 表示。在 U+10000 之前的 code point 可以由一个 UTF-16 code unit 表示,U+10000 及之后的 code point 要由两个 UTF-16 code units 表示

Unicode(代码点) 常用字符 辅助字符
数量(代码单元) 一个代码单元 一对代码单元

 由上图我们可以看出,同一个代码点在不同的编码方式中的代码单元可能不同

引入一个例子:

public class Main {public static void main(String[] args) {// 中文常见字String s = "你好";System.out.println("1. string length =" + s.length());System.out.println("1. string bytes length =" + s.getBytes().length);System.out.println("1. string char length =" + s.toCharArray().length);System.out.println();// emojiss = "??";System.out.println("2. string length =" + s.length());System.out.println("2. string bytes length =" + s.getBytes().length);System.out.println("2. string char length =" + s.toCharArray().length);System.out.println();// 中文生僻字s = "?妹";System.out.println("3. string length =" + s.length());System.out.println("3. string bytes length =" + s.getBytes().length);System.out.println("3. string char length =" + s.toCharArray().length);System.out.println();}
}

运行这个程序,你觉得输出结果是什么?

输出结果:

1. string length =2
1. string bytes length =6
1. string char length =2
2. string length =4
2. string bytes length =8
2. string char length =4
3. string length =3
3. string bytes length =7
3. string char length =3

我们知道,String.getBytes()如果不指定编码格式,Java会使用操作系统的编码格式得到字节数组,在我的MacOS中,默认使用UTF-8作为字符编码(locale命令可以查看操作系统的编码),所以在我的机器运行,String.getBytes()会返回UTF-8编码的字节数组。

String.length返回代码单元的长度(UTF-16编码)。

String.toCharArray返回字符数组。

Unicode

Unicode解决了各国自行一套的问题,将世界上所有的符号都纳入其中。它符提供了唯一码点,不论是什么平台、不论是什么程序、不论是什么语言。

  • 码点code point范围从 0x0 - 0x10FFFF,共分为17个Plane,每个Plane中有65536个字符,共可容纳: 17*(16*16*16*16)= 1114112 个字符。

  • 第一个平面称为基本多语言平面(Basic Multilingual Plane, BMP)。其他平面称为辅助平面(Supplementary Planes, SP),或astral Plane。

  • BMP内,从U+D800到U+DFFF之间的码位区块是永久保留不映射到Unicode字符。后面介绍的UTF-16就利用保留下来的0xD800-0xDFFF区段的码位来对辅助平面的字符的码位进行编码。

我们设置的字符串都是两个unicode字符,输出结果:

  • 普通的中文字:字符串的长度是2,每个中文字按UTF-8编码是三个字节,字符数组的长度看起来也没问题

  • emojis字符:我们设置了两个emojis字符,男女头像。结果字符串的长度是4, UTF-8编码8个字节,字符数组的长度是4

  • 生僻的中文字:我们设置了两个中文字,其中一个是生僻的中文字。结果字符串的长度是3, UTF-8编码7个字节,字符数组的长度是3

看起来字符串的字符数和我们预期的有点不一样,我们的字符串只有两个unicode字符, 可是输出结果有时候是2,有时候是3, 有时候是4,为什么呢?

不管为什么,这至少说明了java中表示一个字符,某些字符使用一个char,但是某些字符使用两个char。也就说明就,java肯定不可能只能表示6万多个字符。

增补字符

Unicode码代码点为U+0000到U+10FFFF,一共1114112个码位,其中U+0000 到U+FFFF的部分被称为基本多语言面(Basic Multilingual Plane,BMP)。U+10000及以上的字符称为增补字符。在Java中(Java1.5之后),增补字符使用两个char型变量来表示。第一个char型变量的范围称为“高代理部分”(high-surrogates range,从"uD800到"uDBFF,共1024个码位), 第二个char型变量的范围称为low-surrogates range(从"uDC00到"uDFFF,共1024个码位),这样使用surrogate pair可以表示的字符数一共是1024的平方计1048576个,加上BMP的65536个码位,去掉2048个非法的码位,正好是1,112,064个码位。

有可能你会问, 对于一个UTF-16编码的扩展字符,它以4个字节来表示,那么前两个字节会不会和BMP平面冲突,导致程序不知道它是扩展字符还是BMP平面的字符?

其实是不会的, 幸运的是, 在BMP平面中, U+D800到U+DFFF之间的码位是永久保留不映射到Unicode字符,UTF-16就利用保留下来的0xD800-0xDFFF区块的码位来对辅助平面的字符的码位进行编码。

UTF-16编码中,辅助平面中的码位从U+10000到U+10FFFF,共计FFFFF个,需要20位来表示。第一个整数(两个字节,称为前导代理)要容纳上述20位的前10位,第二个整数(称为后尾代理)容纳上述20位的后10位。

前导代理的值的范围是0xD800到0xDBFF,后尾代理的0xDC00~0xDFFF。可以看到前导代理和后尾代理的范围都落在了BMP平面中不用来映射的码位,所以不会产生冲突,而且前导代理和后尾代理也没有重合。

这样我们得到两个字节的,就可以直接判断它是否是BMP平面的字符,还是扩展字符中的前导代理还是后尾代码。

    import java.io.*;  class TestSup   {  public static void main(String[] args) throws IOException  {  int[] codePoints = {0xd801,0xd802,0xdf00,0xdf01,0x34};  String str = new String(codePoints,0,5);  char[] ch = str.toCharArray();  for(char c:ch){  System.out.print(c+"--"+Integer.toHexString(c)+" ");//输出???,因为Unicode中不存在这样的char  }  /*测试能否写入文件*/  FileWriter out = new FileWriter("aa");  out.write(ch);  out.close();  System.out.print("\n***********************\n");  FileReader in = new FileReader("aa");  int c;  /** *对比结果发现非代理范围的字符可以正常写入与读出,但是来自高代理与低代理范围的 *字符无法正常写入,而是被转化为0x3f */  while((c = in.read()) != -1){  System.out.print(Integer.toHexString(c)+" ");//为什么是3f?  }  in.close();  System.out.println(str);  }  }  

可以得出:如果要向文本文件写入或读出增补字符,只能采用stream的方式读写。读出后根据代理范围进行判断,是否是增补字符(需要考虑编码)。比如是utf-16编码,需要根据高低代理范围进行判断。

对于char类型来说,charAt(int index)只能获取BMP的字符,对于增补字符,是无法正常获得的.所以当字符串中包含增补字符又该如何获取呢,当文档当中有增补字符呢?

    public static void main(String[] args) throws Exception{// 构造一个高代理部分和底代理部分int[] codePoints = {0xd899,0xdc99};String s = new String(codePoints,0,2);// 可以发现只输出了一个字符,也就是一个增补字符System.out.println("s: " + s);// 说明length()是按代码单元计算的System.out.println("s.length: " + s.length());// 输出结果是两个代码单元的值,不能正确的获取到字符System.out.println((int)s.charAt(0)+" "+(int)s.charAt(1));// 返回的是一个代码点的值System.out.println(s.codePointAt(0));}

方法介绍

String类中的codePointAt(int index)
Character.isSupplementaryCodePoint(int codePoint)
java.lang.Character.toChars(int codePoint)
IntStream codePoints()
int codePointCount(int beginIndex, int endIndex)

1.String类中的codePointAt(int index)

若index所指的为BMP(基本多文种平面或平面0)的索引,则直接返回该代码点值,
否则,当index所指的为增补字符的索引:1.索引指定的char值属于高代理项范围,则返回该增补字符的代码点值.2.索引指定的char值属于低代理项范围,则返回该增补字符的低代理项的代码点值.也就是把低代理项当成独立的项来看待了

2.Character.isSupplementaryCodePoint(int codePoint)

确定指定字符(Unicode 代码点)是否在增补字符范围内。

3.java.lang.Character.toChars(int codePoint)

指定字符(Unicode代码点)存储在一个UTF-16表示形式转换的字符数组。
如果指定的代码点为BMP(基本多文种平面或平面0)的值,由此产生的char数组具有相同的值码点。
如果指定的代码点是一个增补代码点,由此产生的char数组具有相应的代理对。

4.IntStream codePoints()

返回所有代码点的值的int类型的流,他可以判断出一个字符串中正确的字符的个数,而不是代码单元。

4.int codePointCount(int beginIndex, int endIndex)

返回下标范围内的代码点个数。注意:这里的下标就是字符数组下标

自 Java 1.5 java.lang.String就提供了Code Point方法, 用来获取完整的Unicode字符和Unicode字符数量

java中的字符个数,增补字符相关推荐

  1. java string设置编码_详解Java中String类型与默认字符编码

    为什么写这个 至于为什么要写这个,主要是一句mmp一定要讲,绕了一上午,晕死 Java程序中的中文乱码问题一直是一个困扰程序员的难题,自己也不例外,早在做项目时就遇到过很多编码方式的坑,当时想填来着, ...

  2. 解析java中的字面量和字符类型

    解析java中的字面量和字符类型 1.字面量含义 固定不变的量,我们人为所给的一些数据.例如77和88都是整型字面量,1.88和1.99F是浮点型字面量,'中'是字符型字面量,"dfguyf ...

  3. 关于java中char占用2个字符的解释

    文章来源链接 1.区分字符集和字符编码 字符集:统一的字符编号,仅仅提供字符与编号间的映射.如unicode的符号数量在不断增加,已超百万.详细[https://zh.wikipedia.org/zh ...

  4. Java中string字符串和char字符之间的千丝万缕

    目录 前言 String字符串和char字符的区别 拓展 相互转换 String字符串转换成char字符数组 char字符转换成String字符串 char字符数组转换成String字符串 Strin ...

  5. Java中从字符串删除指定字符

    public class RemoveChar {public static void main(String[] args) throws IOException {Scanner sc=new S ...

  6. Java中求一个数的幂次方

    Java中求 m 的 n 次方不能使用 m^n,可使用 Math.pow(m, n) 来求m的n次方,求得结果为浮点类型.

  7. Java中switch对整型/字符型/字符串型具体实现细节

    转自:http://www.hollischuang.com/archives/61 Java7中switch中支持的数据类型有: byte short int char String类型 其实swi ...

  8. java中printreader类_Java基本字符流输入输出类的使用

    1. 基本字符输入输出类结构 Java基本字符流类结构 2. 抽象父类:Writer & Reader Writer类 public abstract class Writer extends ...

  9. java中什么是字节流和字符流_java中字节流与字符流的区别是什么

    字节(Byte)是进行io操作的基本数据单位,在程序进行字节数据输出时可以使用OutputStream类完成 此类定义如下: public abstract class OutputStream ex ...

  10. java 搜索起始位置,从Java中的给定位置搜索字符

    使用该indexOf()方法从给定位置搜索字符. 假设以下是我们的字符串.String myStr = "Amit Diwan"; 在这里,我们正在字符串中搜索字符" i ...

最新文章

  1. Gartner:企业架构开始更加紧密地向业务看齐
  2. PMP知识点(六、质量管理)
  3. distance from ifm to Sidney Sussex College: acceptable
  4. Upma Xmac 测试 03
  5. pymysql(part4)--mysql存储图片信息
  6. jxl导入/导出excel(网上的案例)
  7. java jmap mat_利用jmap和MAT等工具查看JVM运行时堆内存
  8. mac下解决中文乱码的问题
  9. python socket原理 及socket如何使(tcp udp协议)
  10. hfss matlab api 天线,应用HFSS-MATLAB-API设计圆极化微带天线
  11. PLC数据采集之协议转换桥接器
  12. 手写Promise 封装Promise resolve reject then catch Promise.resolve Promise.reject
  13. 工程经济学99分速成复习——第一章 绪论
  14. ANSYS APDL 绘制云图时出现错误“The Requested S data is not available. The PLNSOL command is ignored“的解决方法
  15. VS中修改解决方案/项目/类的名字
  16. 成熟男人与24岁女孩精彩对白 —非常感动
  17. T51 运行文本指令
  18. 如何成为羽毛球高手?你距离一个真正的羽毛球高手还有多远?
  19. 五年Android开发大厂面经总结,吐血整理2022Android面试题合集(符解析)你确定不看看?
  20. Backtrader获得当前持仓详情——持仓数量与持仓的名称

热门文章

  1. 北大硕士毕业,人大在读博一,IJCAI资深审稿人
  2. 所罗门群岛必试体验:去Uepi潜水
  3. ASP.NET上传文件的三种基本方法
  4. java--数组的使用·
  5. Hibernate的游离态与持久态转换
  6. 浪潮inBuilder低代码平台社区版来了!
  7. bootstraptable 列隐藏_bootstraptable 隐藏列的方法
  8. FRED准直透镜模拟与优化
  9. Ctrl + / 快捷键失效解决方案
  10. 使用clip-path裁剪图形