当前位置: 首页 > news >正文

位运算-详细总结

简介

位运算:直接对二进制数进行操作的运算,因为是直接对二进制数进行计算,所以相较于普通的算数运算,位运算的效率很高。

位运算通常用于计算整数,对于小数的运算则不太常见。因为整数在内存中使用补码来存储,它是线性的、固定的,小数在内存则使用浮点数来存储,浮点数包含符号位、指数位、尾数位,使用位运算来计算浮点数会很复杂并且难以理解。

内部原理

整数在内存中的存储方式

在使用位运算计算整数之前,先了解一下整数在内存中的存储方式,因为一会儿会使用位运算来操作整数。

整数在内存中使用补码来存储,补码是由原码、反码转换而来

原码、反码、补码:

  • 原码:最高位为符号位,0代表正数,1代表负数,非符号位为该数字绝对值的二进制表示。原码是一种表示有符号整数的编码方式
  • 反码:正数的反码和原码一致,负数的反码是对原码除符号位外按位取反
  • 补码:正数的补码和原码一致,负数的补码是反码加1

机器数:一个数的二进制表示形式,机器数是带符号的,0表示正数,1表示负数,机器数对应的真正的数值称为机器数的真值。原码是机器数的一种具体表示形式,用于表示有符号整数。

计算机为什么要使用补码来存储整数:为了解决整数的加减运算问题。

  • 如果计算机内部采用原码来表示数,那么在进行加法和减法运算的时候,需要转化为两个绝对值的加法和减法运算,计算机既要实现加法器,又要实现减法器,代价比较大。
  • 解决这个问题的办法就是化减为加,原理是使用计量系统中模的概念
  • 模:指一个计量系统的计数范围,是计量器产生‘溢出’的量,它的值在计量器上表示不出来,计量器只能表示出模的余数,任何有模的计量器,均可化减法为加法运算。例如时钟,它的计量范围是0-11,模是12。在以12为模的系统中,加8和减4的效果是一样的,因此凡是减4的运算,都可以用加8来代替,对模而言, 8和4互为补数,11和1、10和2也都有这个特性。对于计算机,其概念和方法完全一样。计算机中一字节8比特,计数范围是0-255,模是256,等于2的8次方,减2就相当于加上2的补数254,然后再将进位舍弃掉。根据这个原理,科学家发明了补码,负数的补码等于模减去该数的绝对值,使用补码在计算机中进行运算,可以化减法为加法。

案例1:一个数的原码、反码、补码的转换

数字 5:正数的原码、反码、补码都一样原码:0000 0101 = 5反码:0000 0101 = 5补码:0000 0101 = 5数字 -5:原码:1000 0101 = -5反码:1111 1010 = -5  // 对原码除符号位外按位取反补码:1111 1011 = -5  // 反码加 1

案例2:使用补码进行减法计算:例如,7 - 5,相当于 7 + (-5)。

      0000 0111 = 7的补码+ 1111 1011 = -5的补码----------------------------1 0000 0010 去掉溢出的模数256,结果等于2 

基本使用

位运算符:

  • 按位与:&,遇0则0
  • 按位或:|,遇1则1
  • 按位异或:^ ,相同为0,不同为1
  • 按位取反:~,一元运算符,二进制数0变1、1变0。取反操作连符号位也要进行转换
  • 左位移:<<,二进制数整体向左位移多少位,右边补0。左位移时,第一个比特位向左位移29位,现在在第30位
  • 右位移:>>,二进制数整体向右位移多少位,左边补上符号位,整数补0,负数补1
  • 无符号右位移:>>>,右移之后,无论该数为正还是为负,右移之后左边都是补上0。只有java、scala等才有无符号右位移运算。

案例1:位运算的基本使用

public static void main(String[] args) {int a = 10;System.out.println("a的二进制形式 = " + Integer.toBinaryString(a));  // 1010,这里省略了前面的0System.out.println("a & 1 = " + (a & 1));   // a和1按位与,遇0则0,结果为0System.out.println("a | 1 = " + (a | 1));   // a和1按位或,遇1则1,结果为11System.out.println("a ^ 1 = " + (a ^ 1));   // a和1按位异或,相同为0,不同为1,结果为11System.out.println("a >> 1 = " + (a >> 1)); // a右位移1位,相当于除以2,结果为5System.out.println("a << 1 = " + (a << 1)); // a左位移1位,相当于乘以2,结果为20System.out.println("a >>> 1 = " + (a >>> 1)); // a无符号右位移1位,相当于除以2,结果为5
}

案例2:位运算中的取反操作,连符号位也要进行转换

@Test
public void test1() {int x = 1;int y = ~x;System.out.println("y = " + y); // -2System.out.println("x的二进制 = " + Integer.toBinaryString(x)); // 0000 0000 0000 0000 0000 0000 0000 0001System.out.println("y的二进制 = " + Integer.toBinaryString(y)); // 1111 1111 1111 1111 1111 1111 1111 1110
}

使用场景

位运算实现乘除法

例如,向左位移一位,相当于乘以2,向右位移一位,相当于除以2。

问题:用最有效率的方法计算2乘以8?2 << 3,左移3位相当于乘以2的3次方,2的3次方就是8

二进制的取位操作

按位与通常用于二进制的取位操作,例如,一个数和1进行按位与的结果就是取二进制的最末位,这可以用来判断一个整数的奇偶。

二进制特定位上的无条件赋值

按位或,通常用于二进制特定位上的无条件赋值,例如,一个数和1进行按位或的结果,就是二进制的最末位变成1

按位异或可以用于加密

一个数被同一个数异或两次,结果等于它本身,可以用于加密数据。

使用位运算,在不使用第三个变量的前提下交换两个变量

案例:

a = a ^ b;  
b = a ^ b; // 此时b = a ^ b ^ b,a连续两次异或b,结果是a本身,所以b = a
a = a ^ b  // 此时a = a ^ b,b = a,所以,a = a ^ b ^ a,a = b

使用位运算实现正数和负数的互换

正数取反加1,变成它对应的负数,负数取反加1,变成它对应的正数,这是原码、反码、补码的转换逻辑

案例:

public static void main(String[] args) {int a = 5;int b = ~a + 1;System.out.println("b = " + b);  // -5int c = -5;int d = ~c + 1;System.out.println("d = " + d);   // 5
}

实际案例

反射中的位运算: Modifier访问修饰符

Modifier中定义了访问修饰符的枚举值和计算逻辑。

相关源码:

public class Modifier {// 每一个访问修饰符都使用一个数字来表示,这里的数字是16进制的。public static final int PUBLIC           = 0x00000001;  // 000001public static final int PRIVATE          = 0x00000002;  // 000010public static final int PROTECTED        = 0x00000004;  // 000100public static final int STATIC           = 0x00000008;  // 001000public static final int FINAL            = 0x00000010;  // 010000public static final int SYNCHRONIZED     = 0x00000020;  // 100000public static final int VOLATILE         = 0x00000040;  // 以此类推public static final int TRANSIENT        = 0x00000080;public static final int NATIVE           = 0x00000100;public static final int INTERFACE        = 0x00000200;public static final int ABSTRACT         = 0x00000400;public static final int STRICT           = 0x00000800;// 访问修饰符的计算,这里计算一个元素是否是publicpublic static boolean isPublic(int mod) {// 按位与,遇0则0,例如,如果字段的修饰符是 public static,那么它的访问修饰符是 1001,// 它和PUBLIC(0001)按位与,结果是0001,不为0,证明字段被public关键字修饰。因为按位与遇0则0,// 只有传入的访问修饰符PUBLIC位上不为0,按位与的结果才不为0return (mod & PUBLIC) != 0;   }
}

通过Modifier类,可以判断一个元素有什么访问修饰符。它的原理是,每一个访问修饰符使用一个2的倍数来表示,如果一个元素有多个访问修饰符,那么就是多个访问修饰符执行或运算,组合在一起,如果需要判断元素上有没有指定访问修饰符,就是元素的访问修饰符和修饰符常量进行与运算,结果不为0就表示元素上有该修饰符。这应该算是比较简单的位运算,很有参考价值,在设计状态值枚举的时候,尤其是一个实体可以同时拥有多种状态的情况下很有用,否则使用简单的枚举类就可以了。

相关文章:

  • 前端-Vue的项目流程
  • 【Unity】一个AssetBundle热更新的使用小例子
  • 2023年408真题及答案
  • transformer读后感
  • QT6 源(77):阅读与注释滚动条 QScrollBar 的源码,其是基类QAbstractSlider 的子类,
  • 数据库原理——E-R图的极速省流理解 例题解析
  • 如何限制pod 进程/线程数量?
  • Python基本环境搭配
  • C++ 的动态多态
  • C语言易混淆知识点详解
  • 刷leetcodehot100返航版--哈希表5/5、5/6
  • FTP/TFTP/SSH/Telnet
  • 不小心把当前的环境变量路径覆盖掉怎么办
  • 项目管理学习-CSPM(1)
  • 手表功能RunModeTasks
  • 二叉搜索树 AVL树 红黑树 的性质
  • Java——泛型
  • Stellaris 群星 [DLC 解锁] CT 表 [Steam] [Windows SteamOS macOS]
  • rvalue引用()
  • 解决Jenkis安装、配置及账号权限分配时遇到的问题
  • 牛市早报|“五一”假期预计跨区域人员流动量累计14.67亿人次
  • 贵州黔西游船发生侧翻事故,游客:事发时能见度只有一米,所乘船只停靠礁石避险
  • 魔都眼|上海环球马术冠军赛收官,英国骑手夺冠
  • 包揽金银!王宗源、郑九源夺得跳水世界杯总决赛男子3米板冠亚军
  • 美股三大指数均收涨超1%,热门中概股强势拉升
  • 看纪录片抵学分,贺桂梅:纪录片是对抗低质碎片信息的好媒介