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语法教程 位运算符的更多相关文章

  1. IT兄弟连 Java语法教程 算符运算符

    Java提供了丰富的运算符环境.可以将大部分Java运算符划分为4组:算术运算符.位运算符.关系运算符以及逻辑运算符.Java还定义了一些用于处理某些特定情况的附加运算符.本章将介绍除类型比较运算符i ...

  2. IT兄弟连 Java语法教程 关系运算符

    关系运算符用来判定一个操作数与另外一个操作数之间的关系.特别是,它们可以判定相等和排序关系.表7中列出了关系运算符. 表7  关系运算符 关系运算符的结果为布尔值.关系运算符最常用与if语句和各种循环 ...

  3. IT兄弟连 Java语法教程 标识符和关键字

    Java语言也和其它编程语言一样,使用标识符作为变量.对象的名字.也提供了一系列的关键字用以实现特别的功能.本小节将详细介绍Java语言的标识符和关键字等内容. 1.分隔符 Java语言里的分号“;” ...

  4. IT兄弟连 Java语法教程 Java的发展历程

    只有少数几种编程语言对程序设计带来过根本性的影响.其中,Java的影响由于迅速和广泛而格外突出.可以毫不夸张的说,1995年Sun公司发布的Java1.0给计算机程序设计领域带来了一场变革.这场变革迅 ...

  5. IT兄弟连 Java语法教程 数组 经典案例

    案例需求: 编程实现双色球中奖号码的生成 1)应用知识: ●  数组的声明 ●  数组的使用 ●  for循环 2)需求解析: 在该程序中,需要定义一个长度为7的数组,用来存储中奖号码,使用Rando ...

  6. IT兄弟连 Java语法教程 数组 多维数组 二维数组的初始化

    二维数组的初始化与一位数组初始化类似,同样可以使用静态初始化或动态初始化. 1)静态初始化 静态初始化的格式如下: 数组名字 = new 数组元素的类型[][]{new 数组元素的类型[]{元素1,元 ...

  7. IT兄弟连 Java语法教程 数组 多维数组 二维数组的声明

    Java语言里提供了支持多维数组的语法.但是这里还想说,从数组底层的运行机制上来看是没有多维数组的. Java语言里的数组类型是引用类型,因此数组变量其实是一个引用,这个引用指向真实的数组内存,数组元 ...

  8. IT兄弟连 Java语法教程 Java语法基础 经典面试题

    1.Java语言中有几种基本类型?分别是什么?请详细说明每种类型的范围以及所占的空间大小? Java语言中有8中基本类型,分别是代表整形的byte.short.int和long,代表浮点型的float ...

  9. IT兄弟连 Java语法教程 逻辑运算符

    表8中显示的布尔逻辑运算符只能操作布尔类型的操作数,所有的二元逻辑运算符都可以组合两个布尔值,得到的结果为布尔类型. 表8  布尔逻辑运算符 布尔逻辑运算符”&“.”|“以及”^“,都会布尔值 ...

随机推荐

  1. SQL Server获取索引创建时间&重建时间&重组时间

    之前写过一篇博客"SQL Server中是否可以准确获取最后一次索引重建的时间?",里面主要讲述了三个问题:我们能否找到索引的创建时间?最后一次索引重建(Index Rebuild ...

  2. Java_map的key为自定义对象

    首先自定义Key对象 import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; import java ...

  3. Linux系统学习 十六、VSFTP服务—本地用户访问—基本用户基础配置

    缺点,ftp密码是和系统密码是一致的,并不安全 先设置两个测试用户 test1      123123 test2      123123 基本用户基础配置 1.本地用户基本配置 local_enab ...

  4. Tkinter使frame填充整个区域

    在未设置默认情况下效果为 设置之后出现效果 使用方法: 增加 fill=X/Y/BOTH 以我为例: leftFrame.pack(side='left',fill=Y)

  5. 荧屏弹幕_新增h5requestAnimationFrame实现

    所有的页面逻辑也是比较简单,用原生js实现,封装也是比较简单!要让页面效果更为炫酷,则可去引入相应的css,背景图片自己去img/下下载引入喔! HTML页面 <!doctype html> ...

  6. Tomcat中的观察者模式

    1. 几个重要的类,接口 LifeCycle : 主题接口 LifeCycleBase : 抽象的主题实现 LifeCycleListener : 观察者 2. 具体分析 public interfa ...

  7. [译]ASP.NET:WebForms vs MVC

    原文示例(VS2012): 1.  Download Simple WebForm demo - 6.7 KB 2.  Download Simple MVC Demo demo - 1.5 MB 介 ...

  8. RocketMQ 升级到主从切换(DLedger、多副本)实战

    目录 1.RocketMQ DLedger 多副本即主从切换核心配置参数详解 2.搭建主从同步环境 3.主从同步集群升级到DLedger 3.1 部署架构 3.2 升级步骤 3.3 验证消息发送与消息 ...

  9. 在线程中显示一个窗口(多个UI线程)

    多数耗时操作可以异步执行,推荐async/await. 但和UI相关的部分仅能在UI线程执行,这时UI线程的耗时操作,导致界面卡死,不够友好. 我们可以创建一个单独的UI线程显示一个正在加载的窗口,可 ...

  10. vscod如何自定义 python虚拟环境

    参考文档:https://code.visualstudio.com/docs/python/environments 1.创建虚拟环境,cd到当前目录 py -3 -m venv env 2.Ctr ...