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. MySQL语句使用。

    目录 MySQL的DDL.DML.DQL语句和单表增.删.改.查 实验准备: 实验开始: DDL语句 DML语句 DQL语句 单表操作的分组统计 MySQL的DDL.DML.DQL语句和单表增.删.改 ...

  2. nginx(4)

    目录 一.安装配置 1.安装 2.配置文件 3.测试和启动 二.功能 1.虚拟主机 1.1 基于IP 1.2 基于域名 1.3 基于端口 2.访问控制 3.用户认证 4.文件共享 5.文件别名 6.状 ...

  3. 【转】开发一个这样的 APP 要多长时间?

    作者:蒋国刚 www.cnblogs.com/guogangj/p/4676836.html 呵呵. 这是一个“如有雷同,纯属巧合”的故事,外加一些废话,大家请勿对号入座.开始了…… 我有些尴尬地拿着 ...

  4. Linux配置svn服务器版本库

    1)创建版本库首先使用yum安装subversion 2)创建版本库 svnadmin create /home/svn/svnfile 3)进入conf目录 authz               ...

  5. go语言的json

    简介 json 中提供的处理 json 的标准包是 encoding/json,主要使用的是以下两个方法: // 序列化 func Marshal(v interface{}) ([]byte, er ...

  6. ASP.NET中的身份验证

    身份验证方式windows passport form none授权allow 允许deny 拒绝特殊符号 *代表所有用户 ?代表匿名用户跳转配置<system.web><autho ...

  7. C#爬虫例子

    公司需要抓取新闻,每次手动复制粘贴新闻,太麻烦了,业务人员就提出了要求,需要程序实现自动抓取新闻,因此就写了这个简单的爬虫程序. Html Agility Pack库 这是一个.NET下的HTML解析 ...

  8. python-pyppeteer模块使用汇总

    一.简单代码示例 import asyncio from pyppeteer import launch async def main(): browser = await launch() page ...

  9. 松软科技web课堂:SQLServer之NOW() 函数

    NOW() 函数 NOW 函数返回当前的日期和时间. 提示:如果您在使用 Sql Server 数据库,请使用 getdate() 函数来获得当前的日期时间. SQL NOW() 语法 SELECT ...

  10. Data Management Technology(5) -- Recovery

    Recovery Types of Failures Wrong data entry Prevent by having constraints in the database Fix with d ...