java中的字符个数,增补字符
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中的字符个数,增补字符相关推荐
- java string设置编码_详解Java中String类型与默认字符编码
为什么写这个 至于为什么要写这个,主要是一句mmp一定要讲,绕了一上午,晕死 Java程序中的中文乱码问题一直是一个困扰程序员的难题,自己也不例外,早在做项目时就遇到过很多编码方式的坑,当时想填来着, ...
- 解析java中的字面量和字符类型
解析java中的字面量和字符类型 1.字面量含义 固定不变的量,我们人为所给的一些数据.例如77和88都是整型字面量,1.88和1.99F是浮点型字面量,'中'是字符型字面量,"dfguyf ...
- 关于java中char占用2个字符的解释
文章来源链接 1.区分字符集和字符编码 字符集:统一的字符编号,仅仅提供字符与编号间的映射.如unicode的符号数量在不断增加,已超百万.详细[https://zh.wikipedia.org/zh ...
- Java中string字符串和char字符之间的千丝万缕
目录 前言 String字符串和char字符的区别 拓展 相互转换 String字符串转换成char字符数组 char字符转换成String字符串 char字符数组转换成String字符串 Strin ...
- Java中从字符串删除指定字符
public class RemoveChar {public static void main(String[] args) throws IOException {Scanner sc=new S ...
- Java中求一个数的幂次方
Java中求 m 的 n 次方不能使用 m^n,可使用 Math.pow(m, n) 来求m的n次方,求得结果为浮点类型.
- Java中switch对整型/字符型/字符串型具体实现细节
转自:http://www.hollischuang.com/archives/61 Java7中switch中支持的数据类型有: byte short int char String类型 其实swi ...
- java中printreader类_Java基本字符流输入输出类的使用
1. 基本字符输入输出类结构 Java基本字符流类结构 2. 抽象父类:Writer & Reader Writer类 public abstract class Writer extends ...
- java中什么是字节流和字符流_java中字节流与字符流的区别是什么
字节(Byte)是进行io操作的基本数据单位,在程序进行字节数据输出时可以使用OutputStream类完成 此类定义如下: public abstract class OutputStream ex ...
- java 搜索起始位置,从Java中的给定位置搜索字符
使用该indexOf()方法从给定位置搜索字符. 假设以下是我们的字符串.String myStr = "Amit Diwan"; 在这里,我们正在字符串中搜索字符" i ...
最新文章
- Gartner:企业架构开始更加紧密地向业务看齐
- PMP知识点(六、质量管理)
- distance from ifm to Sidney Sussex College: acceptable
- Upma Xmac 测试 03
- pymysql(part4)--mysql存储图片信息
- jxl导入/导出excel(网上的案例)
- java jmap mat_利用jmap和MAT等工具查看JVM运行时堆内存
- mac下解决中文乱码的问题
- python socket原理 及socket如何使(tcp udp协议)
- hfss matlab api 天线,应用HFSS-MATLAB-API设计圆极化微带天线
- PLC数据采集之协议转换桥接器
- 手写Promise 封装Promise resolve reject then catch Promise.resolve Promise.reject
- 工程经济学99分速成复习——第一章 绪论
- ANSYS APDL 绘制云图时出现错误“The Requested S data is not available. The PLNSOL command is ignored“的解决方法
- VS中修改解决方案/项目/类的名字
- 成熟男人与24岁女孩精彩对白 —非常感动
- T51 运行文本指令
- 如何成为羽毛球高手?你距离一个真正的羽毛球高手还有多远?
- 五年Android开发大厂面经总结,吐血整理2022Android面试题合集(符解析)你确定不看看?
- Backtrader获得当前持仓详情——持仓数量与持仓的名称