1:推算byte,int,long等类型的范围
公式:-2的n-1次方 ~ 2的n-1次方-1,n代表类型的位数
byte类型推算过程
1:byte为1字节,对应8位二进制数,最大值为:01111111
2:将二进制01111111转为10进制,计算公式:1*2^6 + 1*2^5 + 1*2^4 + 1*2^3 + 1*2^2 + 1*2^1 + 1*2^0
3:去掉乘1简化公式:2^6 + 2^5 + 2^4 + 2^3 + 2^2 + 2^1 + 2^0
4:将2^0替换成2^1 - 2^0:2^6 + 2^5 + 2^4 + 2^3 + 2^2 + 2^1 + 2^1 - 2^0
5:推导过程
5.1:合并两个2^1:2^6 + 2^5 + 2^4 + 2^3 + 2^2 + 2^2 - 2^0
5.2:合并两个2^2:2^6 + 2^5 + 2^4 + 2^3 + 2^3 - 2^0
5.3:合并两个2^3:2^6 + 2^5 + 2^4 + 2^4 - 2^0
5.4:合并两个2^4:2^6 + 2^5 + 2^5 - 2^0
5.5:合并两个2^5:2^6 + 2^6 - 2^0
5.6:合并两个2^6:2^7 - 2^0 = 2^7 - 1
6:推导结果:2^7 - 1,符合公式:2的n-1次方-1
7:负数推导类似,除了特殊值-128 = -2^7,对应的补码:10000000,减1计算反码,可以发现会算不下去
8:综上而论byte范围:-2^7 ~ 2^7 - 1
2:左移的效果(如何左移,可以查看这篇文章)
公式:m * 2的n次方,m为原值,n为移动位数(前提是有效位(原码中的1)不能移动超过或等于符号位的位置,否则就会失效)
使用5进行推算
1:int为4字节,对应32位二进制数,5转化二进制:00000000 00000000 00000000 00000101
2:将 00000000 00000000 00000000 00000101 转为10进制,计算公式:2^2 + 2^0
3:左移1位:00000000 00000000 00000000 00001010 转为10进制,计算公式:2^3 + 2^1 = 2^1 *(2^2 + 2^0)
4:左移2位:00000000 00000000 00000000 00010100 转为10进制,计算公式:2^4 + 2^2 = 2^2 *(2^2 + 2^0)
5:左移29位:10100000 00000000 00000000 00000000 符号位变成1,已经变成负数
6:通过观察可以发现移动n位就相当于乘以2的n次方,但是前提是有效位不能移动超过或等于符号位的位置,否则就会失效
在HashMap中,数组初始长度和最大长度(移动31位,再移动就会变成负数)
3:右移的效果(如何右移,可以查看这篇文章)
公式:m / 2的n次方,m为原值,n为移动位数
使用6进行推算
1:int为4字节,对应32位二进制数,6转化二进制:00000000 00000000 00000000 00000110
2:将 00000000 00000000 00000000 00000110 转为10进制,计算公式:2^2 + 2^1
3:右移1位:00000000 00000000 00000000 00000011 转为10进制,计算公式:2^1 + 2^0 = (2^2 + 2^1)/ 2^1
4:右移2位:00000000 00000000 00000000 00000001 转为10进制,计算公式:2^0 = (2^2 + 2^1)/ 2^2
5:右移3位:00000000 00000000 00000000 00000000 转为10进制,计算公式:0 = (2^2 + 2^1)/ 2^3
6:通过观察可以发现移动n位就相当于除以2的n次方,上面右移2位以上虽然有效位已经舍弃,但是整数除法结果不考虑浮点数,得到的结果是没有问题的
4:与运算的应用(如何与运算,可以查看这篇文章)
快速取模:m % n = m & (n - 1),前提条件n等于2的某次方,且n必须为正数
使用10&3进行推算
1:10转化二进制:00000000 00000000 00000000 00001010,3转化二进制:00000000 00000000 00000000 00000011
2:计算过程:
00000000 00000000 00000000 00001010
& 00000000 00000000 00000000 00000011
-----------------------------------------
00000000 00000000 00000000 00000010
3:00000000 00000000 00000000 00000010 转为10进制,结果为2 = 10 % 4,符合公式
4:为什么说除数必须为2的某次方,因为对应的2进制,除了符号位,必定有且只有一位的值1,减1之后相当于把1后移1位,并且把1后面的位进行取反
例如:4 - 1 -> 00000000 00000000 00000000 00000100 - 1 = 00000000 00000000 00000000 00000011
& 01111111 11111111 11111111 11111111
-----------------------------------------
00000000 00000000 00000000 00000011
例如:8 - 1 -> 00000000 00000000 00000000 00001000 - 1 = 00000000 00000000 00000000 00000111
& 01111111 11111111 11111111 11111111
-----------------------------------------
00000000 00000000 00000000 00000111
例如:16 - 1 -> 00000000 00000000 00000000 00010000 - 1 = 00000000 00000000 00000000 00001111
& 01111111 11111111 11111111 11111111
-----------------------------------------
00000000 00000000 00000000 00001111
这个时候可以看到只有低位连续为1,与运算之后,高位都是0,只有低位可能为1,那么结果一定是小于等于除数-1的值,刚好符合取模结果的特征
在HashMap中,因为数组规定长度必须为2的n次方,所以使用取模计算数组下标的时候刚好可以使用与运算
5:异或运算的应用(如何异或运算,可以查看这篇文章)
散列算法:HashMap中计算hash:(h = key.hashCode()) ^ (h >>> 16),得到的hash值就是为了上面后续与运算取模准备的,
1.先看看不对hashCode进行操作,后续的与运算会出现什么情况,因为HashMap默认数组长度为16,下面举几个例子进行:hashCode & (16 -1),看看结果如何
例:hashCode = 131072 -> 00000000 00000010 00000000 00000000
& 00000000 00000000 00000000 00001111
-----------------------------------------
00000000 00000000 00000000 00000000
例:hashCode = 65536 -> 00000000 00000001 00000000 00000000
& 00000000 00000000 00000000 00001111
-----------------------------------------
00000000 00000000 00000000 00000000
例:hashCode = 32768 -> 00000000 00000000 10000000 00000000
& 00000000 00000000 00000000 00001111
-----------------------------------------
00000000 00000000 00000000 00000000
例:hashCode = 16384 -> 00000000 00000000 01000000 00000000
& 00000000 00000000 00000000 00001111
-----------------------------------------
00000000 00000000 00000000 00000000
2:通过上面例子可以发现,高16位的特征都已经丢失了,结果都是0,数据最终都落会到数组下标为0的位置,这是一个很明显的数据倾斜问题
3:这个时候对除数无符号右移16位,再处理上面例子
例:hashCode = 131072 -> 00000000 00000010 00000000 00000000
-----------------------------------------
00000000 00000000 00000000 00000010
& 00000000 00000000 00000000 00001111
-----------------------------------------
00000000 00000000 00000000 00000010
例:hashCode = 65536 -> 00000000 00000001 00000000 00000000
-----------------------------------------
00000000 00000000 00000000 00000001
& 00000000 00000000 00000000 00001111
-----------------------------------------
00000000 00000000 00000000 00000001
例:hashCode = 32768 -> 00000000 00000000 10000000 00000000
-----------------------------------------
00000000 00000000 00000000 00000000
& 00000000 00000000 00000000 00001111
-----------------------------------------
00000000 00000000 00000000 00000000
例:hashCode = 16384 -> 00000000 00000000 01000000 00000000
-----------------------------------------
& 00000000 00000000 00000000 00001111
-----------------------------------------
00000000 00000000 00000000 00000000
4:这个时候可以发现数据没有那么倾斜了,但是低16位的特征丢失了,看32768和16384,还是有一定倾斜,这个时候再对数据进行异或运算
例:hashCode = 131072 -> 00000000 00000010 00000000 00000000
-----------------------------------------
00000000 00000000 00000000 00000010
^ 00000000 00000010 00000000 00000000
-----------------------------------------
00000000 00000010 00000000 00000010
& 00000000 00000000 00000000 00001111
-----------------------------------------
00000000 00000000 00000000 00000010
例:hashCode = 65536 -> 00000000 00000001 00000000 00000000
-----------------------------------------
00000000 00000000 00000000 00000001
^ 00000000 00000001 00000000 00000000
-----------------------------------------
00000000 00000001 00000000 00000001
& 00000000 00000000 00000000 00001111
-----------------------------------------
00000000 00000000 00000000 00000001
例:hashCode = 32768 -> 00000000 00000000 10000000 00000000
-----------------------------------------
00000000 00000000 00000000 00000000
^ 00000000 00000000 10000000 00000000
-----------------------------------------
00000000 00000000 10000000 00000000
& 00000000 00000000 00000000 00001111
-----------------------------------------
00000000 00000000 00000000 00000000
例:hashCode = 16384 -> 00000000 00000000 01000000 00000000
-----------------------------------------
00000000 00000000 00000000 00000000
^ 00000000 00000000 01000000 00000000
-----------------------------------------
00000000 00000000 01000000 00000000
& 00000000 00000000 00000000 00001111
-----------------------------------------
00000000 00000000 00000000 00000000
5:这个时候可以看到虽然32768和16384还是倾斜了,但是低16位的特征保留了,后续数组进行扩容上来,低16位就可以参与到运算了
6:为什么是要右移16位,而不是其他位数?个人看法是:数组长度绝大部分的情况是小于2的16次方,右移小于16位会丢失高位前几位
的特性(与运算这几位都会是0),如果大于16位又会丢失高位后面几位的特性,右移16位然后异或刚好可以保留到高16位和低16位的特性
7:为什么是使用异或而不是与运算,或运算?个人看法是:与运算位上面的结果会偏向0,因为只有都是1才会是1,并且高16位运算后都是0。
或运算的话,位上面结果会偏向1,因为只要一位是1结果就是1。异或可以使结果分散,减少数据的倾斜
8:为什么位移使用的是无符号右移,而不是直接右移?因为hashCode是存在负数的,如果是直接右移的话,进行异或操作的时候,数据的
特性将会被改变
例:hashCode = -65536 -> 10000000 00000001 00000000 00000000
11111111 11111110 11111111 11111111
11111111 11111111 00000000 00000000
-----------------------------------------
11111111 11111111 11111111 11111111
^ 11111111 11111111 00000000 00000000
-----------------------------------------
00000000 00000000 11111111 11111111
HashMap中进行操作的源码
使用异或进行数据交换
异或的特点
1:任何数异或自己,结果为0。因为自己异或自己,每一位的数都相同,相同的数异或的结果为0,所以最后结果会是0
例:3 ^ 3 -> 00000000 00000000 00000000 00000011
^ 00000000 00000000 00000000 00000011
-----------------------------------------
00000000 00000000 00000000 00000000
2:任何数异或0,结果都是自己。因为0异或1,结果为1,0异或0,结果还是0
例:3 ^ 0 -> 00000000 00000000 00000000 00000011
^ 00000000 00000000 00000000 00000000
-----------------------------------------
00000000 00000000 00000000 00000011
3:使用异或进行数据交换
例:a和b进行数据
a = a ^ b
b = a ^ b = a ^ b ^ b = a ^ 0 = a
a = a ^ b = a ^ b ^ a = b ^ 0 = b