目录
Java 中 char 型,两字节,16位,字面量用单引号扩起来,例如 'A' 是编码值为 65 所对应的字符常量。 'A' 与 "A" 不同,后者是包含一个字符 A 的字符串。
所以 char 型有什么特别的地方呢?Talk is cheap, show you my code!
public class Demo{
public static void main(String... args){
String str = " ";
System.out.println(str.length());
}
}
所以这个输出应该是啥呢?是 2
用 length 方法可以得到 string 的代码单元数,也就是说字符 的代码单元数为 2 。Why ?这个就说来话长了,听我给你慢慢唠嗑。
了解 char 类型
想要了解 char 类型,就必须了解 Unicode 编码机制。20 世纪 80 年代开始启动设计 Unicode 的工作时,人们认为两个字节(16 位)的代码宽度足以对世界上各种语言的所有字符进行编码,并有足够空间留给未来的扩展。在设计 Java 时决定采用 16 位的 Unicode 字符集。
结果过了一段时间,Unicode 字符超过了 65536(2^16) 个,其主要原因是增加了大量的汉语、日语和韩语中的表意文字。现在 16 位的 char 类型已经不能满足描述所有 Unicode 字符的需要了。
现在 Unicode 标准已经扩展到包含多达 1112064 个字符,那些超出原来的 16 位限制的字符被称作增补字符。然而 Java 是怎样做扩展的呢?
Unicode 基本字符用一个 char 型表示
Unicode 增补字符用一对 char 型表示
Unicode
编码字符集是一个字符集,它为每个字符分配一个唯一数字。 Unicode 标准的核心是一个编码字符集, 字母 “A” 的编码为 0041(16) 、字符 “€” 的编码为 20��(16) 。Unicode 标准始终使用十六进制数字,而且在书写时在前面加上前缀 “U+” ,所以 “A” 的编码书写为 “U+0041” 。
代码点是指可用于编码字符集的数字。编码字符集定义一个有效的代码点范围,但是并不一定将字符分配给所有这些代码点。有效的 Unicode 代码点范围是 U+0000 到 U+10FFFF 。Unicode 4.0 将字符分配给一百多万个代码点中的 96382 代码点。
基本字符表示从 U+0000 到 U+FFFF 之间的字符集,也被成为基本多语言面(BMP)。
增补字符表示从 U+10000 到 U+10FFFF 范围之间的字符集,也就是原来的 16 位设计无法表示的字符。
字符编码方案
字符编码方案是从一个或多个编码字符集到一个或多个固定宽度单元序列的映射。最常用的代码单元是字节,但是 16 位或 32 位整数也可用于内部处理。 UTF-32 、 UTF-16 、 UTF-8 是 Unicode 标准的编码字符集的字符编码方案。
几个字符不同表达方式的比较
UTF-8
UTF-8 使用一至四个字节的序列对编码 Unicode 代码点进行编码。
U+0000 至 U+007F 使用一个字节编码
U+0080 至 U+07FF 使用两个字节
U+0800 至 U+FFFF 使用三个字节
U+10000 至 U+10FFFF 使用四个字节
UTF-8 设计原理为:字节值 0x00 至 0x7F 始终表示代码点 U+0000 至 U+007F(Basic Latin 字符子集,它对应 ASCII 字符集)。这些字节值永远不会表示其他代码点,这一特性使 UTF-8 可以很方便地在软件中将特殊的含义赋予某些 ASCII 字符。
UTF-8 编码方案
拿欧元符号( € )来举例
1.它的 Unicode 代码点为 U+20AC
2.根据上面的表格,欧元符号将被编码为 3 字节
3.20AC 的二进制表示为 0010 0000 1010 1100 (补足前两个 0 是因为上述表格要求占据 16 位)
4.将二进制表示按顺序填充到上述表格的 xxxx 位置
5.得到欧元符号的 UTF-8 编码 1110 0010 1000 0010 1010 1100 ,表示为 16 进制为 E2 82 AC。
UTF-16
UTF-16 使用一个或两个未分配的 16 位代码单元的序列对 Unicode 代码点进行编码。
值 U+0000 至 U+FFFF 编码为一个相同值的 16 位单元。
增补字符编码为两个代码单元,第一个单元来自于高代理范围(U+D800 至 U+DBFF),第二个单元来自于低代理范围(U+DC00 至 U+DFFF)。
值 U+D800 至 U+DFFF 保留用于 UTF-16,这些值没有分配给字符作为代码点。这意味着,对于一个字符串中的每个单独的代码单元,软件可以识别是否该代码单元表示某个单单元字符,或者是否该代码单元是某个双单元字符的第一个或第二单元。
几个 UTF-16 编码的例子
将一个字符编码为 UTF-16 的方式
1.将此字符的代码点减去 0x010000 ,留下一个 20 位的数字,它的范围在 0x000000 ~ 0xFFFFF
2.将留下的 20 位数字的高十位取出加上 0xD800 得到高代理
3.将留下的 20 位数字的低十位取出加上 0xDC00 得到低代理
取字符 举例,它的代码点为 U+10437
1.0x10437 - 0x10000 = 0x0437 = 0000 0000 0100 0011 0111
2.高代理:0x0001 + 0xD800 = 0xD801
3.低代理:0x0037 + 0xDC00 = 0xDC37
UTF-32
UTF-32 将每一个 Unicode 代码点表示为相同值的 32 位整数。很明显,它是内部处理最方便的表达方式,但是,如果作为一般字符串表达方式,则要消耗更多的内存。
最后在 Java 中,使用基本类型 int 在底层 API 中表示代码点;
所有形式的 char 序列均解释为 UTF-16 序列。
因此在最初的代码中,符号 的 UTF-16 编码为 D801 DC37 ,这时的代码点数量为 1 ,而代码单元长度为 2 ,因此最后输出的就是 2 了。
public class Demo{
public static void main(String... args) {
char[] chs = Character.toChars(0x10437);
System.out.printf("U+10437 高代理字符: %04x%n", (int)chs[0]);
System.out.printf("U+10437 低代理字符: %04x%n", (int)chs[1]);
String str = new String(chs);
System.out.println("代码单元长度: " + str.length());
System.out.println("代码点数量: " + str.codePointCount(0, str.length()));
}
}
输出为:
U+10437 高代理字符: d801
U+10437 低代理字符: dc37
代码单元长度: 2
代码点数量: 1
几个实用 API
- char charAt( int index ):返回给定位置的代码单元。
- int codePointAt( int index ):返回从给定位置开始的码点。
- int offsetByCodePoints( int startIndex, int cpCount):返回从 startIndex 代码点开始,位移 cpCount 后的码点索引。
- IntStream codePoints( ):将这个字符串的码点作为一个流返回。调用 toArray 将它们放在一个数组中。
最后:并不推荐在自己的代码中使用 char 型,除非对底层非常了解,不然会出现一些奇怪的事情。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现