java中n次⽅怎么表⽰_Java语⾔位运算符详解
很多编程语⾔都有位运算符,Java语⾔也不例外。在Java语⾔中,提供了7种位运算符,分别是按位与(&)、按位或(|)、按位异或(^)、取反(~)、左移(<<)、带符号右移(>>)和⽆符号右移(>>>)。这些运算符当中,仅有~是单⽬运算符,其他运算符均为双⽬运算符。在讲
位运算符是对long、int、short、byte和char这5种类型的数据进⾏运算
解这些运算符的使⽤之前,必须了解⼀个常识,那就是:位运算符是对long、int、short、byte和char这5种类型的数据进⾏运算的,我们不能对double、float和boolean进⾏位运算操作。下⾯就来详细讲解这7种位运算符的使⽤⽅法。
⼀、按位与运算符
按位与运算符的写法是⼀个”&”符号,与”不短路的逻辑与运算符”写法是完全⼀样的,但意义不同。逻辑与运算是对布尔型数据进⾏运算,⽽按位与运算是对⼆进制位上的数值进⾏计算。按位与运算符的运算规则如下图所⽰:
运算规则总结成⼀句话就是:如果两个⼆进制位上的数都是1,那么运算结果为1,其他情况运算结果均为0。下⾯举例说明按位与运算符的运算过程,我们⽤数字5和6进⾏按位与运算。这个过程可以⽤下图表⽰:
运算过程中,⾸先把5和6这两个数字转换为补码,之后还要把这两个数字按位对齐,然后⼀⼀把两个相应的⼆进制位上的数字进⾏按位与运算,运算得到的⼆进制串就是最终的结果。按照补码反向转换为⼗进制数字的规则,可以计算出5&6的运算结果是4。在这⾥要提醒⼤家⼀句:进⾏位运算的时候,最左边的符号位也是要参与运算的。
⼆、按位或运算符
按位或运算符的写法是⼀个”|”符号,与”不短路的逻辑或运算符”写法相同,它的运算规则也很简单,如下图所⽰:
运算规则概括成⽐较好记的⼀句话就是:两个⼆进制位上的数字如果都为0,那么运算结果为0,否则运算结果是1。同按位与运算⼀样,符号位也要参与运算。下⾯我们还是⽤5和6为例来讲解⼀下按位或的
运算过程,如下图所⽰:
负数二进制补码运算法则
⾸先还是把这两个数字转换成补码形式,之后把相应的⼆进制位上的数字进⾏按位或运算:如果两个⼆进制数都是0,计算结果为0,其他情况计算结果均为1。按照这个规则把每⼀位上的数字都计算⼀遍后,得到⼆进制的运算结果是111,这个运算结果转换为⼗进制数是7。
三、按位异或运算符
按位异或运算符写法是”^”,它的运算规则如下图:
如上图,运算规则为:两个⼆进制位上的数字如果相同,则运算结果为0,如果两个⼆进制位上的数字不相同,则运算结果为1。下⾯我们还是⽤5和6为例来讲解⼀下异或的运算过程,如下图:
⾸先还是把这两个数字转换成补码形式,之后把相应的⼆进制位上的数字进⾏异或运算,如果对应的两个⼆进制位上的数相同,计算结果为0,否则计算结果为1。按照这个规则把每⼀位上的数字都计算⼀遍后,得到⼆进制的运算结果是11,这个运算结果转换为⼗进制数是3。
关于异或运算符,有很多⾮常有⽤的特性,我们在这⾥梳理总结⼀下。
Ⅰ、异或运算符满⾜交换律
也就是说,a^b与b^a是等价的,虽然a和b交换了位置,但还是会运算出相同的结果。这个规律还可以推⼴到N个操作数,也就是说,如果有N个变量都参与了异或运算,那么它们的位置⽆论如何交换,运算的结果都是相同的。
Ⅱ、任何两个相同的数字进⾏异或操作,所得到的结果都必然为0
这个特性并不难理解,因为两个相同的数字,换算成补码后,每个⼆进制位上的数也都相同,这样在进⾏异或运算时,按照运算规则,每个⼆进制位上得到的运算结果也都是0,这N个0所组成的⼆进制串就是数字0的补码。我们可以利⽤这个特性快速的判断两个整数是否相同。另外,利⽤这个特性还可以实现内存的快速清零操作,⽐如我们可以在代码中写上a=a^a;这条语句能快速的把变量a所占据的那⼏个字节的内存迅速清零。
Ⅲ、对于任意⼀个⼆进制位来说,这个位上的数与0进⾏异或运算,运算结果与这个⼆进制位上的数是相同的,⽽与1进⾏异或运算,结果与这个⼆进制位上的数字相反
注意,我们现在说的是⼆进制位上的数字,所谓相反不是说原来这个位上是1,运算结果是-1,⽽是说原来是1,运算结果为0,原来如果是0,运算结果是1,这才是此处所说的”相反”的概念。这个特性也⾮常好理解,⼩伙伴们⼀定要记住它,在以后进⾏⼀些位运算操作的时候经常会⽤到这个特性。
Ⅳ、对于任何两个整数a和b,a^b^b等于a
这个结论为什么成⽴呢?简单说来,就是因为这个表达式中有b^b,⽽b^b的结果为0,前⽂已讲过,任何⼀个数与0进⾏按位异或操作,结果仍然是这个数本⾝,所以,a^b^b等于a。这个特性在加密运算⽅⾯有着很普遍的应⽤。我们可以把a当作要加密的数据,⽽把b当作密钥。a异或b就是把a⽤密钥b进⾏了加密操作,当需要解密时,仍然以b作为密钥,再进⾏⼀次异或就实现了解密。
这个特性还可以推出另外⼀个结论:对于任何两个整数a和b,a^b^a等于b
对于任何两个整数a和b,a^b^a等于b。我们能够得到这个结论的原因也很简单,就是因为按照交换律,a^b与b^a的运算结果是⼀样的,所以a^b^a等价于b^a^a,这个表达式中出现了a^a,a^a的值也为0,所以整个表达式的其实就相当于b^0,最终结果还是b。
希望⼤家能够牢记以上这些结论,在后续的⽂章中,会讲解如何⽤这些结论去解决实际问题。
四、按位取反运算符
按位取反运算符写法是”~”,它的运算规则是:对每个⼆进制位进⾏取反操作,所谓取反就是原来⼆进制位上如果是0,那么就变成1,反之,如果原来⼆进制位上是1,那么就变为0。取反运算符是⼀个单⽬运算符,所以只需要⼀个操作数就可以了。我们以数字5为例讲解按位取反的运算过程:
⾸先把数字5转换成补码形式,之后把每个⼆进制位上的数字进⾏取反,如果是0就变成1,如果1就变成
0,经过取反后得到的⼆进制串就
如果是运算结果,这个运算结果被还原为⼗进制数是-6。取反运算符的运算规则也⾮常容易理解,但是在这⾥⽼师需要提醒各位读者注意:如果是对变量进⾏取反操作,那么经过操作之后,变量的值并不会发⽣变化!为⽅便⼩伙伴们理解,请看下图:
是对变量进⾏取反操作,那么经过操作之后,变量的值并不会发⽣变化!
从程序运⾏的结果可以看出:输出a的值还是5,这说明变量a经过取反得到的那个-6并没有被赋值到变量a中,通过这个例⼦可以证明,取反运算并没有对变量重新赋值的功能,取反运算的结果只是临时保存在操作数栈中,变量本⾝的值不会因取反操作⽽发⽣改变。
下⾯再来讲解⼀下与位移相关的运算符。所谓”位移”就是指在内存中对⼆进制串进⾏移动的操作。只要是移动操作,就必然会涉及到以下⼏个问题,怎样表⽰移动⽅向?怎样表⽰移动的位数?移动之后空出来的⼆进制位⽤什么来填充?移动之后跑到原来内存单元外⾯的那些数字怎么处理?符号位要不要跟着⼀起移动?这些问题都是我们学习位移操作要弄清楚的细节。各位⼩伙伴可以带着这些问题来学习位移相关的运算符。与位移相关的运算符有三个,分别是左移(<<)、带符号右移(>>)、⽆符号右移(>>>)。
五、左移运算符
左移运算符的写法是”<<“,看上去向两个向左的箭头,表⽰要把⼆进制数据在内存空间中向左边移动。使⽤左移运算符时,把想进⾏位移操作的操作数放最左⾯,之后写上左移运算符,在左移运算符的右边写上移动的位数。例如:5<<2就表⽰对数字5进⾏左移2位的操作。下图展⽰了进⾏左移操作之后,⼆进制串在内存中是怎样变化的:
可以看到这个⼆进制串在内存中整体向左移动了两位,那么最左边的两位就跑到内存单元的外⾯去了,这两位数字将会被舍弃,右边空出的两位⽤0补齐。
左移运算有乘以2的N次⽅的效果。⼀个数向左移动1位,就相当于乘以2的1次⽅,移动两位就相当于乘以2的2次⽅,也就是乘以4。位移操作在实际运算时远远快于乘法操作,所以在某些对运算速度要求⾮常⾼的场合,可以考虑⽤左移代替乘以2的N次⽅的乘法操作。但是需要提醒⼤家注意三个细节:
⾸先:位移操作同取反操作⼀样,并不能改变变量本⾝的值,所能改变的仅是存储在操作数栈中那个数据的值,不理解这句话意思的⼩伙伴看下图:
其次:当位移的位数很多时,导致最左边的符号位发⽣变化,就不再具有乘以2的N次⽅的效果了。⽐如⼗进制的5转换为补码形式是:前⾯29个0最后3位是101,如果移动29位,那么最前⾯的符号位就变成了1,此时运算的结果就成为了⼀个负数,不再是5乘以2的29次⽅的乘法结果。
最后:对于byte/short/int三种类型的数据,Java语⾔最多⽀持31位的位移运算,对于long类型的数据⽽⾔,最多⽀持63位的位移运算。这可能是因为Java语⾔的设计者认为位移的偏移量已经超过存储数据本⾝的长度,没有什么意义。⼩伙伴们可以试⼀下数字5左移32位是什么结果。
六、带符号右移运算符
右移运算分为两种,分别是带符号右移和⽆符号右移。⾸先我们来说说带符号右移运算符。带符号右移运算符的写法是”>>“,与左移运算符的⽅向恰好相反。所谓带符号右移就是指当⼆进制串向右边移动以后,左边空出的位⽤”符号位上的数字”填充,说的更直⽩⼀点,如果是正数,⼆进制串右移的时候⽤0来填充左边的空位,⽽对于负数⽽⾔,右移的时候⽤1来填充左边的空位,如下图:
从图上可以清楚的看到带符号右移操作在⼆进制串移动之后左边空位是怎样被填充的。之前强调过,左
移N位的操作具有乘以2的N次⽅的效果,其实带符号右移也具有”类似”除以2的N次⽅的效果。请注意,这⾥说的是“类似”除以2的N次⽅的效果,为什么要加上“类似”两个字呢?就是因为对于正数⽽⾔,带符号右移之后产⽣的数字确实等于除以2的N次⽅,⽐如说我们把N的值设为3,对于正15,带符号右移3位的结果是1,这个结果与“15除以2的3次⽅”的结果是相同的。
但是对于负数⽽⾔,带符号右移的效果分为两种情况,我们分别来讨论。
如果这个负数是“2的N次⽅”的整数倍,那么带符号右移N位的效果也等于除以2的N次⽅。举个例⼦:我们还是把N的值设为3,如果对
于“-16”来说,它是“2的3次⽅”的整数倍,那么带符号右移3位的结果是-2,这个结果相当于“-16除以2的3次⽅”。
⽽如果这个负数不是“2的N次⽅”的整数倍,那么右移N位之后,是在除以2的N次⽅的结果之上还要减去1。⽐如,对于-15来说,它不是“2的3次⽅”的整数倍,那么带符号右移3位的结果是-2,这个运算结果其实就是“-15被2的3次⽅整除再减去1”。⼩伙伴们也可以⽤其他负整数来验证⼀下这个结论。因为并⾮每个负整数带符号右移的结果都等于除以“2的N次⽅”,所以我们才在⽂中添加了“类似”这两个字。
带符号右移的操作可以保证移动之前和移动之后数字的正负属性不变,原来是正数,不管移动多少位,移动之后还是正数,原来是负数,移
对于任何⼀个byte、short或者int类型的数据⽽⾔,动之后还是负数。另外,我们还可以继续深挖⼀下这个特性,从⽽得到⼀个结论:对于任何⼀个byte、short或者int类型的数据⽽⾔,带符号右移31位(或更多位)之后,得到的必然是0或者是-1。对于long类型的数据⽽⾔,带符号右移63位(或更多位)之后,得到的也必然是0或者是-1。能够得出这个结论的依据也很简单,就是因为对于byte、short和int类型的变量⽽⾔,如果是正数,带符号右移31也必然是0或者是-1。
位之后产⽣的⼆进制串必然全部是0,转换成对应的⼗进制数就是0;⽽对于负数⽽⾔,带符号右移31位之后产⽣的⼆进制串必然全部是1,转换成⼗进制数就是-1。对于long类型的数据,带符号右移63位也具有相同效果。
七、⽆符号右移运算符
前⽂已说过:右移运算分为两种,分别是带符号右移和⽆符号右移。现在再来讲解⽆符号右移。⽆符号右移运算符的写法是”>>>”,⽐带符号右移多了⼀个”>”。带符号右移的运算规则与⽆符号右移的运算规则差别就在于:⽆符号右移在⼆进制串移动之后,空位由0来补充,与符号位是0还是1毫⽆关系,如下图:
以上图⽚展⽰了⽆符号右移的运算规则。对于正数⽽⾔,⽆符号右移和带符号右移没有什么区别,⽽对于负数⽽⾔,经过⽆符号右移会产⽣⼀个正数,因为最左边的符号位被0填充了。
到此为⽌,我们已经讲解了全部的7个位运算符。很多⼩伙伴都会认为位运算没有太多的实⽤价值,学习位运算操作也只是为了应付⾯试。其实这种想法是完全错误的。位运算在很多场合都有着深⼊应⽤,能够解决很多实际问题。随后的⼏篇⽂章将详细讲解位运算在实际开发中的应⽤技巧。
欢迎各位⼩伙伴关注我的专栏,专栏将持续输出有益有趣的技术⽂章。如想系统学习Java编程,可以点击下⾯的链接观看我的视频课程,有问题也可以加⼊我的QQ291839907⼀起讨论。
沙利穆_腾讯课堂s halimu.ke.qq