大家好,欢迎来到IT知识分享网。
我们必须要了解的Java位运算(不仅限于Java) – 陈咬金 – 博客园
基本概念
1、当前常见的CPU位数是32位和64位,所谓32位处理器就是一次只能处理32位,也就是4个字节的数据,而64位处理器一次则能处理64位,即8个字节的数据。
2、一字节(1Byte)等于8位(8bit),位是计算机存储数据的最小单位,也就是计算存储的数据是一系列二进制位信息。每个位用0或1表示。(大B和小b的区别需注意哦)
4、二进制加减运算,加法:0+0=0,0+1=1,1+0=1,1+1=10 ,逢2进1。
5、二进制转十进制及十进制转二进制。
上述列出了一些基本概念和对应的可参考链接,建议先优先了解一下。尤其是二进制和十进制的互相转换以及二进制的加减法规则,在后面都会具体涉及到。
机器数、真值
人类用十进制完全是因为我们有10个手指头。如果有一天你看到一个外星人,它只有4个手指头,那么他使用的一定是四进制,如图所示:
机器数
机器数分为:无符号数和有符号数两种。
一个数在计算机中的二进制表示形式, 叫做这个数的机器数。机器数是带符号的,在计算机用一个数的最高位存放符号, 正数为0,负数为1。
比如,十进制中的数 +3 ,计算机字长为8位,转换成二进制就是00000011。
如果是 -3 ,就是 。那么,这里的 00000011 和 就是机器数。
真值
因为第一位是符号位,所以机器数的形式值就不等于真正的数值。
例如上面的有符号数 ,其最高位1代表负,其真正数值是 -3 而不是形式值131(转换成十进制等于131)。
所以,为区别起见,将带符号位的机器数对应的真正数值称为机器数的真值。
例:0000 0001的真值 = +000 0001 = +1,1000 0001的真值 = –000 0001 = –1
原码、反码、补码
为了妥善的处理数据运算过程中符号位的问题,于是就产生了把符号位和数值位一起编码起来表示相应的数的各种表示方法。例如我们熟悉的原码、反码、补码、移码等。通常将未经编码的数称为真值,编码后的数称为机器数或者机器码。
计算机当前所使用的机器数是采用的补码的方式。在探求为何机器要使用补码之前, 让我们先了解原码, 反码和补码的概念。对于一个数, 计算机要使用一定的编码方式进行存储, 原码, 反码, 补码是机器存储一个具体数字的编码方式。
原码
原码就是符号位加上真值的绝对值, 即用第一位表示符号, 其余位表示值. 比如如果是8位二进制:
[+1]原 = 0000 0001 [-1]原 = 1000 0001
第一位是符号位. 因为第一位是符号位, 所以8位二进制数的取值范围就是:
[0,]
转换为真值后即:
[127,-127 ]
原码是人脑最容易理解和计算的表示方式。原码也可以理解为最原始的机器码。
反码
[+1] = [00000001]原 = [00000001]反 [-1] = []原 = []反
可见如果一个反码表示的是负数, 人脑无法直观的看出来它的数值. 通常要将其转换成原码再计算。
补码
[+1] = [00000001]原 = [00000001]反 = [00000001]补 [-1] = []原 = []反 = []补
对于负数, 补码表示方式也是人脑无法直观看出其数值的. 通常也需要转换成原码在计算其数值。
互相转换、注意事项
注意:我们在最初的基本概念中提到了二进制和十进制的转换方式。但是需要知道的是,一个数在计算机中的二进制表示方式叫做机器数。
而机器数是有符号的,我们将对应的最高位存放符号,0位正数,1位负数。所以机器数并不等于二进制。
由此我们才引出了真值的概念,例如:符号数 ,在十进制中对应的值为131,但是在机器数中,由于1表示负数,所以其真正数值是-3,而并不是131。
符号数:,我们需要先消除其符号位的影响,将其调整为正数:00000011,此时将正数转换为10进制为3,然后再加上最初的符号位表示负数,所以为-3。
需知道的是,二进制仅是以2为基数的计数方式而已,在二进制中是无法区分正数和负数的。而如果想区分正数和负数那么必须在二进制的最高一位中用0和1来表示符号以此来表示正负。那么此时最高位的0和1已经不是二进制的一种计数方式了,而只是一种符号的标识,该符号的标识则是万万不可以直接参与二进制的计算的。
当我们用最高位中的0和1来表示正负时,此时该二进制的数已经不再是符合二进制规则的数了,而是机器数。
二进制仅仅是以2为基数的计算方式,此时最高位的值已经不再是通过2位基数的计算方式而计算出来的,而只是一个表示正负的标识了,所以此时该数值则已经不再是符合二进制规则的数了,而是机器数。
所以,也只有机器数才可以表示正负。而机器数所对应得到的结果则是真数,而并不是10进制数。
如果此时抛出来一个问题: 转换为对应的10进制,那么对应的结果则是131,但如果是转换为真数,则是-3。同样的,如果是将00000011 转换为对应的10进制,则是3,而如果转换为真数,则也是3。
所以当我们看到一个以8位数所表示的二进制数时,则一定要确认该二进制数是表示机器数,还是二进制数?是转换为10进制数,还是真数。其中最大的区别则是,最高的符号位到底是参与二进制的运算,还是仅仅表示符号位。
所以可知,真数和10进制数最大的区别则是是否忽略最高位,在确认完是否忽略最高位,得到最初的正数后,该正数的计算方式,则和二进制转十进制的方式完全相同。其实就是二进制转十进制。哈哈。
那么在注意了二进制数和机器数以及十进制数和真数的区别之后。我们则需要注意的另外一个问题则是:
机器数是包含原码、反码、补码。其中三者之间是可以互相转换的。原码转反码转补码,这块上面已经说明过了,而补码则也可以通过想反的方式重新转换为原码。
而此处需要知晓的则是,反码和补码当然是不可以直接转为真数的,而必须要通过原码才可以进行转换。
这个其实也没什么问题,毕竟原码经过层层转换后得到补码,而补码又可以直接转换为真数,当然不可能了。
接下来则是,为什么原码需要转换为补码,为什么原码不是计算机的计算方式,而是补码?
本文原创地址为 https://www.cnblogs.com/zh94/p/16195373.html
原创声明:作者:陈咬金、 博客地址:https://www.cnblogs.com/zh94/
为什么补码才是计算机的真正计算方式
现在我们知道了计算机可以有三种编码方式表示一个数. 对于正数因为三种编码方式的结果都相同:
[+1] = [00000001]原 = [00000001]反 = [00000001]补
所以不需要过多解释. 但是对于负数:
[-1] = []原 = []反 = []补
可见原码, 反码和补码是完全不同的. 既然原码才是被人脑直接识别并用于计算表示方式, 为何还会有反码和补码呢?
1 - 1 = 1 + (-1) = [00000001]原 + []原 = []原 = -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。
于是补码的出现, 解决了0的符号以及两个编码的问题:
1-1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原 = [0000 0001]补 + [1111 1111]补 = [0000 0000]补=[0000 0000]原
这里说明一下,二进制想加:0000 0001+1111 1111 = 1 0000 0000,但由于是8位数,所以最终的值为 0000 0000。
这样0用[0000 0000]表示, 而以前出现问题的-0则不存在了.而且可以用[1000 0000]表示-128:
(-1) + (-127) = [1000 0001]原 + [1111 1111]原 = [1111 1111]补 + [1000 0001]补 = [1000 0000]补
使用补码, 不仅仅修复了0的符号以及存在两个编码的问题, 而且还能够多表示一个最低数. 这就是为什么8位二进制, 使用原码或反码表示的范围为[-127, +127], 而使用补码表示的范围为[-128, 127]。
因为机器使用补码, 所以对于编程中常用到的32位int类型, 可以表示范围是: [-2的31次方, 2的31次方 – 1] 因为第一位表示的是符号位.而使用补码表示时又可以多保存一个最小值。
Amazing,我们在上面最初使用原码进行加法运算时,由于我们人脑还需要先判断一下最高位的符号后,才能进行二进制运算,然后再添加上对应的符号位。而采用补码后,直接将对应的符号位也参与运算,将补码的数值直接相加,得到的竟然刚好也就是二进制转换后的结果。这样一来,计算机的基础电路设计就可以更加简单,而无需关注符号位的问题,仅需要按照二进制的加法法则执行即可。简直完美。所以这也是补码作为计算机的真正计算方式的原因之一!
位运算
接下来则开始涉及到位运算了。
概念
什么是位运算?我们先来看下百度百科的概念:
程序中的所有数在计算机内存中都是以二进制的形式储存的。位运算就是直接对整数在内存中的二进制位进行操作。
百度百科中所给的解释是具备歧义性的,按照百度百科的解释,直接对整数在内存中的二进制位进行操作就是位运算的话,那么使用二进制数进行算术运算法(+,-,*,/ )岂不是也属于位运算?
我们再来看下维基百科所给的概念解释:
位操作是程序设计中对位模式或二进制数的一元和二元操作。在许多古老的微处理器上,位运算比加减运算略快,通常位运算比乘除法运算要快很多。在现代架构中,情况并非如此:位运算的运算速度通常与加法运算相同(仍然快于乘法运算)。
维基百科中针对位运算的概念相对合理,通过维基百科中的概念我们可以很明显的区分到,位运算是和加减乘除这些算数运算符是不同的。不同的CPU针对位运算的操作是较快于(乘/除)法运算的。
所以这也才是我们需要了解位运算的真正原因,那就是CPU处理器针对位运算符的计算是快于算术运算符的!在特定的编码场景下使用位运算的执行速率则是远远大于算术运算的!
网络上针对位运算较多的内容解释是:位运算是直接对整数在内存中的二进制位进行操作,所以位运算更加节省内存、提高运算效率等等的。其实这是很不严谨的说法,很容易误导大家对位运算的理解,因为所有的整数最终在计算机中都是二进制数,那么所有对整数的运算岂不是都是位运算?当然不是啦。且,位运算真正快的原因也并不是因为节省内存,而主要是因为CPU对位运算的支持!和内存并没有较大关联。
为何位运算CPU执行速率更快
程序中的基本运算包含:
- 算术运算:加、减、乘、除、取余
- 按位运算:按位或“|”、按位与“&”、按位取反“~”、按位异或“^”
- 移位运算:左移x<<k;右移x>>k
其中按位运算和移位运算均属于位运算的范畴。
根据与运算符的规则可知,位运算的整体执行逻辑实际是较为简单的,更多的是进行位数的比较,从而得到一个结果,这种较为简单的运算逻辑,则对于CPU处理器来说,在电路的设计中则也会更加简单许多,以下为与运算符所涉及到的CPU电路图:
而对于一个除法来说呢,在CPU中所对应的电路图设计则是这样的:
可以看到,整个CPU电路图的设计复杂了不止一个层级,所以这也就是为何位运算比我们人常用的算术运算更快的直接原因了。因为对于整个CPU的执行逻辑来说从设计层面就复杂了很多。
本文原创地址为 https://www.cnblogs.com/zh94/p/16195373.html
原创声明:作者:陈咬金、 博客地址:https://www.cnblogs.com/zh94/
位运算符
首先需知道的是,计算机中执行位运算,肯定是采用的补码的方式进行的位计算哦,所以对于真值为负数的情况下,必须先转为补码才能进行计算。
符号 |
描述 |
运算规则 |
& |
与 |
两个位都为1时,结果才为1 |
| |
或 |
两个位都为0时,结果才为0 |
^ |
异或 |
两个位相同为0,相异为1 |
~ |
取反 |
0变1,1变0 |
<< |
左移 |
各二进位全部左移若干位,高位丢弃,低位补0 |
>> |
右移 |
各二进位全部右移若干位,对无符号数,高位补0。各编译器处理方法不一样,有的补符号位(算术右移),有的补0(逻辑右移) |
& 与运算符
运算规则:两位同时为1,结果才为1,否则结果为0。
0&0=0 0&1=0 1&0=0 1&1=1
例如:2 & -2
注意:负数当然是按照补码的方式来进行位计算哦。
& 与运算符的用途:
使用与运算符的方式,则完全可以替代掉:if (a % 2 == 0) 来判断奇偶数的方式,且位运算符由于CPU的支持,执行效率也更高。
关于二进制转十进制的方式,参考基本概念中URL即可。
如果想获取X的低4位的数,则将Y的低4位为1,其余位数为0即可,(X & Y=1010 1110 & 0000 1111=0000 1110)便可得到X的指定低4位。
| 或 运算符
运算规则:两个位都为0时,则结果为0。否则则为1。
0|0=0 0|1=1 1|0=1 1|1=1
例如:2 | -2 =-2
| 或运算符的用途
以上则说明 & 和 | 灵活运用,其实是可以达到相同效果的。但实际使用中则并不然,首先对于上述低4位设置为1的场景,我们只需要找一个Y的数,令Y的低4位为1,其余位为0,这样一个数是很好找的,是一个固定的数,比如:15。转换为二进制后为1111。
但如果使用 & 运算符来面对这个场景,则需要找一个Y,Y的第四位为0,其余位置为1,这样一个数则很难找,并且随着位数的不同,值也是不断变换的,比如:1111 0000=240,但如果是12位数,1111 1111 0000=4080。所以如果使用 & 运算符来在该场景下则是没有 | 运算符更加方便的。
尽管 & 和 | 的规则相反,可灵活变更,但针对特定场景下,还是使用特定的运算符效果更佳O。
^ 异或运算符
运算规则:两个位相同为0,相异为1。
0^0=0 0^1=1 1^0=1 1^1=0
例如:2 ^ -2
^ 异或运算符用途
1、交换两个数
a=a^b; //a=a^b b=a^b; //b=(a^b)^b=a^0=a a=a^b; //a=(a^b)^(a^b^b)=0^b=0
交换两个数的原理,即上面注释所写内容。
不使用位运算的方式交换两个数,则需要定义一个中间变量C,来承接其中的一个数的交换,对于Java来说,定义一个新的Int 变量C,则表示内存中需开辟一个4字节的空间。
所以根据服务特性来选择合适的方式即可,对内存使用率有强要求则使用位运算,没要求则都可以。
static void swap(int a, int b) { int c = a; a = b; b = c;} static void swapBit(int a, int b) { a = a ^ b; b = a ^ b; a = a ^ b;}
~ 取反运算符
运算规则:0变1,1变0。
<< 左移运算符
运算规则:将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)。
设 a=15,即二进制数00001111,左移2位得00,即十进制数60。
设 a=-46,补码后为,1010 1110,a = a<< 2 将a的二进制位左移2位、右补0,即得a=1011 1000,转换为真值后为-56。
设 a=110,补码后为:0110 1110,a = a<<2 将a 的二进制位左移2位,右补0,即得 a=1011 1000,转换为真值后为184。
以此可知,左移n位等于乘以2的n次方,该结论仅适用于该数左移时被溢出舍弃的高位中不包含1的情况,如果溢出的高位中包含1,则不符合上述结论。
>> 右移运算符
设 a=16,补码后为00010000,a = a<<2 将a的二进制位右移2位,左边补0,即得a=00000100,转换为真值后为4。
设a=-16,补码后为,a = a<<2 将a的二进制位右移2位,左边补1,得到a=,转换真值后为-4。
结论:右移运算符,操作数每右移一位,相当于该数除以2。
本文原创地址为 https://www.cnblogs.com/zh94/p/16195373.html
原创声明:作者:陈咬金、 博客地址:https://www.cnblogs.com/zh94/
Java位运算
位运算本身就是处理器、计算机自身所提供的能力,所以针对位运算的使用,实际上是不限于任何编程语言的,此处之所以以JAVA为例,主要是因为本人常用的开发语言是JAVA,哈哈。
针对JAVA中位运算的使用,实际上在JDK中有这很丰富的案例,比如:
1、JDK中线程池ThreadPoolExecutor的实现当中使用Integer类型(4字节,32位)其中高3位保存线程池状态,而低29位保存线程池内有效线程数量。
2、比如JDK的HashMap中使用位运算的方式将初始化容量的数值,快速的转换为2的n次幂。以及计算key的hash时,根据该key的hashCode结果,再将该hashCode的高16位和低16位通过位运算的方式进行混合,以此降低hash碰撞的概率等等。
3、比如我们直接打开常用的Integer类的源码,也会发现里面有大量的位运算的使用。
此处仅是为了通过上述举例的方式来以此说明位运算在Java生态中的使用程度,实际上是非常丰富的,并且由于位运算独特的计算特性,在某些相对特殊的代码场景下,使用位运算会意想不到的将问题给简单化。
如果想了解更多在JAVA中的使用场景和案例,建议大家直接翻看各种源码即可。
以上是一些举例,以下再做一些小的补充说明:
在Java当中的位运算,是只针对Int类型和Long类型有效(java中,一个int的长度始终是32位,也就是4个字节,它操作的都是该整数的二进制数,Long则是64位,表示8字节。),而对于byte,char,short,当为这三个类型时,JVM会先把他们转换为Int类型后再进行操作。
使用 toBinaryString() 可以将对应的十进制转为对应的补码。
System.out.println(Integer.toBinaryString(10));//1010 System.out.println(Integer.toBinaryString(-10));// System.out.println(Long.toBinaryString(10));//1010 System.out.println(Long.toBinaryString(-10));//
如上代码可知,Integer和Long转换为补码时,Integer为32位,Long是64位。实际上上述的基本类型32位还是64位,均是直接定义在源码当中的,感兴趣直接看对应的Integer和Long的源码即可。
其它参考链接
以下参考链接,仅供参考,部分链接中的内容,可能会具备一些歧义,请读者自行分辨。
祝福各位读者所有看到的知识,都可以最终成为自己的知识!撒花!✿✿ヽ(°▽°)ノ✿!
我们必须要了解的Java位运算(不仅限于Java) – 陈咬金 – 博客园
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/138545.html