Java 包装类的二进制操作
创始人
2024-05-28 20:50:21

Integer

位翻转

位翻转就是将二进制左边的位与右边的位进行互换,reverse 是按位进行互换, reverseBytes 是按 byte 进行互换。

public static int reverse(int i)public static int reverseBytes(int i)

来看个例子:

int a = 0x12345678;
System.out.println(Integer.toBinaryString(a));
// 0001 0010 0011 0100 0101 0110 0111 1000int r = Integer.reverse(a);
System.out.println(Integer.toBinaryString(r));
// 0001 1110 0110 1010 0010 1100 0100 1000int rb = Integer.reverseBytes(a);
System.out.println(Integer.toHexString(rb));
// 0111 1000 0101 0110 0011 0100 0001 0010

reverse 的实现:

public static int reverse(int i) {//HD, Figure 7-1i = (i & 0x55555555) << 1 | (i >>> 1) & 0x55555555;i = (i & 0x33333333) << 2 | (i >>> 2) & 0x33333333;i = (i & 0x0f0f0f0f) << 4 | (i >>> 4) & 0x0f0f0f0f;i = (i << 24) | ((i & 0xff00) << 8) |((i >>> 8) & 0xff00) | (i >>> 24);return i;
}

高效实现位翻转的基本思路是:首先交换相邻的单一位,然后以2位为一组,再交换相邻的位,接着是4位一组交换、然后是8位、16位,16位之后就完成了。

i = (i & 0x55555555) << 1 | (i >>> 1) & 0x55555555; 是对单一位进行翻转。

x & 0x55555555 是取 x 的奇数位。
& 运算符的优先级比 | 运算符高。

i = (i & 0x33333333) << 2 | (i >>> 2) & 0x33333333; 是以两位为一组,对相邻位进行互换。

x & 0x33333333 是取 x 以两位为一组的奇数位。

reverse 代码为什么不能用更容易理解的方式写吗?比如,实现翻转,一种常见的思路是:第一个和最后一个交换,第二个和倒数第二个交换,直到中间两个交换完成。如果数据不是二进制位,这个思路是好的,但对于二进制位,这个思路的效率比较低。

CPU指令并不能高效地操作单个位,它操作的最小数据单位一般是32位(32位机器),另外,CPU可以高效地实现移位和逻辑运算,但实现加、减、乘、除运算则比较慢。reverse是在充分利用CPU的这些特性,并行高效地进行相邻位的交换,可以通过其他更容易理解的方式实现相同功能,但很难比这个代码更高效。

reverseBytes 的实现:

public static int reverseBytes(int i) {return ((i >>> 24))| ((i >>    8) &    0xFF00)| ((i <<    8) & 0xFF0000)| ((i << 24));
}

循环移位

Integer 有两个静态方法可以进行循环移位。

public static int rotateLeft(int i, int distance)
public static int rotateRight(int i, int distance)

所谓循环移位,是相对于普通的移位而言的,普通移位,比如左移2位,原来的最高两位就没有了,右边会补0,而如果是循环左移两位,则原来的最高两位会移到最右边。

源码实现:

public static int rotateLeft(int i, int distance) {return (i << distance) | (i >>> -distance);
}public static int rotateRight(int i, int distance) {return (i >>> distance) | (i << -distance);
}

令人费解的是负数,如果 distance 是 8,那 i>>>-8 是什么意思呢?

实际的移位个数不是后面的直接数字,而是直接数字的最低 5 位的值,之所以这样,是因为 5 位最大表示 31,移位超过 31 位对 int 整数是无效的。

valueOf

public static Integer valueOf(int i) {if (i >= IntegerCache.low && i <= IntegerCache.high)return IntegerCache.cache[i + (-IntegerCache.low)];return new Integer(i);
}

IntegerCache表示Integer缓存,其中的cache变量是一个静态Integer数组,在静态初始化代码块中被初始化,默认情况下,保存了-128~127共256个整数对应的Integer对象。

在valueOf代码中,如果数值位于被缓存的范围,即默认-128~127,则直接从Integer-Cache中获取已预先创建的Integer对象,只有不在缓存范围时,才通过new创建对象。

通过共享常用对象,可以节省内存空间,由于Integer是不可变的,所以缓存的对象可以安全地被共享。

Character

Unicode

Unicode给世界上每个字符分配了一个编号,编号范围为0x000000~0x10FFFF。编号范围在0x0000~0xFFFF的字符为常用字符集,称BMP(Basic Multilingual Plane)字符。编号范围在0x10000~0x10FFFF的字符叫做增补字符(supplementary character)。

Unicode主要规定了编号,但没有规定如何把编号映射为二进制。UTF-16是一种编码方式,或者叫映射方式,它将编号映射为2个或4个字节,对BMP字符,它直接用两个字节表示;对于增补字符,使用4个字节表示,前两个字节叫高代理项(high surrogate),范围为0xD800~0xDBFF,后两个字节叫低代理项(low surrogate),范围为0xDC00~0xDFFF。UTF-16定义了一个公式,可以将编号与4字节表示进行相互转换。

Java内部采用UTF-16编码,char表示一个字符,但只能表示BMP中的字符,对于增补字符,需要使用两个char表示,一个表示高代理项,一个表示低代理项。

使用int可以表示任意一个Unicode字符,低21位表示Unicode编号,高11位设为0。整数编号在Unicode中一般称为代码点(code point),表示一个Unicode字符,与之相对,还有一个词代码单元(code unit)表示一个char。

参考:《Java 编程的逻辑》马俊昌

相关内容

热门资讯

苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
阿西吧是什么意思 阿西吧相当于... 即使你没有受到过任何外语培训,你也懂四国语言。汉语:你好英语:Shit韩语:阿西吧(아,씨발! )日...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...