第八章《Java高级语法》第3节:位运算符
很多编程语言都有位运算符,Java语言也不例外。位运算符与我们之前学过的其他运算符不同,它是以一个二进制位上的值作为操作数的,也就是说:位运算符的操作数就是二进制位上的那个0或者1。Java语言提供了7种位运算符,如表8-1所示。
表8-1 Java语言位运算符
符号 |
运算符意义 |
& |
按位与 |
| |
按位或 |
^ |
按位异或 |
~ |
取反 |
<< |
左移 |
>> |
带符号右移 |
>>> |
无符号右移 |
此处要特别提醒各位读者:位运算符是对long、int、short、byte和char这5种类型的数据进行运算的,不允许对double、float和boolean进行位运算操作。下面逐一讲解每种位运算符的运算规则。
1. 按位与运算符
按位与运算符的写法是一个“&”符号,与不短路的逻辑“与”运算符写法完全一样,但意义不同。逻辑“与”运算是对布尔型数据进行运算,而按位与运算符是对二进制位上的数值进行计算。按位与运算符的运算规则是:如果两个二进制位上的数都是1,那么运算结果为1,其他情况运算结果均为0,这个规则可以用下面的图8-11表示。
图8-11按位与运算符运算规则
如果对数字5和6进行按位与运算,其过程可以用下面的图8-12表示:
图8-12 5&6运算过程
运算过程中,首先把5和6这两个数字转换为补码,并把这两个数字按位对齐,然后一一把两个相应的二进制位上的数字进行按位与运算,运算得到的二进制数就是最终的结果。按照补码反向转换为十进制数字的规则,可以得出5&6的运算结果是4。此处还需说明:进行位运算的时候,最左边的符号位也要参与运算。
2. 按位或运算符
按位或运算符的写法是一个“|”符号,与不短路的逻辑“或”运算符写法相同。它的运算规则是:如果两个二进制位上的数字都为0,那么运算结果为0,否则运算结果是1。这个规则可以用图8-13表示:
图8-13 按位或运算符运算规则
如果对数字5和6进行按位或运算,其过程可以用下面的图8-14表示:
图8-12 5|6运算过程
首先还是把这两个数字转换成补码形式并对齐,之后把相应的二进制位上的数字进行按位或运算。经计算所得的二进制数111,将这个运算结果转换为十进制数是7。
3. 按位异或运算符
按位异或运算符写法是“^”,它的运算规则是:如果两个二进制位上的数字相同,则运算结果为0,如果两个二进制位上的数字不相同,则运算结果为1。这个规则可以用下面的图8-15表示:
图8-15 按位异或运算符运算规则
关于异或运算符,有以下特性:
(1)异或运算符满足交换律。
也就是说,a^b与b^a是等价的,虽然a和b交换了位置,但还是会运算出相同的结果。这个规律还可以推广到N个操作数,也就是说,如果有N个变量都参与了异或运算,那么它们的位置无论如何交换,运算的结果都是相同的。
(2)任何两个相同的数字进行异或操作,所得到的结果都必然为0。
这个特性并不难理解,因为两个相同的数字,换算成补码后,每个二进制位上的数也都相同,这样在进行异或运算时,按照运算规则,每个二进制位上得到的运算结果也都是0,这N个0所组成的二进制数就是0的补码。我们可以利用这个特性快速的判断两个整数是否相同。另外,利用这个特性还可以实现内存的快速清零操作,比如可以在代码中写上“a=a^a;”这条语句能快速的把变量a所占据的那几个字节的内存迅速清零。
(3)对于任意一个二进制位来说,这个位上的数与0进行异或运算,运算结果与这个二进制位上的数是相同的,而与1进行异或运算,结果与这个二进制位上的数字相反.
注意,此处所说的是二进制位上的数字,所谓相反不是说原来这个位上是1,运算结果是-1,而是说原来是1,运算结果为0,原来如果是0,运算结果是1,这才是这里所说的”相反”的概念。
(4)对于任何两个整数a和b,a^b^b等于a.
这个结论之所以成立,就是因为这个表达式中有b^b,而b^b的结果为0,前文已讲过,任何一个数与0进行按位异或操作,结果仍然是这个数本身,所以,a^b^b等于a。这个特性在加密运算方面有着很普遍的应用。我们可以把a当作要加密的数据,而把b当作密钥。a^b就是把a用密钥b进行了加密操作,当需要解密时,仍然以b作为密钥,再进行一次异或就实现了解密。
各位读者可以仿照按位与和按位或的计算过程推导一下5^6的运算结果。
4. 按位取反运算符
按位取反运算符写法是“~”,它的运算规则是:对每个二进制位进行取反操作,即原来二进制位上如果是0,那么就变成1,反之,如果原来二进制位上是1,那么就变为0。取反运算符是一个单目运算符,所以只需要一个操作数就可以了。假如对数字5进行按位取反操作,其运算过程如图8-16所示。
图8-16 ~5运算过程
从图8-16可以看出,这个运算结果是一个负数。按照负数补码规则,可以还原出这个运算结果是十进制数-6。此处需要提醒各位读者注意:对变量进行取反操作,变量的值并不会发生变化!为方便读者理解这句话的含义。
【例08_03 按位取反运算】
Exam08_03.java
public class Exam08_03 {public static void main(String[] args) {int a = 5;System.out.println("对a按位取反的结果:"+~a);System.out.println("取反操作后a的值为:"+a);}
}
【例08_03】的运行结果如图8-17所示。
图8-17 【例08_03】运行结果
从图8-17可以看出:取反操作后a的值还是5,这说明变量a经过取反得到的那个-6并没有被赋值到变量a中,由此可以证明:取反运算并没有对变量重新赋值的功能,因此变量本身的值不会因取反操作而发生改变。
除以上4种位运算符外,Java语言还提供了位移相关的位运算符。所谓”位移”就是指在内存中对二进制数进行移动的操作。与位移相关的运算符有三个,分别是: <<、>>、>>>,它们分别表示:左移、带符号右移和无符号右移
5 左移运算符
左移运算符的写法是“<<”,它表示要把二进制数据在内存空间中向左边移动。使用左移运算符时,把想进行位移操作的操作数放在左面,之后写上左移运算符,在左移运算符的右边写上移动的位数。例如:5<<2就表示对数字5进行左移2位的操作。下面的图8-18展示了数字5进行左移2位的操作后,二进制数在内存中是怎样变化的.
图8-18 5<<2运算示意
从图8-18可以看到:这个二进制数在内存中整体向左移动了两位,最左边的两位被移出内存单元,这两位数字将会被舍弃,右边空出的两位用0补齐。
左移运算有乘以2的N次方的效果。一个数向左移动1位,就相当于乘以2的1次方,移动两位就相当于乘以2的2次方,也就是乘以4。位移操作在实际运算时远远快于乘法操作,所以在某些对运算速度要求非常高的场合,可以考虑用左移代替乘以2的N次方的乘法操作。使用左移运算符时需要注意三个细节。
(1)位移操作同取反操作一样,并不能改变变量本身的值,例如:int型变量a的值是5,执行完a<<2的操作后,a的值仍然是5。
(2)当位移的位数很多时,导致最左边的符号位发生变化,就不再具有乘以2的N次方的效果了。比如十进制的5转换为补码形式是:前面29个0最后3位是101,如果移动29位,那么最前面的符号位就变成了1,此时运算的结果就成为了一个负数,不再是5乘以2的29次方的乘法结果。
(3)对于byte/short/int三种类型的数据,Java语言最多支持31位的位移运算。如果位移数超过31,则虚拟机会对位移数按连续减去32,直到得到一个小于32并且大于等于0的数,然后以这个数作为最终的位移数。例如对int型变量进行位移97位的操作,虚拟机会首先对97连续减去3个32,最终得到数字1,实际进行位移运算时会对变量只位移1位。而对于long类型的数据而言,最多支持63位的位移运算,如果位移数超过63,则连续减去64,以最终得到的小于64并且大于等于0的数作为位移数。
6. 带符号右移运算符
右移运算分为两种,分别是带符号右移和无符号右移。带符号右移运算符的写法是“>>”,与左移运算符的方向恰好相反。带符号右移就是指当二进制数向右边移动以后,左边空出的位用“符号位上的数字”填充,也就是说:如果是正数,二进制数右移的时候用0来填充左边的空位,而对于负数而言,右移的时候用1来填充左边的空位。图8-19和8-20分别展示了5和-5进行带符号右移2位后的结果:
图8-19 5>>2运算示意
图8-20 -5>>2运算示意
带符号右移具有“类似”除以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。例如:N的值还为3,对于-15来说,它不是“2的3次方”的整数倍,那么带符号右移3位的结果是-2,这个运算结果其实就是“-15除以2的3次方再减去1”。各位读者也可以用其他负整数来验证一下这个结论。
带符号右移的操作可以保证移动之前和移动之后数字的正负属性不变,原来是正数,不管移动多少位,移动之后还是正数,原来是负数,移动之后还是负数。另外,对于任何一个byte、short或者int类型的数据而言,带符号右移31位之后,得到的必然是0或者是-1。对于long类型的数据而言,带符号右移63位之后,得到的也必然是0或者是-1。能够得出这个结论的依据也很简单,就是因为对于byte、short和int类型的变量而言,如果是正数,带符号右移31位之后产生的二进制数的每一位必然全部是0,转换成对应的十进制数就是0;而对于负数而言,带符号右移31位之后产生的二进制数的每一位必然全部是1,转换成十进制数就是-1。对于long类型的数据,带符号右移63位也具有相同效果。
7. 无符号右移运算符
无符号右移运算符的写法是“>>>”,比带符号右移多了一个“>”。无符号右移操作在二进制数移动之后,空位由0来补充,与符号位是0还是1毫无关系,下面的图8-21和8-22分别展示了5和-5进行无符号右移2位后的结果:
图8-21 5>>>2运算示意
图8-22 -5>>>2运算示意
可以看出:对于正数而言,无符号右移和带符号右移没有什么区别,而对于负数而言,经过无符号右移会产生一个正数,因为最左边的符号位被0填充了。
以上讲解了7种位运算符的运算规则和注意事项,下面的【例08_04】演示了各种位运算符的用法和运算效果:
【例08_04 位运算符演示】
Exam08_04.java
public class Exam08_04 {public static void main(String[] args) {int a = 5, b = -5, c = 6;long d = 10,e = -10;System.out.println("5&6=="+(a&c));System.out.println("5|6=="+(a|c));System.out.println("5^6=="+(a^c));System.out.println("~5=="+(~a));System.out.println("5<<2=="+(a<<2));System.out.println("-5<<2=="+(b<<2));System.out.println("5<<33=="+(a<<33));System.out.println("5>>2=="+(a>>2));System.out.println("-5>>2=="+(b>>2));System.out.println("5>>>2=="+(a>>>2));System.out.println("-5>>>2=="+(b>>>2));System.out.println("int型正数带符号右移31位=="+(a>>31));System.out.println("int型负数带符号右移31位=="+(b>>31));System.out.println("long型正数带符号右移63位=="+(d>>63));System.out.println("long型负数带符号右移63位=="+(e>>63));}
}
建议各位读者先把例子中的每一个表达式都自己先手动计算一遍,然后再把计算结果与源程序的运行结果对照一下,这样能加深对各种位运算符的理解和记忆。
除阅读文章外,各位小伙伴还可以点击这里观看我在本站的视频课程学习Java!
第八章《Java高级语法》第3节:位运算符相关推荐
- Java基本语法(10)--位运算符
位运算符的使用对象是数,位运算是直接对整数的二进制进行的运算,理解必须要在二进制层面进行. 功能说明: 每<<左移1位,乘一次2(低位补0) 每>>右移一位,除一次2,符号位不 ...
- 【Java高级语法】(十一)枚举类:还在因为使用了魔法数而被老大怼吗,一起来看看枚举器的作用吧!~
Java高级语法详解之枚举类 1️⃣ 概念 2️⃣ 优势和缺点 3️⃣ 使用
- Java基础语法 第2节 Java语言基本语法
一.标识符和关键字 1.标识符 1)java中标识符用来为程序的白能量.常量.方法.类.接口和包名命名,标识符由字母.数字.下划线.美元符号组成,且第一个字符不能是数字: 2)标志符命名规则:见名知意 ...
- java 异或 排序_Java的位运算符详解实例——与()、非(~)、或(|)、异或(^)...
位运算符主要针对二进制,它包括了:"与"."非"."或"."异或".从表面上看似乎有点像逻辑运算符,但逻辑运算符是针对两 ...
- 第八章《Java高级语法》第1节:数制及数制间的转换
人们在生活中用到数字都是以十进制的方式计数的,其实除十进制之外,计算机科学领域还会经常使用二进制.八进制和十六进制完成计数.二进制.八进制.十进制和十六进制的数字,都是是如何完成计数的?它们之间如何进 ...
- Java高级语法笔记-反射机制(Reflection) (1)
反射机制:在C/C++里面是没有的. 反射机制是Java的一个非常重要的机制.一些著名的应用框架都使用了此机制. java.lang.Class它是Java语法的一个基础类,用于描述一个class对象 ...
- Java高级语法笔记-枚举类型
用enum定义枚举类型,即定义一些常量. public enum Weekday{ SUNDAY,MONDAY,TUESDAY,WEDNESDAY, THURSDAY,FRIDAY,SATURDAY ...
- Java高级语法笔记-匿名类(Anonymous Class)
匿名类(Anonymous Class) 匿名内部类,简称匿名类:是内部类的一种化简写法. 基本写法如下: BaseType obj=new BaseType(){ //类的定义 } Java项目中匿 ...
- Java高级语法笔记-语法支持的异常
语法支持的异常 Java对异常支持得很彻底,举例来说: (java.lang.*下面,语法自带的异常类) 数组越界 ArrayIndexOutOfBoundsException 除0异常 Arithm ...
最新文章
- oracle 分区使用情况,Oracle Hash分区的使用总结
- 《Python for Data Analysis》之 Series
- pandas使用idxmax函数获取dataframe每个数据行中最大值对应的列名称(column label of max value in each row in dataframe)
- java consumer.poll_kafka消费者API consumer.poll()没有错误,没有异常,只是阻止
- Android6.0 源码修改之 仿IOS添加全屏可拖拽浮窗返回按钮...
- vue定时ajax获取数据,vue 中使用 AJAX获取数据的方法
- Java 链表数据修改
- 致谢!华为全联接2020精彩回顾
- ntp 配置详解(转载后整理汇总)
- python读音有道-python利用有道翻译实现quot;语言翻译器”的功能
- win32 disk imager使用后u盘容量恢复
- 在控制器控制方式中,异步控制与联合控制有什么区别?
- Tips for ASP.NET Application Performance Enhancement
- android电视自动关机,Android定时关机问题解决
- Windows 中使用苹果 macOS 动态桌面壁纸
- WoMic虚拟麦克风技术剖析
- 获取Android设备的序列号(SN号)
- 平板电脑触摸屏行业研究及十四五规划分析报告
- MAC通过ZOC远程访问Linux
- 南通大学教务管理微信公众号的用户体验