Java位运算知识点详解

在日常的Java开发中,位运算使用的不多,使用的更多的是算数运算(+、-、*、/、%)、关系运算(<、>、<=、>=、==、!=)和逻辑运算(&&、||、!),所以相对来说对位运算不是那么熟悉,本文将以Java的位运算来详细介绍下位运算及其应用。

1、 位运算起源

位运算起源于C语言的低级操作,Java的设计初衷是嵌入到电视机顶盒内,所以这种低级操作方式被保留下来。所谓的低级操作,是因为位运算的操作对象是二进制位,但是这种低级操作对计算机而言是非常简单直接,友好高效的。在简单的低成本处理器上,通常位运算比除法快得多,比乘法快几倍,有时比加法快得多。虽然由于较长的指令流水线和其他架构设计选择,现代处理器通常执行加法和乘法的速度与位运算一样快,但由于资源使用减少,位运算通常会使用较少的功率,所以在一些Java底层算法中,巧妙的使用位运算可以大量减少运行开销。

2、 位运算详解

Java位运算细化划分可以分为按位运算和移位运算,见下表。


细化


符号


描述


运算规则


按位运算


&



两位都为1,那么结果为1


|



有一位为1,那么结果为1


~



~0 = 1,~1 = 0


^


异或


两位不相同,结果为1


移位运算


<<


左移


各二进制位全部左移N位,高位丢弃,低位补0


>>


右移


各二进制位全部右移N位,若值为正,则在高位插入 0,若值为负,则在高位插入 1


>>>


无符号右移


各二进制位全部右移N位,无论正负,都在高位插入0

在进行位运算详解之前,先来普及下计算机中数字的表示方法。对于计算机而言,万物皆0、1,所有的数字最终都会转换成0、1的表示,有3种体现形式,分别是:原码、反码和补码。

原码:原码表示法在数字前面增加了一位符号位,即最高位为符号位,正数位该位为0,负数位该位为1.比如十进制的5如果用8个二进制位来表示就是00000101,-5就是10000101。

反码:正数的反码是其本身,负数的反码在其原码的基础上,符号位不变,其余各个位取反。5的反码就是00000101,而-5的则为11111010。

补码:正数的补码是其本身,负数的补码在其原码的基础上,符号位不变,其余各位取反,最后+1。即在反码的基础上+1。5的反码就是00000101,而-5的则为11111011。

了解了这几个概念后,我们现在先记住一个结论,那就是在计算机系统中,数字一律用补码来表示、运算和存储,具体的原因可以看这篇文章的讨论,这里不做更多讨论,因为不是本文的重点。

2.1 与运算(&)

规则:转为二进制后,两位为1,则结果为1,否则结果为0。

举例:


十进制


二进制(正数原码、反码、补码一致)


10


00000000000000000000000000001010


&12


&00000000000000000000000000001100


=


=


8


00000000000000000000000000001000


十进制


二进制(原码)


-6


10000000000000000000000000000110


&-2


&10000000000000000000000000000010


十进制


二进制(反码)


-6


11111111111111111111111111111001


&-2


&11111111111111111111111111111101


十进制


二进制(补码)


-6


11111111111111111111111111111010


&-2


&11111111111111111111111111111110


=


=


-6


11111111111111111111111111111010

最后的计算结果11111111111111111111111111111010还是补码的形式,要看其十进制,还需要先转成二进制原码。

先转反码:11111111111111111111111111111010-1=11111111111111111111111111111001,得反码11111111111111111111111111111001。

再转原码:在反码的基础上转原码,符号位不变,其他各位取反,得10000000000000000000000000000110。第一位1代表负数,后面0110转成十进制是6,得-6。

2.2 或运算(|)

规则:转为二进制后,有一位为1,则结果为1,否则结果为0。

举例:


十进制


二进制(正数原码、反码、补码一致)


10


00000000000000000000000000001010


|12


|00000000000000000000000000001100


=


=


14


00000000000000000000000000001110


十进制


二进制(原码)


-6


10000000000000000000000000000110


|-2


|10000000000000000000000000000010


十进制


二进制(反码)


-6


11111111111111111111111111111001


|-2


|11111111111111111111111111111101


十进制


二进制(补码)


-6


11111111111111111111111111111010


|-2


|11111111111111111111111111111110


=


=


-2


11111111111111111111111111111110

2.3 非运算(~)

规则:转为二进制后,~0 = 1,~1 = 0。

举例:


十进制


二进制(正数原码、反码、补码一致)


~7


~00000000000000000000000000000111


=


=


-8


11111111111111111111111111111000(补码需转换为原码)

11111111111111111111111111111000-1得反码,可以把1000看成是0112,得反码

11111111111111111111111111110111。根据反码得原码10000000000000000000000000001000。


十进制


二进制(原码)


~(-6)


~10000000000000000000000000000110


十进制


二进制(反码)


~(-6)


~11111111111111111111111111111001


十进制


二进制(补码)


~(-6)


~11111111111111111111111111111010


=


=


5


00000000000000000000000000000101(正数原码、反码、补码一致)

2.4 异或运算(^)

规则:转为二进制后,两位不相同,结果为1,否则为0。

举例:


十进制


二进制(正数原码、反码、补码一致)


15^2


00000000000000000000000000001111

^00000000000000000000000000000010


=


=


13


00000000000000000000000000001101

2.5 左移运算(<<)

规则:转为二进制后,各二进制位全部左移N位,高位丢弃,低位补0。

举例:


十进制


二进制(正数原码、反码、补码一致)


2<<2


00000000000000000000000000000010


=


0000000000000000000000000000001000


8


00000000000000000000000000001000


十进制


二进制(先取补码 再对补码操作位移)


-2<<2


10000000000000000000000000000010(原码)


11111111111111111111111111111101(反码)


11111111111111111111111111111110(补码)


1111111111111111111111111111111000


11111111111111111111111111111000(补码)


11111111111111111111111111110111(反码)


-8


10000000000000000000000000001000(原码)

2.6 右移运算(>>)

规则:转为二进制后,各二进制位全部右移N位,若值为正,则在高位插入 0,若值为负,则在高位插入 1。

举例:


十进制


二进制(正数原码、反码、补码一致)


2>>2


00000000000000000000000000000010


=


0000000000000000000000000000000010


0


00000000000000000000000000000000


十进制


二进制(先取补码 再对补码操作位移)


-6>>2


10000000000000000000000000000110(原码)


11111111111111111111111111111001(反码)


11111111111111111111111111111010(补码)


1111111111111111111111111111111010


11111111111111111111111111111110(补码)


11111111111111111111111111111101(反码)


-2


10000000000000000000000000000010(原码)

2.7 无符号右移运算(>>>)

规则:转为二进制后,各二进制位全部右移N位,无论正负,都在高位插入0。

举例:


十进制


二进制(先取补码 再对补码操作位移)


-1>>>1


10000000000000000000000000000001(原码)


11111111111111111111111111111110(反码)


11111111111111111111111111111111(补码)


011111111111111111111111111111111


01111111111111111111111111111111(补码)


01111111111111111111111111111110(反码)


溢出,只能表示到int的最大值2147483647


10000000000000000000000000000001(原码)

3、 应用

3.1 不用额外的变量实现两个数字互换

见参考资料中的BitOperationTest,方法reverse通过三次异或操作完成了两个变量值的替换。

证明很简单,我们只需要明白异或运算满足下面规律(实际不止如下规律):

0^a = a,a^a = 0;

a ^ b = b ^ a;

a ^ b ^ c = a ^ (b ^ c) = (a ^ b) ^ c;

a ^ b ^ a = b;

假设a,b两个变量,经过如下步骤完成值交换:a=a^b,b=b^a,a=a^b。

证明如下:

因为a ^ b = b ^ a,又a=a^b,b=b^a。故b=b^a= b^ (a^b)=a。

继续a=a^b,a=(a^b) ^ b^ (a^b),故a=b。完成值交换。

3.2 不用判断语句实现求绝对值

公式如下:(a^(a>>31))-(a>>31)

先整理一下使用位运算取绝对值的思路:若a为正数,则不变,需要用异或0保持的特点;若a为负数,则其补码为原码翻转每一位后+1,先求其原码,补码-1后再翻转每一位,此时需要使用异或1具有翻转的特点。

任何正数右移31后只剩符号位0,最终结果为0,任何负数右移31后也只剩符号位1,溢出的31位截断,空出的31位补符号位1,最终结果为-1.右移31操作可以取得任何整数的符号位。

那么综合上面的步骤,可得到公式。a>>31取得a的符号,若a为正数,a>>31等于0,a^0=a,不变;若a为负数,a>>31等于-1 ,a^-1翻转每一位。

3.3 判断一个数的奇偶性

通过与运算判断奇偶数,伪代码如下:

n&1 == 1?”奇数”:”偶数”

奇数最低位肯定是1,而1的二进制最低位也是1,其他位都是0,所以所有奇数和1与运算结果肯定是1。

(0)

相关推荐

  • 详谈Java中的二进制及基本的位运算

    二进制是计算技术中广泛采用的一种数制.二进制数据是用0和1两个数码来表示的数.它的基数为2,进位规则是"逢二进一",借位规则是"借一当二",由18世纪德国数理哲学大师莱布尼兹发现.当前的计算机系统使用的基本上是二进制系统,数据在计算机中主要是以补码的形式存储的.计算机中的二进制则是一个非常微小的开关,用"开"来表示1,"关"来表示0. 那么Java中的二进制又是怎么样的呢?让我们一起来揭开它神秘的面纱吧. 一.Java内置的进

  • java中的移位运算符心得总结

    java中有三种移位运算符 <<      :     左移运算符,num << 1,相当于num乘以2 >>      :     右移运算符,num >> 1,相当于num除以2 >>>    :     无符号右移,忽略符号位,空位都以0补齐 下面来看看这些移位运算都是怎样使用的 复制代码 代码如下: /** *  */package com.b510.test; /** * @author Jone Hongten * @creat

  • Java中的位运算符、移位运算详细介绍

    一.位运算 Java中有4个位运算,它们的运算规则如下: (1)按位与 (&) :两位全为1,结果为1,否则为0: (2)按位或 (|) :两位有一个为1,结果为1,否则为0: (3)按位取反(~):0 变 1, 1变0: (4)按位异或(^):两位,如果相同,结果为0:如果不同,结果为1: 注意: (1)位运算中的 符号位(最高位)也跟着变; (2)位运算符与逻辑运算符(逻辑与&&.逻辑或||.逻辑非! )有点相似.但是,逻辑运算符只能操作boolean变量 (也就是左右两边的值

  • java位运算加密示例

    创建一个类,通过位运算中的"^"异或运算符把字符串与一个指定的值进行异或运算,从而改变字符串每个字符的值,这样就可以得到一个加密后的字符串.当把加密后的字符串作为程序输入内容,再与那个指定的值进行异或运算,实现把加密后的字符串还原为原有字符串的值. 复制代码 代码如下: import java.util.Scanner;public class Example {    public static void main(String[] args) {        Scanner sc

  • java中关于移位运算符的demo与总结(推荐)

    首先,移位运算符有三种,其操作类型只支持:byte / short / char / int和long五种. << 左移运算符,表示将左边的操作数的二进制数据向左移动*位,移动后空缺位以0填充,多余位舍弃.(等同于乘2的n次方) >> 右移运算符,二进制数据向右移动*位,就在其二进制数据后抹掉几位?(这里还不错定,但个人理解是这样的)(等同于除2的n次方) >>> 无符号右移运算符,不管移动前最高位是0还是1,右移后左侧产生的空位部分都以0来填充. 下面我们借一个

  • Java中位运算(移位、位与、或、异或、非) 的简单实例

    复制代码 代码如下: public class Test {     public static void main(String[] args) {         // 1.左移( << )         // 0000 0000 0000 0000 0000 0000 0000 0101 然后左移2位后,低位补0://         // 0000 0000 0000 0000 0000 0000 0001 0100 换算成10进制为20         System.out.pri

  • Java位运算和逻辑运算的区别实例

    复制代码 代码如下: public class Test {     public static void main(String[] args) {         // 逻辑运算符执行的是短路求值,当左边操作数可以推断出表达式的值,就不再执行 了         int x, y = 10;         if (((x = 0) == 0) || ((y = 20) == 20)) {             System.out.println(y);// 输出10         }

  • Java 通过位运算求一个集合的所有子集方法

    Java没有自带的求一个集合的所有子集的方法,我们可以通过集合的子集规律来求. 一个集合的所有子集等于2^该集合的长度.比如{c,b,a}的长度为3,这个集合的子集就有8个. 这句话看起来很简单,但同时也隐含着高深的哲理.其实一个集合的所有集合,和2^该集合的长度这个数字有关.比如上面的例子,{c,b,a}的长度为3,则可以用0-7表示其所有子集.如下所示,改数字所对应的位置为1,则说明我需要这个数字形成子集.从0-7的二进制表示,刚好代表完,一个长度为3,子集个数为8的所有子集. 0(000)

  • Java位运算知识点详解

    在日常的Java开发中,位运算使用的不多,使用的更多的是算数运算(+.-.*./.%).关系运算(<.>.<=.>=.==.!=)和逻辑运算(&&.||.!),所以相对来说对位运算不是那么熟悉,本文将以Java的位运算来详细介绍下位运算及其应用. 1. 位运算起源 位运算起源于C语言的低级操作,Java的设计初衷是嵌入到电视机顶盒内,所以这种低级操作方式被保留下来.所谓的低级操作,是因为位运算的操作对象是二进制位,但是这种低级操作对计算机而言是非常简单直接,友好高效

  • java super关键字知识点详解

    在对一些父类的调用上,我们需要借助java中的关键字使用,这就是super关键字,可以归纳为三种方法:作用于类.方法名和构造函数.可以把它看做是使用父类的一个工具,相信大家在之前类的使用中有所接触.下面我们就super的说明.三种用法.注意事项进行说明,然后在实例中体会用法. 1.说明 super相当于是指代当前的对象的父类,调用父类的属性.方法和构造方法 2.用法 (1)super.变量/对象名: 使用这种方法可以直接访问父类中的变量或对象,进行修改赋值等操作 (2)super.方法名():

  • java伪泛型知识点详解

    说明 1.Java中的泛型是伪泛型.这种泛型实现方法称为类型擦除 ,基于这种方法实现的泛型称为伪泛型. 2.由于Java的泛型只在编译阶段发挥作用,因此在写代码时,起到了检查的作用,当代码运行时,它的内部并没有泛型. 实例 List<String> l1 = new ArrayList<String>(); List<Integer> l2 = new ArrayList<Integer>(); System.out.println(l1.getClass(

  • Java字符串编码知识点详解介绍

    在 Java 中,当我们处理String时,有时需要将字符串编码为特定字符集.编码是一种将数据从一种格式转换为另一种格式的方法.字符串对象使用 UTF-16 编码.UTF-16 的问题在于它不能被修改.只有一种方法可以用来获得不同的编码,即 byte[] 数组.如果我们得到意外的数据,编码的方式是不合适的.在本节中,我们将学习如何在Java中对字符串进行编码. 在继续本节之前,我们必须了解字符编码.让我们快速浏览一下.让我们了解为什么我们需要对字符串进行编码. 字符编码是一种将文本数据转换为二进

  • java二进制运算基础知识点详解

    一.二进制位运算 1. 按位与(&) 位运算实质是将参与运算的数字转换为二进制,而后逐位对应进行运算. 按位与运算为:两位全为1,结果为1,即1&1=1,1&0=0,0&1=0,0&0=0. 例如51 & 5 -> 00110011 & 00000101 = 00000001 -> 51 & 5 = 1 特殊用法: (1)与0相与可清零. (2)与1相与可保留原值,可从一个数中取某些位.例如需要取10101110中的低四位,101

  • Java单例模式的知识点详解

    懒汉模式与饿汉模式 懒汉模式就是懒加载,用到的时候去加载,存在线程安全问题,需要手动地加锁控制.它的优点是类加载的速度比较快,按需加载,节省资源. 饿汉模式就是在类加载的时候会创建出实例.它天生就不存在线程安全问题.但是类加载的速度会变慢且耗费资源. 懒汉模式-单重检查 示例代码如下: public class LazySingleton { private static LazySingleton singletoninstance = null; private Object data =

  • 基于Java语言的递归运算例题详解

    目录 一.实例演示:递归求N的阶乘 二. 递归调用练习 递归求1+2+3+……10的和 顺序打印一个数字的每一位 返回一个数组成本身的数字之和 求解汉诺塔问题 求斐波那契数列第N项 递归定义:一个方法在执行过程中调用自身, 就称为 "递归". 递归的必要条件: 1. 将原问题划分成其子问题,注意:子问题必须要与原问题的解法相同. 2. 递归出口. 一.实例演示:递归求N的阶乘 public class fac { public static int factorial(int x){

  • 基于java集合中的一些易混淆的知识点(详解)

    (一) collection和collections 这两者均位于java.util包下,不同的是: collection是一个集合接口,有ListSet等常见的子接口,是集合框架图的第一个节点,,提供了对集合对象进行基本操作的一系列方法. 常见的方法有: boolean add(E e) 往容器中添加元素:int size() 返回collection的元素数:boolean isEmpty() 判断此容器是否为空: boolean contains(Object o) 如果此collecti

  • JAVA位运算的知识点总结

    一.在计算机中数据是如何进行计算的? 1.1:java中的byte型数据取值范围 我们最开始学习java的时候知道,byte类型的数据占了8个bit位,每个位上或0或1,左边第一位表示符号位,符号位如果为1表示负数,为0则表示正数,因此要推算byte的取值范围,只需要让数值位每一位上都等于1即可. 我们来用我们的常规思维来分析下byte类型的取值范围: 图1 如果按照这种思路来推算,七个1的二进制数转换为十进制是127,算上符号位,取值范围应为:-127~+127,但事实上我们知道,byte的取

  • java.lang.Runtime.exec() Payload知识点详解

    有时,通过Runtime.getRuntime().exec()执行命令的有效负载有时会失败.使用Web Shell,反序列化利用或通过其他媒介时,可能会发生这种情况. 有时这是因为重定向和管道字符的使用在启动过程的上下文中没有意义.例如,在shell中执行ls> dir_listing会将当前目录的列表输出到名为dir_listing的文件中.但是在exec()函数的上下文中,该命令将被解释为获取>和dir_listing目录的列表. 有时,StringTokenizer类会破坏其中包含空格

随机推荐