人有两次生命,一次是出生,一次是觉醒,但愿咱们能够在风华正茂时重获新生,而不是在垂暮之年幡然醒悟。java
1、开篇浅谈
开篇咱们先来小谈一下:若是你是一名高级工程师或者是架构师,你在读源码的过程当中,必定见过以下的代码,能够发现源码里运用了不少位运算来提升性能。node
ArrayList.class 源码节选
ArrayList源码解读—Java8版本web
private void grow(int minCapacity) {
...
//ArrayList扩容1.5倍关键代码
int newCapacity = oldCapacity + (oldCapacity >> 1);
...
}
LinkedList.class 源码节选
LinkedList源码解读—Java8版本架构
//经过判断索引靠链表的前面仍是后面,提升效率
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
...
} else {
...
}
}
HashMap.class 源码节选
HashMap源码解读-Java8版本svg
// 获取一个既大于 cap 又最接近 cap 的 2 的整数次幂数值
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;
}
2、基本概念
在开始java位运算的知识以前,咱们先来了解几个基础的概念,机器数,真值,原码,反码,补码。性能
1.机器数学习
咱们知道不管是代码仍是数值,在计算机中最后都转换成以二进制的形式存在的,而一个数值在计算机中的二进制表示形式,就是这个数的机器数。机器数是有符号位的,在计算机中用一个二进制数的最高位存放符号,正数为0,负数为1,以下实例(按原码表示):spa
十进制的+5,计算机字长为8位,其二进制就是00000101.net
十进制的-5,计算机字长为8位,其二进制就是10000101(这里用的是原码)code
其中00000101和10000101就是机器数
2.真值
因为机器数的第一位是符号位,因此其形式值就不等于其真值的数值,也就是说10000101表示的是-5而不是133(10000101的十进制是131,前提是不算最高位为符号位),所以-5才是机器数的真值。
3.原码
原码是一种计算机中对数字的二进制定点表示方法。原码表示法在数值前面增长了一位符号位(即最高位为符号位):正数该位为0,负数该位为1,其他位表示数值的大小。
[+5]=[00000101](原码)
[ - 5]=[10000101](原码)
由于第一位是符号位,所以8位二进制的取值范围就是[1111 1111,0111 1111]也就是[-127,127]
4.反码
反码是数值存储的一种,可是因为补码更能有效表现数字在计算机中的形式,因此多数计算机通常都不采用反码表示数,反码的表示方法以下:
正数的反码是其自己
负数的反码是在其原码的基础上, 符号位不变,其他各个位取反.
[+5]=[00000101](原码)= [00000101](反码)
[ - 5]=[10000101](原码)= [11111010](反码)
5.补码
在计算机系统中,数值一概用补码来表示和存储。缘由在于,使用补码,能够将符号位和数值域统一处理;同时,加法和减法也能够统一处理。补码的表示方法是:
正数的补码就是其自己
负数的补码是在其原码的基础上, 符号位不变, 其他各位取反, 最后+1. (即在反码的基础上+1)
[+5]=[00000101](原码)= [00000101](反码)=[00000101](补码)
[ - 5]=[10000101](原码)= [11111010](反码)=[11111011](补码)
6.补充
计算机中的符号数有三种表示方法,即原码、反码和补码。三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位,三种表示方法各不相同。而在计算机系统中,数值一概用补码来表示和存储。
3、Java位运算
位移操做:(只针对 int类型的数据有效,java中,一个int的长度始终是32位,也就是4个字节,它操做的都是该整数的二进制数).也可做用于如下类型,即 byte,short,char,long(它们都是整数形式)。当为这四种类型时,JVM先把它们转换成int型再进行操做。
7.左移(<<)
m<<n的含义:把整数m表示的二进制数左移n位,高位移出n位都舍弃,低位补0. (此时将会出现正数变成负数的可能),以下实例:
5<<2 :把十进制的数值5左移两位,按以下步骤计算,
把5转位16位的二进制机器数:00000000 00000000 00000000 00000101
按左移原理,将二进制数左移两位:00000000 00000000 00000000 00010100
左移后结果为20
5<<29:把十进制的数值5左移29位,按以下步骤计算,
把5转位16位的二进制机器数:00000000 00000000 00000000 00000101
按左移原理,将二进制数左移29位:10100000 00000000 00000000 00000000
左移后高位是1,结果显然是负数
小结:m<<n即在数字没有溢出的前提下,对于正数和负数,左移n位都至关于m乘以2的n次方.
8.右移(>>)
m>>n的含义:把整数m表示的二进制数右移n位,m为正数,高位所有补0;m为负数,高位所有补1,实例以下:
5>>2 :把十进制的数值5右移两位,按以下步骤计算,
把5转位16位的二进制机器数:00000000 00000000 00000000 00000101
按右移原理,将二进制数左移两位:00000000 00000000 00000000 00000001
右移后结果为1
-5>>2:把十进制的数值-5右移两位,按以下步骤计算,
把-5转位16位的二进制机器数:11111111 11111111 11111111 11111011
按右移原理,将二进制数右移两位:11111111 11111111 11111111 11111110
右移后结果为-2
小结: m>>n即至关于m除以2的n次方,获得的为整数时,即为结果。若是结果为小数,此时会出现两种状况:
若是m为正数,获得的商会无条件 的舍弃小数位;
若是m为负数,舍弃小数部分,而后把整数部分加+1获得位移后的值。
9.无符号右移(>>>)
m>>>n:整数m表示的二进制右移n位,不论正负数,高位都补0,实例以下:
5>>>2 :把十进制的数值5右移两位,按以下步骤计算,
把5转位16位的二进制机器数:00000000 00000000 00000000 00000101
按右移原理,将二进制数左移两位:00000000 00000000 00000000 00000001
右移后结果为1
-5>>>2:把十进制的数值-5右移两位,按以下步骤计算,
把-5转位16位的二进制机器数:11111111 11111111 11111111 11111011
按右移原理,将二进制数右移两位:00111111 11111111 11111111 11111110
右移后结果为正数
10.按位非操做(~)
~ 按位取反操做符,对每一个二进制位的内容求反,即1变成0,0变成1实例以下
把-5转位16位的二进制机器数:11111111 11111111 11111111 11111011
~(-5) 取反结果:00000000 00000000 00000000 00000100
转为十进制,结果为4
11.按位与操做(&)
& 位与操做符,对应的二进制位进行与操做,两个都为1才为1,其余状况均为0,原理以下:
1&0=0
0&0=0
1&1=1
0&1=0
实例:-5 & 4
-5的二进制形式为: 11111111 11111111 11111111 11111011
4的二进制形式为: 00000000 00000000 00000000 00000100
——————————————————————————————
逻辑与运算结果: 00000000 00000000 00000000 00000000
最终结果为0。
12.按位或操做(|)
| 位或操做符,对应的二进制位进行或操做,两个都为0才为0,其余状况均为1,原理以下:
1|0=1
0|0=0
1|1=1
0|1=1
实例:-5 | 4
-5的二进制形式为:11111111 11111111 11111111 11111011
4的二进制形式为:00000000 00000000 00000000 00000100
————————————————————————————
逻辑或运算结果: 11111111 11111111 11111111 11111111
最终结果为-1。
利用或的原理咱们能够把字节转换为整数,-64&0xFF=192,其中0xFF表示整数255。
13.按位异或操做( ^ )
^ 异或操做符,相同位值为0 不然为1,原理以下:
1^1=0
1^0=1
0^1=1
0^0=0
实例:-5 ^ 4
-5的二进制形式为:11111111 11111111 11111111 11111011
4的二进制形式为:00000000 00000000 00000000 00000100
————————————————————————————
逻辑异或运算结果: 11111111 11111111 11111111 11111111
最终结果为-1。
其实利用逻辑异或操做有个做用就是能够比较两个数值是否相等,即利用11=0,00=0的原理,如5^5==0。
14.总结
经过上面的分析,咱们对java的位运算也算有了比较全面的了解,那么咱们的程序经过位运算又有什么优点呢?其实经过位运算确实会比咱们直接的程序代码运算会快不少,由于位运算直接运算的是计算机底层的二进制机器操做指令,而咱们的程序代码运算最终也是要转成计算机可识别的二进制操做指令才能执行,位运算能够理解为省了中间转换的操做,处理器能够直接操做。事实是咱们在某些源码常常能看见以下代码:
HashMap.class
/**
* Returns a power of two size for the given target capacity.
*/
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;
}
其实原理是同样的,处理器可以直接支持和处理。
推荐阅读
:Java小白进阶架构师学习路线