我的原则:先会用再说,内部慢慢来。
学以致用,根据场景学源码
一、概念
7 -7
原码: 0000 0111 1000 0111
反码: 0000 0111 1111 1000
补码: 0000 0111 1111 1001
- 正数原码、反码、补码都是一样的。
- 原码的第一位是符号位, 0表示非负数+(包括正数 + 0),1表示负数-
- (针对负数)反码就是对原码逐位取反(0变成1,1变成0),但是第一位符号位除外
- (针对负数)补码就是反码+1
- 补码 = 原码取反 + 1
二、为什么会有反码?
切记:计算机只会加法
-
假如只有原码,那么下面运算是错误的。
1 - 1 = 1 + (-1) = [00000001]原 + [10000001]原 = [10000010]原 = -2 -
此时有了反码,用反码进行运算
1 - 1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原= [0000 0001]反 + [1111 1110]反 = [1111 1111]反 = [1000 0000]原 = -0
三、为什么会有补码?
- 用反码计算减法, 结果的真值部分是正确的。
- 唯一的问题其实就出现在"0"这个特殊的数值上。+0和-0是一样的, 但是0带符号是没有任何意义的。而且会有[0000 0000]原和[1000 0000]原两个编码表示0。
- 于是补码出现了
3.1 用补码解决 0 的符号以及两个编码的问题
1-1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原 = [0000 0001]补 + [1111 1111]补 = [0000 0000]补=[0000 0000]原
3.2 用补码相比原码可以多保存一个数字(+0 与 -0 在补码中占1个坑)
- 8位二进制, 使用原码或反码表示的范围为[-127, +127], 而使用补码表示的范围为[-128, 127].
- 32位int类型, 可以表示范围是: [-2 ^ 31, 2 ^ 31-1]
切记:计算机都是用补码进行运算
四、二进制运算
4.1 位运算
位运算符包括: 与(&)、非(~)、或(|)、异或(^)
与(&):当两边操作数的位同时为1时,结果为1,否则为0。如1100&1010=1000
或(|) :当两边操作数的位有一边为1时,结果为1,否则为0。如1100|1010=1110
非(~):0 变 1,1 变 0
异或(^):两边的位不同时,结果为1,否则为0。 如1100^1010=0110
4.2 移位运算
<<(左移)、>>(带符号右移)和>>>(无符号右移)。
- 如果移动的位数超过了该类型的最大位数,那么编译器会对移动的位数取模。如对int型移动33位,实际上只移动了33%32=1位。
- 右移一位相当于除2,右移n位相当于除以2的n次方。 左移反之
- 正数的左移与右移,负数的无符号右移,就是相应的补码移位所得,在高位补0即可。
- 负数的右移,就是补码高位补1,然后按位取反加1即可。
<<(左移): 左移的规则只记住一点:丢弃最高位,0补最低位
>>(带符号右移)和>>>(无符号右移)。
举例1:
-100带符号右移4位。
-100原码: 10000000 00000000 00000000 01100100
-100补码: 保证符号位不变,其余位置取反加1
11111111 11111111 11111111 10011100
右移4位 : 在高位补1
11111111 11111111 11111111 11111001
补码形式的移位完成后,结果不是移位后的结果,要根据补码写出原码才是我们所求的结果。其方法如下:
保留符号位,然后按位取反
10000000 00000000 00000000 00000110
然后加1,即为所求数的原码:
10000000 00000000 00000000 00000111
所有结果为:-7
举例2:
-100无符号右移4位。
-100原码: 10000000 00000000 00000000 01100100
-100补码: 保证符号位不变,其余位置取反加1
11111111 11111111 11111111 10011100
无符号右移4位 : 在高位补0
00001111 11111111 11111111 11111001
即为所求:268435449
4.3 计算题:short a = 128; byte b = (byte) a ; 经过强制类型转换以后,b的值为( )
- 答案: b = -128
八位 0000 0000:能存储2^8 - 1 = 255,
对于有符号的类型:例如byte型有正负(-128 - 127),那就第一位符号位。(以下两行为补码)
0000 0000 - 0111 1111 即 0 -> 127
1000 0000 - 1111 1111 即 -128 -> -1
(注:无符号位的8位 1000 0000 为128)
补码:1000 0000 ---> 原值: -128 (8位的有符号位,对于128来说,反码和原码没法显示)
补码:1111 1111 ---> 原码:1000 0001 ---> 原值: -1
上述是8位的情况,同理32位的情况是 :
补码:11111111 11111111 11111111 11111111 ---> 原码:10000000 00000000 00000000 00000001 ---> 原值: -1 ,不理解就看下面的图。
4.4 用与&符号进行二进制取余
c = a % b
b 必须是2的N次方,
c = a & ( b - 1)
17 % 16 = 1
-->
17 & ( 16 -1 ) = 17 & 15
0001 0001
& 0000 1111
0000 0001
余数为 1.
&符号实现取余数原理是什么?
- 如何对8(111)取余?
除法,除与2的冥可以用无符号右移来表示,比如说除与8,就是右移3位。
9 (1001) 除与 8,右移3位结果为 0001,商为1。(移走了001,这个就是余数,1)
8 (1000) 除与 8,右移3位结果为 0001,商为1。(移走了000,这个就是余数,0)
7 (0111) 除与 8,右移3位结果为 0000,商为0。(移走了1111,这个就是余数,7)
- 那么求余数也就是要看我们移走了什么,那么& 111…111 即可实现这个目的。而 111…111 就是2的N次方 - 1.
五、位运算在JAVA中的实际应用
HashMap.tableSizeFor 方法
位于构造方法中:
public HashMap(int initialCapacity, float loadFactor) {
- 目的是为了初始化threshold,求出你的传入容量大小的最小的2的幂,确保能够装下。
- 也就是不管你的值给多少,求出一个比你给出的数值大的最小二进制数字。
5.1 jdk1.7实现
先 * 2,再处理
private void inflateTable(int toSize) {
//根据toSize计算容量,即大于toSize的最小的2的n次方
int capacity = roundUpToPowerOf2(toSize);
………
}
private static int roundUpToPowerOf2(int number) {
// assert number >= 0 : "number must be non-negative";
return number >= MAXIMUM_CAPACITY
? MAXIMUM_CAPACITY
: (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
}
public static int highestOneBit(int i) {
// HD, Figure 3-1
i |= (i >> 1);
i |= (i >> 2);
i |= (i >> 4);
i |= (i >> 8);
i |= (i >> 16);
return i - (i >>> 1);
}
- 重点看 highestOneBit 方法
- 假如传入的 cap = 17 (0001 0001) ,那么会得到 32 (0010 0000)
- Integer.highestOneBit((number - 1) << 1)
//tmp = number - 1 = 0001 0000 ,i = tmp << 1 = 0010 0000
i |= (i >> 1); // tmp = i >> 1 = 0001 0000 , i | tmp = 0011 0000
i |= (i >> 2); // tmp = i >> 2 = 0000 1100 , i | tmp = 0011 1100
i |= (i >> 4); // tmp = i >> 4 = 0000 0001 , i | tmp = 0011 1111
i |= (i >> 8); // tmp = i >> 8 = 0000 0000 , i | tmp = 0011 1111
i |= (i >> 16); // tmp = i >> 16 = 0000 0000 , i | tmp = 0011 1111
return i - (i >>> 1);// tmp = i >>> 1 = 0001 1111 ,i - tmp = 0010 0000
- 假如传入的 cap = 01000000 00000000 00000000 00000001 ,那么会得到 10000000 00000000 00000000 00000000
//tmp = number - 1 = 01000000 00000000 00000000 00000000 ,i = tmp << 1 = 10000000 00000000 00000000 00000000
i |= (i >> 1); // tmp = i >> 1 = 01000000 00000000 00000000 00000000 , i | tmp = 11000000 00000000 00000000 00000000
i |= (i >> 2); // tmp = i >> 2 = 00110000 00000000 00000000 00000000 , i | tmp = 11110000 00000000 00000000 00000000
i |= (i >> 4); // tmp = i >> 4 = 00001111 00000000 00000000 00000000 , i | tmp = 11111111 00000000 00000000 00000000
i |= (i >> 8); // tmp = i >> 8 = 00000000 11111111 00000000 00000000 , i | tmp = 11111111 11111111 00000000 00000000
i |= (i >> 16); // tmp = i >> 16 = 00000000 00000000 11111111 11111111 , i | tmp = 11111111 11111111 11111111 11111111
return i - (i >>> 1);// tmp = i >>> 1 = 01111111 11111111 11111111 11111111 ,i - tmp = 10000000 00000000 00000000 00000000
5.2 jdk1.8 实现
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
- 上面的思路就是把全部位数都占满成1,变成11111…1111, 然后最后再 + 1。
- 假如传入的 cap = 17 (0001 0001),那么会得到 32 (0010 0000)
int n = cap - 1; // n = 0001 0000
n |= n >>> 1; // tmp = n >>> 1 = 0000 1000 ,n |= tmp = 0001 1000 (补1,原先n的1都在)
n |= n >>> 2; // tmp = n >>> 2 = 0000 0110 ,n |= tmp = 0001 1110 (补1,原先n的1都在)
n |= n >>> 4; // tmp = n >>> 4 = 0000 0001 ,n |= tmp = 0001 1111 (补1,原先n的1都在)
n |= n >>> 8; // tmp = n >>> 8 = 0000 0000 ,n |= tmp = 0001 1111 (补1,原先n的1都在)
n |= n >>> 16;// tmp = n >>> 16 = 0000 0000 ,n |= tmp = 0001 1111 (补1,原先n的1都在)
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; // return 0010 0000 = 32
- 假如传入的 cap = 01000000 00000000 00000000 00000001 ,那么会得到 10000000 00000000 00000000 00000000
int n = cap - 1; // n = 01000000 00000000 00000000 00000000
n |= n >>> 1; // tmp = n >>> 1 = 00100000 00000000 00000000 00000000 ,n |= tmp = 01100000 00000000 00000000 00000000 (1个1复制成2个1)
n |= n >>> 2; // tmp = n >>> 2 = 00011000 00000000 00000000 00000000 ,n |= tmp = 01111000 00000000 00000000 00000000 (2个1复制成4个1)
n |= n >>> 4; // tmp = n >>> 4 = 00000111 10000000 00000000 00000000 ,n |= tmp = 01111111 10000000 00000000 00000000 (4个1复制成8个1)
n |= n >>> 8; // tmp = n >>> 8 = 00000000 01111111 10000000 00000000 ,n |= tmp = 01111111 11111111 10000000 00000000 (8个1复制成16个1)
n |= n >>> 16;// tmp = n >>> 16 = 00000000 00000000 01111111 11111111 ,n |= tmp = 01111111 11111111 11111111 11111111 (16个1复制满32位)
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; // return 10000000 00000000 00000000 00000000 = 2 ^ 31
- 用二进制看
initialCapacity | 计算出来的 threshold |
---|---|
0000 0000 | 0000 0001 |
0000 0001 | 0000 0010 |
0000 0011 | 0000 0100 |
… | … |
0001 0000 | 0010 0000 |
… | … |
- 用十进制看
initialCapacity | 计算出来的 threshold |
---|---|
0 | 1 |
1 | 2 |
3 | 4 |
… | … |
17 | 32 |
… | … |
5.3 jdk11 实现
原值: -1 —> 补码:11111111 11111111 11111111 11111111
static final int tableSizeFor(int cap) {
int n = -1 >>> Integer.numberOfLeadingZeros(cap - 1);
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
@HotSpotIntrinsicCandidate
public static int numberOfLeadingZeros(int i) {
// HD, Count leading 0's
if (i <= 0)
return i == 0 ? 32 : 0;
int n = 31;
if (i >= 1 << 16) { n -= 16; i >>>= 16; }
if (i >= 1 << 8) { n -= 8; i >>>= 8; }
if (i >= 1 << 4) { n -= 4; i >>>= 4; }
if (i >= 1 << 2) { n -= 2; i >>>= 2; }
return n - (i >>> 1);
}
- 假如传入的 cap = 17 (0001 0001),那么会得到 32 (0010 0000)
public static int numberOfLeadingZeros(int i) {
// i = cap - 1 = 0001 0000(二进制) = 16(十进制)
if (i <= 0)
return i == 0 ? 32 : 0;
int n = 31; // n = 31 = 0001 1111
if (i >= 1 << 16) { n -= 16; i >>>= 16; } // 每一步都需要判断
if (i >= 1 << 8) { n -= 8; i >>>= 8; }
if (i >= 1 << 4) { n -= 4; i >>>= 4; } //命中这个, 1 << 4 = 0001 0000, n = n - 4 = 0001 1111 - 0000 0100 = 0001 1011 , i = i >>> 4 = 0000 0001
if (i >= 1 << 2) { n -= 2; i >>>= 2; }
return n - (i >>> 1); // tmp = i >>> 1 = 0000 0000,n - tmp = 0001 1011 = 27
}
static final int tableSizeFor(int cap) {
// -1 补码:11111111 11111111 11111111 11111111
int n = -1 >>> Integer.numberOfLeadingZeros(cap - 1); // -1 >>> 27 ,右移动高位补0 , 00000000 00000000 00000000 00011111
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; // 00000000 00000000 00000000 00100000 = 32
}
- 假如传入的 cap = 01000000 00000000 00000000 00000001 ,那么会得到 10000000 00000000 00000000 00000000
public static int numberOfLeadingZeros(int i) {
// i = cap - 1 = 01000000 00000000 00000000 00000000
if (i <= 0)
return i == 0 ? 32 : 0;
int n = 31; // n = 31 = 0001 1111
if (i >= 1 << 16) { n -= 16; i >>>= 16; } // 命中这个, n = n - 16 = 0001 1111 - 0001 0000 = 0000 1111, i = i >>> 16 = 00000000 00000000 01000000 00000000,
if (i >= 1 << 8) { n -= 8; i >>>= 8; } // 继续命中这个, n = n - 8 = 0000 1111 - 0000 1000 = 0000 0111, i = i >>> 8 = 00000000 00000000 00000000 01000000
if (i >= 1 << 4) { n -= 4; i >>>= 4; } // 继续命中这个, n = n - 4 = 0000 0111 - 0000 0100 = 0000 0011 , i = i >>> 4 = 00000000 00000000 00000000 00000100
if (i >= 1 << 2) { n -= 2; i >>>= 2; } // 继续命中这个,n = n - 2 = 0000 0011 - 0000 0010 = 0000 0001 , i = i >>> 2 = 00000000 00000000 00000000 00000001
return n - (i >>> 1); // tmp = i >>> 1 = 00000000 00000000 00000000 00000000 ,n - tmp = 0000 0001 = 1
}
static final int tableSizeFor(int cap) {
// -1 补码:11111111 11111111 11111111 11111111
int n = -1 >>> Integer.numberOfLeadingZeros(cap - 1); // -1 >>> 1 ,右移动高位补0 , 01111111 11111111 11111111 11111111
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; // 10000000 00000000 00000000 00000000 = 2 ^ 31
}