目录
java中的二进制操作很多地方都有很好的使用,比如HashMap源码中就利用了当n等于2的次幂时,"hash%n"和"hash&(n-1)"
的特性,下面再列举几个巧妙使用位运算的例子。
HashMap
hashMap中再计算key存储在tab数组的什么位置时,计算方式是int index = (n - 1) & hash
。
这主要有两个好处
- tab数组长度的一般是2的n次方,这时该操作等价于
hash%n
,但执行速度更快 - hash可能是负数,
%
操作的结果可能仍然是负数,但是&操作结果肯定为整数。
SpacePadder填充固定位
假设又一个StringBuffer
要求要有n(20或者更多)
个字符,如果不足,向右填充空格
直到满足为止,一般最简单的方式就是for循环,一个个append
直到满足n个字符,但是效率就不高了,牛逼的做法如下
public class SpacePadder {
final static String[] SPACES = { " ", " ", " ", " ", // 1,2,4,8
// spaces
" ", // 16 spaces
" " }; // 32 spaces
/**
* Fast space padding method.
*/
final static public void spacePad(StringBuilder sbuf, int length) {
//当length>=32时,一次性追加32个空格
while (length >= 32) {
sbuf.append(SPACES[5]);
length -= 32;
}
//当length <32时,根据具体值,一次次追加 16, 8, 4, 2, 1个字符,则32以内的
//任意个空格都可以在5次操作下完成。
for (int i = 4; i >= 0; i--) {
if ((length & (1 << i)) != 0) {
sbuf.append(SPACES[i]);
}
}
}
}
巧妙的利用了32
与位操作,最好地解决了追加任意个字符的问题。
Integer的numberOfLeadingZeros
Integer的前导0的个数,返回Integer最高非0位的前面0的个数。
比如 00001111 00000000 00000000 00000000
的前导0个数为4。
jdk8源码
public static int numberOfLeadingZeros(int i) {
// HD, Figure 5-6
if (i == 0)
return 32;
int n = 1;
if (i >>> 16 == 0) { n += 16; i <<= 16; } 一
if (i >>> 24 == 0) { n += 8; i <<= 8; } 二
if (i >>> 28 == 0) { n += 4; i <<= 4; } 三
if (i >>> 30 == 0) { n += 2; i <<= 2; } 四
n -= i >>> 31;
return n;
}
前导0的个数可能从0到32,如果结果为3,则反推i可能为
i = 0b 00010000_00000000_00000000_00000000
走到图中第4步, 表示i的高两位肯定为0
n = 1+2=3,
i = 0b01000000_00000000_00000000_00000000
由于前两位只可能为01
,10
,11
,右移31的结果只能为0或者1,当原始i的高第三位为0时,i>>>310,n=3,没问题; 当原始i的高第三位为1时,i>>>311,n=3-1=2,也没问题。
参考 https://www.jianshu.com/p/2c1be41f6e59
Integer的numberOfTrailingZeros
int的后导0的个数,也是以2为底的对数
public static int numberOfTrailingZeros(int i) {
// HD, Figure 5-14
int y;
if (i == 0) return 32;
int n = 31;
y = i <<16; if (y != 0) { n = n -16; i = y; }
y = i << 8; if (y != 0) { n = n - 8; i = y; }
y = i << 4; if (y != 0) { n = n - 4; i = y; }
y = i << 2; if (y != 0) { n = n - 2; i = y; }
return n - ((i << 1) >>> 31);
}
异或运算的特性
异或运算满足 交换律
,结合律
,
0^x=x;
x^x=0;
一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
Integer.signum
计算一个int的符号,正数返回1,负数返回-1,0返回0。
public static int signum(int i) {
// HD, Section 2-7
return (i >> 31) | (-i >>> 31);
}
对于正数, i>>31
带符号右移31位,结果为0
;
对于负数,i>>31
带符号右移31位, 结果为11111111_11111111_11111111_11111111
;
对于正数,-i>>>31
无符号右移31位,-i
为负数,最高位为1,无符号右移31位,则结果为1;
对于负数,-i>>>31
无符号右移31位, -i
为正数, 最高位为0, 无符号右移31位,则结果为0;
综上,两部分再|
,则
对于正数,最终结果为1
;
对于负数,最终结果为11111111_11111111_11111111_11111111
, 由于计算机中存储的是补码
,所以真实值为-1
;
n个bit所能表示的最大正数
n<=64;
1个bit能表示的最大正数为1;
2个bit能表示的最大正数为3;
3个bit能表示的最大正数为7;
…
64个bit能表示的最大正数为64;
如何函数表示呢?lucene
中PackedInts
类中有
public static long maxValue(int bitsPerValue) {
return bitsPerValue == 64 ?
// 0取反则是连续64个1
// 左移n位,则低n位都是0,初次之外其他位都是1
// 再取反,则低n位都是1,其他位都是0
// 比如n=2,结果为 xxxx11 = 3
// n=3,结果为xxx111=7
// 构思巧妙
Long.MAX_VALUE : ~(~0L << bitsPerValue);
}
计算一个long值需要多少个bit保存
参考lucene
中PackedInts
类
public static int unsignedBitsRequired(long bits) {
return Math.max(1, 64 - Long.numberOfLeadingZeros(bits));
}
public static int numberOfLeadingZeros(long i) {
// HD, Figure 5-6
if (i == 0)
return 64;
int n = 1;
int x = (int)(i >>> 32);
if (x == 0) { n += 32; x = (int)i; }
if (x >>> 16 == 0) { n += 16; x <<= 16; }
if (x >>> 24 == 0) { n += 8; x <<= 8; }
if (x >>> 28 == 0) { n += 4; x <<= 4; }
if (x >>> 30 == 0) { n += 2; x <<= 2; }
n -= x >>> 31;
return n;
}
获取>=m的且是n的倍数的最小数
节选自lucene
/**
* Aligns an object size to be the next multiple of {@link #NUM_BYTES_OBJECT_ALIGNMENT}.
* >=size且是8的倍数的最小值
*/
public static long alignObjectSize(long size) {
size += (long) NUM_BYTES_OBJECT_ALIGNMENT - 1L;
return size - (size % NUM_BYTES_OBJECT_ALIGNMENT);
}
一般我们这样设计
if(size % NUM_BYTES_OBJECT_ALIGNMENT) == 0{
return size;
}
return size - (size % NUM_BYTES_OBJECT_ALIGNMENT)
+ NUM_BYTES_OBJECT_ALIGNMENT;