IT兄弟连 Java语法教程 位运算符
Java定义了几个位运算符,它们都可以用于整数类型(long、int、short、byte以及char)。这些运算符对操作数的单个位进行操作。表1 对位运算符进行了总结。
表1 位运算符
由于位运算符是对整数中的位进行操作,因此理解这类操作会对数值造成什么影响是很重要的。特别是,掌握Java存储整数数值的方式以及如何表示负数是有用的。因此,在介绍位运算符之前,先简要描述以下这两个主题。
在Java中,所有整数类型都由宽度可变的二进制数字表示。例如,byte型数值42的二进制形式是00101010,其中每个位置表示2的幂,从最右边的20开始。向左的下一个位置位21,即2;接下来是22,即4;然后是8、16、32,等等,所以42在位置1、3、5(从右边开始计算,最右边的位计数位0)被设置1;因此,42是21+23+25的和,即2+8+32。
所有整数类型(char类型除外)都是有符号整数,这意味着它们既可以表示正数,也可以表示负数。java使用所谓的“2的补码”进行编码,这意味着负数的表示方法为:首先反转数值中的所有位(1变为0,0变为1),然后再将结果加1.例如,-42的表示方法位:首先反转42中所有位(00101010),得到11010101,然后加1,结果为11010110,即-42。为了解码负数,首先先反转所有位,然后加1.例如,反转-42(11010110),得到00101001,即41,所以再加上1就得到了42。
如果分析“零交叉”问题,就不难理解Java(以及大多数其它计算机语言)使用2的补码表示负数的原因。假定对于byte型数值,0被表示为00000000.如果使用1的补码,简单地反转所有位,得到11111111,这会创建-0.但问题是,再整数数学中,-0是无效的。使用2的补码表示负数可解决这个问题。如果使用2的补码,1被加到补码上,得到100000000,这样就在左边新增了一位,超出了byte类型表示的范围,从而得到了所期望的行为,即-0和0相同,并且-1被编码位11111111.尽管再前面的例子中使用的是byte数值,但是相同的基本原则被应用与Java中的所有整数类型。
因为Java使用2的补码存储负数,并且因为Java中的所有整数都是有符号数值,所以应用位运算符时很有可能产生意外的结果。例如,不管是有意还是无意的,将高阶位改为1,都会导致结果值被解释为负数。为了避免产生不愉快的结果,只需要记住高阶位决定了整数的符号,而不管高阶位是如何设置的。
1 位逻辑运算符
位逻辑运算符包括&、|、^和~。表2显示了各种位逻辑运算的结果。在后续的使用中,请牢记位运算符是针对操作数中的每个位进行操作的。
表2 位逻辑运算
1)按位取反
也称为“位求补”。一元非运算符“~”可以反转操作数中的所有位。例如数字42,位模式如下:
00101010
进行“非”运算之后,变为:
11010101
2)按位与
对于按位与运算符“&”,如果两个操作数都是1,结果为1,只要其中任何一个操作数为0,结果就为0。下面是一个例子:
00101010 42
&00001111 15
-------------
00001010 10
3)按位或
按位或运算符“|”的运算规则为:只要两个操作数中有一个为1,结果就为1。实例如下所示:
00101010 42
|00001111 10
-------------
00101111 47
4)按位异或
按位异或运算符“^”的运算规则为:如果只有一个操作数为1,结果为1,否者结果为0。下面的例子演示了“^”运算符的效果。这个例子还演示了按位异或运算的一个有用特性。请注意,只要第二个操作数中的某位为1,就会反转42的位模式中的对应位;只要第二个操作数中的某位为0,第一个操作数中的对应就保持不变。当执行某些类型的位数操作时,将会发现该特性很有用。
00101010 42
^00001111 10
-------------
00101010 37
5)使用位逻辑运算符
下面的程序演示了位逻辑运算符的用法:
public class BitLogic{
public static void main(String[] args){
String[] binary = {"0000","0001","0010","0011","0100","0101","0110", "0111","1000","1001","1010","1011","1100","1101","1110","1111"};
int a = 3;
int b = 6;
int c = a | b;
int d = a & b;
int e = a ^ b;
int f = (~a & b) | (a & ~b);
int g = ~a & 0x0f;
System.out.println(" a = " + binary[a]);
System.out.println(" b = " + binary[b]);
System.out.println(" a|b = " + binary[c]);
System.out.println(" a&b = " + binary[d]);
System.out.println(" a&b = " + binary[e]);
System.out.println("~a&b|a&~b = " + binary[f]);
System.out.println(" ~a = " + binary[g]);
}
}
在这个例子中,a和b的位模式包含了两个二进制位的所有4种可能:0-0、0-1、1-0以及1-1。根据c和d中的结果,可以看出“|”和“&”对每一位的操作方式。e和f被赋值为相同的值,并演示了“^”运算的工作原理。字符串数组binary中保存了介于0到15的数字的二进制表示形式。在这个例子中,为了显示每个结果的二进制表示形式,对数组进行了索引。二进制数值n的字符串表示恰好存储在binary[n]中。将~a和0x0f(二进制00001111)进行按位与运算,以减小其值,使其小于16,从而可以使用binary数组输出结果。下面图1是该程序的输出。
图1 BitLogic运行结果
2 左移
左移运算符“<<”可以将数值中的所有位向左一定指定的次数,它的一般形式为:
value << num
其中,num指定了将value中的数值向左移动的位数,即“<<”将指定值中的所有位向左移动由num指定的位数。对于每次位移,高阶位被移出(并丢失),右边的位用0补充。这意味着左移int型操作数时,如果某些位一旦超出位31,那么这些位将丢失。如果操作数为long类型,那么超出位63的位会丢失。
当左移byte和short型数值时,Java的自动类型转换会导致意外的结果。您知道,当表达式进行求值时,byte换个short型数值会被提升为int型。而且,这种表达式的结果也是int型。这意味着对byte和short型数值进行左移操作的结果为int型,并且移动的位不会丢失,除非它们超过位31。此外,当将负数的byte和short型数值提升为int型时,会进行符号扩展。因此,高阶位将使用1填充。所以,对byte和short型数值进行左移操作,必须抛弃int型结果的高阶字节。例如,如果左移byte型数值,会先将该数值提升为int型,然后左移。这意味着如果您想要的结果是移位后的byte型数值,就必须丢弃结果的前三个字节。完成这个任务最容易的方法是,简单地将结果转换为byte类型。下面的程序演示了这一概念。
public class ByteShift{
public static void main(String[] args){
byte a = 64;
int i = a << 2;
byte b = (byte)(a << 2);
System.out.println("a : " + a);
System.out.println("i : " + i);
System.out.println("b : " + b);
}
}
编译并运行这个程序,控制台将显示如图2所示的信息。
图2 ByteShift运行结果
由于为了进行求值,a被提升为int类型,因此对64(01000000)左移两次,使得i包含256(100000000)。但是,b中的值为0,因为移位之后,现在低字节为0。只有一位被移出了。
因为每次左移相当于将原始值乘以2,所以程序员经常利用这个事实作为乘以2的高效替代方法。但是需要小心。如果将二进制1移进高阶位(第31位或第63位),结果会变为负数。
2.3 右移
右移运算符“>>”可以将数值中的所有位向右移动指定的次数,它的一般形式为:
value >> num
其中,num指定了将value中的数值向右边移动的位数,即“>>”将指定值中的所有位向右移动由num指定的位数。
下面的代码将数值32向右边移动两位,结果是a被设置为8:
int a = 32;
a = a >> 2;
如果数值中的有些位被“移出”了,这些位会丢失。例如,下面的代码将35右移两位,从而导致两个低阶位丢失,结果是再次将a设置为8:
int a = 35;
a = a >> 2;
用二进制形式分析同一操作,可以更清晰地看出操作过程;
00100011 35
>> 2
00001000 8
每次右移一个值,相当于将改值除以2,并丢弃所有余数。可以利用这一特性,实现高性能的整数除以2操作。
当进行右移操作时,右移后的顶部(最左边)位使用右移前顶部位的值填充。这称为符号扩展,当对负数景行右移操作时,该特性可以保留负数的符号。例如,-8>>1的结果是-4,用二进制表示为:
11111000 -8
>> 1
11111100 -4
有趣的是,如果对-1进行右移,结果总是-1,因为符号扩展使得高阶位总是1。
2.4 无符号右移
每次位移时,“>>”运算符自动使用原来的内容填充高阶位。这个特性可以保持数值的符号。但是,有时这不是期望的效果。例如,如果对那些表示非数值的内容进行位置操作,可能不希望发生符号扩展。当操作基于像素的值和图形时,这种情况非常普遍。对于这种情形,不管高阶位的初始值时什么,通常会希望将0移进高阶位。这就是所谓的无符号右移。为了完成无符号右移,需要使用Java的无符号右移运算符“>>>”,该运算符总是将0移进高阶位。
下面的代码演示了“>>>”的用法。其中,a被设置为-1,这会将所有的32位设置为二进制1。然后将该值右移24位,并使用0填充高端的24位,而忽略常规的符号扩展。该操作将a设置位255.
int a = -1;
a = a >>> 24;
下面时同一操作的二进制表示形式,以进一步演示这个操作的具体过程:
11111111 11111111 11111111 11111111 -1
>>> 24
00000000 00000000 00000000 11111111 255
“>>>”运算符可能不是那么有用,因为只有对于32位和62位数值它才有意义。请记住,表达式中更小的数值会自动提升为int型。
2.5 位运算符与赋值的组合
所有二元位运算符都具有与算术运算符类似的复合形式,这些运算符将赋值运算符和位运算组合到一起。例如,下面的两行语句是等价的。都是将a的值右移4位:
a = a >> 4;
a >>= 4;
类似地,下面这两行语句也是等价的,都是将a设置位表达式“a|b”:
a = a | b;
a |= b;
下面的程序创建了几个整型变量,然后使用复合位运算符对这些变量进行操作:
public class OpBitEquals{
public static void main(String[] args){
int a = 1;
int b = 2;
int c = 3;
a |= 4;
b >>= 1;
c <<= 1;
a ^= c;
System.out.println("a = " + a);
System.out.println("b = " + b);
System.out.println("c = " + c);
}
}
编译并运行这个程序,控制台将显示图3所示的信息。
图3 OpBitEquals运行结果
IT兄弟连 Java语法教程 位运算符的更多相关文章
- IT兄弟连 Java语法教程 算符运算符
Java提供了丰富的运算符环境.可以将大部分Java运算符划分为4组:算术运算符.位运算符.关系运算符以及逻辑运算符.Java还定义了一些用于处理某些特定情况的附加运算符.本章将介绍除类型比较运算符i ...
- IT兄弟连 Java语法教程 关系运算符
关系运算符用来判定一个操作数与另外一个操作数之间的关系.特别是,它们可以判定相等和排序关系.表7中列出了关系运算符. 表7 关系运算符 关系运算符的结果为布尔值.关系运算符最常用与if语句和各种循环 ...
- IT兄弟连 Java语法教程 标识符和关键字
Java语言也和其它编程语言一样,使用标识符作为变量.对象的名字.也提供了一系列的关键字用以实现特别的功能.本小节将详细介绍Java语言的标识符和关键字等内容. 1.分隔符 Java语言里的分号“;” ...
- IT兄弟连 Java语法教程 Java的发展历程
只有少数几种编程语言对程序设计带来过根本性的影响.其中,Java的影响由于迅速和广泛而格外突出.可以毫不夸张的说,1995年Sun公司发布的Java1.0给计算机程序设计领域带来了一场变革.这场变革迅 ...
- IT兄弟连 Java语法教程 数组 经典案例
案例需求: 编程实现双色球中奖号码的生成 1)应用知识: ● 数组的声明 ● 数组的使用 ● for循环 2)需求解析: 在该程序中,需要定义一个长度为7的数组,用来存储中奖号码,使用Rando ...
- IT兄弟连 Java语法教程 数组 多维数组 二维数组的初始化
二维数组的初始化与一位数组初始化类似,同样可以使用静态初始化或动态初始化. 1)静态初始化 静态初始化的格式如下: 数组名字 = new 数组元素的类型[][]{new 数组元素的类型[]{元素1,元 ...
- IT兄弟连 Java语法教程 数组 多维数组 二维数组的声明
Java语言里提供了支持多维数组的语法.但是这里还想说,从数组底层的运行机制上来看是没有多维数组的. Java语言里的数组类型是引用类型,因此数组变量其实是一个引用,这个引用指向真实的数组内存,数组元 ...
- IT兄弟连 Java语法教程 Java语法基础 经典面试题
1.Java语言中有几种基本类型?分别是什么?请详细说明每种类型的范围以及所占的空间大小? Java语言中有8中基本类型,分别是代表整形的byte.short.int和long,代表浮点型的float ...
- IT兄弟连 Java语法教程 逻辑运算符
表8中显示的布尔逻辑运算符只能操作布尔类型的操作数,所有的二元逻辑运算符都可以组合两个布尔值,得到的结果为布尔类型. 表8 布尔逻辑运算符 布尔逻辑运算符”&“.”|“以及”^“,都会布尔值 ...
随机推荐
- vue 中的小知识点
1)使用is解决小bug <!DOCTYPE html><html lang="en"> <head> <meta charset=&qu ...
- JS时间戳与时间字符串之间的相互转换
时间字符串 转 时间戳 /** * 时间字符串 转 时间戳 * @param {String} time_str 时间字符串(格式"2014-07-10 10:21:12") * ...
- Redis与Redis 伪集群环境的搭建
一 .准备工作 GCC编译环境 ruby运行环境 安装ruby脚本运行包 二.环境安装 1.GCC环境 首先,因为redis是由C语言编写的,所以需要安装GCC环境,可以用 gcc -v 命令来检查是 ...
- laravel实现多模块
一.这里使用Caffienate Modules 网址:modules maintained by caffeinated 二.根据自己的版本选择包的版本 三.在项目composer.json文件中加 ...
- Spring Cloud Alibaba 实战(十二) - Nacos配置管理
本章主要内容是:使用Nacos管理配置以及实现配置管理的原因,配置如何管理以及动态刷新和最佳实现总结,最后是Nacos配置刷新原理解读 该技术类似于Spring Cloud Config 1 配置管理 ...
- Python 从入门到进阶之路(三)
在之前的文章我们介绍了一下 Python 中 if while for 的使用,本章我们来看一下 Python 中的变量类型. 在 Python 定义变量时的规则是 变量名 = 变量 ,Python ...
- linux中rpm和yum
一.rpm介绍 一种用于互联网下载包的打包及安装工具.它生成具有.RPM 扩展名的文件.RPM 是 RedHat Package Manager(RedHat 软件包管理工具)的缩写,类似 windo ...
- Winform中自定义添加ZedGraph右键实现设置所有Y轴刻度的上下限
场景 Winforn中实现ZedGraph自定义添加右键菜单项(附源码下载): https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/10 ...
- sed 面试题
#oldboy my qq num is 49000448.$ not 4900000448. my god ,i am not oldbey,but clsn!$ #oldboy my name i ...
- js相同的正则多次调用test()返回的值却不同
项目中文件上传需要验证文件的格式,第一次正常,第二次就验证不通过了.在验证的地方console.log()两遍,发现结果不一样 !!! 正则和文件名都没变,但是两次的验证结果不同. this.reg ...