char 在 Java 中是个很底层的东西了,比如 String 、StringBuilder 的底层就是它,但是在我们平时的工作中呢,很少使用他,不过既然是底层,今天我们大家就来一起研究下这 “哥们”。
char 用来表示 ==1 个字符== 。也就是说,如果你写 2个字符,那么这里是会报错的。如下:
// 这种编译报错 Too many characters in character literal
char c = '中国';
当然 char 也可以用来表示中文字符。在赋值时,我们经常这样表示:
char c = '中';
char c1 = 'H';
但是这里就会遇到一些面试上的坑了,比如使用字符类型进行算术运算和比较运算? 这是个什么鬼 A + B == ???
其实在 Java 内部进行字符处理时,采用的是 Unicode,(这里插一句 对 Unicode 和 UTF-X 的理解,Unicode 是一个包含世界各国字母的字符列表的编码。 Universal Multiple-Octet Coded Character Set”,简称 UCS, 俗称 “unicode “,就是每一个字母对应一个编码 ID,是一种映射关系,我们可以理解为 Unicode 是 一个标准,一个规则,而 UTF-X 则是一种具体的对 Unicode 的实现,UTF-X 是一种 针对 Unicode 的可变长度字符编码,也是一种前缀码,是一种编码格式。它可以用来表 示Unicode标准中的任何字符,而且 UTF-8 是兼容 ASCII 的。UTF-8 是 Unicode 的 实现方式之一。)
char 本质上是一个固定占用 2 个字节的无符号正整数,对应 Unicode, 也就是说 上面 的 李, H 都分别对应一个 正整数,char 只能表示 Unicode 编号在 65 536 以内的字符。因为一个字节 只能表示 256 个符号,2 个字节就是 256 x 256 = 65536 个符号。那么如果超出范围该咋表示呢,用 2 个char!!!
既然了解完了 Unicode,那么每个字符都可以用一个 对应的 编码ID 表示,也就是 一个正整数。既然是数字了,自然可以进行算术运算和比较运算。
char 的 二进制转换
下面我们就以 Integer 的转换二进制函数 toBinaryString 为例,说明一下 char 字节在 Java 中如何转换二进制的。
public static void main(String[] args) {
char c = '中';
System.out.println(Integer.toBinaryString(c));
// 二进制为: 100111000101101
}
我们给 char 赋值一个 中文 字节 中,然后求出它的 二进制。
Integer 的 toBinaryString 方法:
/**
* 返回 输入参数 i 的 二进制字符串
*/
public static String toBinaryString(int i) {
return toUnsignedString0(i, 1);
}
这里调用了 toUnsignedString0, 如下
/**
* Convert the integer to an unsigned number.
* 转换一个 整型到一个无符号二进制数字
*/
private static String toUnsignedString0(int val, int shift) {
// 这里断言忽略
// assert shift > 0 && shift <=5 : "Illegal shift value";
// Integer.numberOfLeadingZeros 返回无符号整型的最高非零位前面的0的个数,包括符号位在内
// 比如 Integer.numberOfLeadingZeros(10) 结果是 28
// Integer.SIZE 为 32
int mag = Integer.SIZE - Integer.numberOfLeadingZeros(val);
int chars = Math.max(((mag + (shift - 1)) / shift), 1);
char[] buf = new char[chars];
// 这里是整个方法的核心,整数转换二进制,然后更新 char 的缓冲
formatUnsignedInt(val, shift, buf, 0, chars);
// Use special constructor which takes over "buf".
return new String(buf, true);
}
核心转换函数 formatUnsignedInt 如下:
/**
* 格式化到字符缓冲区
* @param val 被格式化的数
* @param shift 格式化的类型 (4 代表16进制, 3 代表8进制, 1 代表二进制)
* @param buf 待写入的字符缓冲区
* @param offset 字符开始的位置
* @param len 要写的字符数
* @return the lowest character location used
*/
static int formatUnsignedInt(int val, int shift, char[] buf, int offset, int len) {
int charPos = len;
int radix = 1 << shift;
int mask = radix - 1;
do {
// 二进制的 & 运算,求出下标,然后获取 digits 中对应的值,写入 buf 缓冲区
// final static char[] digits = {
// '0' , '1' , '2' , '3' , '4' , '5' ,
// '6' , '7' , '8' , '9' , 'a' , 'b' ,
// 'c' , 'd' , 'e' , 'f' , 'g' , 'h' ,
// 'i' , 'j' , 'k' , 'l' , 'm' , 'n' ,
// 'o' , 'p' , 'q' , 'r' , 's' , 't' ,
// 'u' , 'v' , 'w' , 'x' , 'y' , 'z'
}
buf[offset + --charPos] = Integer.digits[val & mask];
// 无符号右移
val >>>= shift;
} while (val != 0 && charPos > 0);
return charPos;
}
通过上面的代码可以看出,Integer 内部维护了一个 char 数组,我们的普通字符转换 二进制的时候,都是求出 digits 某个下标的值,然后写到缓冲区,以 String 的形式返回给用户的。