位运算的运用场景使用总结
Posted 小羊子说
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了位运算的运用场景使用总结相关的知识,希望对你有一定的参考价值。
位运算的使用场景总结
文章目录
前言
本文总结了位运算在算法、源码、面试中和android中的运用场景使用。不足之处,欢迎指正。
位运算相关概念
什么是位运算 ?
计算机在底层使用的是二进制补码进行运算。对应的二进制位进行操作,计算机只识别0和1。
位运算的好处
巧妙的使用位运算可以大量减少运行开销,优化算法。
位运算快的原因是直接跟计算机的底层二进制机器操作指令,而我们的程序代码运算最终也是由 JVM转换成计算机可执行的二进制操作指令,位运算省略了中间转换的操作,处理器直接操作所以更快。
bit 和byte
-
bit指“位‘,是数据传输速度的计量单位,常简写为“b”;Byte指“字节”,是文件大小的计量单位,常简写为“B”。
-
Byte和bit的换算关系是,1 Byte=8 bits。在电脑上,一个英文字母需要占用1 Byte的硬盘空间,一个汉字则需占用2 Byte。
例如,在我们java语言中,一个int 占4 byte,也就是占32bit。
原码
将一个数字转换成二进制(机器数)就是这个数值。
反码
反码的表示方法是:正数的反码是其本身;负数的反码是在其原码的基础上, 符号位不变,其余各个位取反。
补码
补码的表示方法是:正数的补码就是其本身;负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1。 (即在反码的基础上+1)
十进制原数 | 原码 | 反码 | 补码 |
---|---|---|---|
10 | 0000 1010 | 0000 1010 | 0000 1010 |
-10 | 1000 1010 | 1111 0101 | 1111 0110 |
5 | 0000 0101 | 0000 0101 | 0000 0101 |
-5 | 1000 0101 | 1111 1010 | 1111 1011 |
设计意义
简化了计算机的设计,计算机只能进行加法运算,通过补码的设计,使之可以在这种设计下,进行减法运算。
比如 1-1 在计算机中执行的 实际上是 1 +(-1) 即补码运算,也就是说,所有计算都是使用该数的补码,计算完成以后再换回源码。
位运算基础
-
位运算是针对整数的二进制进行的位移操作
-
整数 32位(4个字节) , 正数符号为0,负数符号为1。十进制转二进制 不足32位的,最高位补符号位,其余补零
-
在Java中,整数的二进制是以补码的形式存在的
-
位运算计算完,还是补码的形式,要转成原码,再得出十进制值
-
正数:原码 = 反码=补码 负数:反码=原码忽略符号位取反, 补码=反码+1
例如:十进制4 转二进制在计算机中表示为(补码) 00000000 00000000 00000000 00000100
例如:十进制-4 转二进制在计算机中表示为(补码) 11111111 11111111 11111111 11111100
Java支持的7个位运算符
- &:与运算符
- |:或运算符
- ~:非运算符
- ^: 异或运算符
>>
:右移运算符<<
:左运算符>>>
:无符号右运算符
运算符如何使用
位移操作:(只针对int类型的有效,Java中,一个int的长度始终是32位,也就是4个字节,它操作的都是该整数的二进制数).也可作用于以下类型,即 byte,short,char,long(它们都是整数形式)。当为这四种类型时,JVM先把它们转换成int型再进行操作。
-
与(&)运算符,两个都为1时才为1,其他情况均为0。 例如:-5 & 4 =0
-5的二进制形式为:11111111 11111111 11111111 11111011
4的二进制形式为:00000000 00000000 00000000 00000100
进行逻辑运算后为:00000000 00000000 00000000 00000000
转换为十进制为:0
-
或(|)运算符,两个都为0时才为0,其他情况均为1。 例如:5 | 4 = -1
-
非(~)运算符,取反,即1变为0,0变为1。 ~(-5)=4
-
异或(^)运算符,相同值为0,不同值为1。 -5^4 = -1
-
右移(>>)运算符,m>>n,把m的二进制数右移n位,m为正数,高位全部补0,m为负数,高位全部补1。例如:5>>2 =1
-
左移(<<)运算符,m<<n,把m的二进制数左移n位,高位超出n位都舍弃,低位补0(此时可能出现正数变负数。 5<<2 =20
-
无符号右移(>>>)运算符,m>>>n,整数m表示的二进制右移n位,不论正负数,高位都补0
基础介绍就到这,下面介绍位运算的使用场景。考察内功的时候到了。
使用场景
1.判断奇偶数
常规做法:
if( n % 2) == 1
// n 是奇数
采用(&)与运算,效率直接提高。
if(n & 1 == 1) //(n&1 == 0)//n是偶数
//n是奇数
原理分析:与(&)运算 两个都为1时才为1,其他情况均为0。
2. 交换两个数
常规的使用是引入额外的变量来完成交换。
private void swap(int x, int y)
int temp = x;
x = y;
y = temp;
在面试中这个经常会做为考点,来考察内功。比如:不允许你使用额外的辅助变量来完成交换呢?
于是,用异或来完成。
private void swap(int x, int y)
x = x ^ y // (1)
y = x ^ y // (2)
x = x ^ y // (3)
//或者
private void swap2(int x, int y)
x ^= y;
y ^= x;
x ^= y;
原理分析:异或(^)运算符,相同值为0,不同值为1
把(1)中的 x 带入 (2)中的 x,有:
y = x^y = (x^)^y = x^(y^y) = x^0 = x
。 x 的值成功赋给了 y。
对于(3),推导如下:
x = x^y = (x^y)^x = (x^x)^y = 0^y = y
。
3.找出没有重复的数
给你一组整型数据,这些数据中,其中有一个数只出现了一次,其他的数都出现了两次,让你来找出一个数 。
这道题可能很多人会用一个哈希表来存储,每次存储的时候,记录 某个数出现的次数,最后再遍历哈希表,看看哪个数只出现了一次。这种方法的时间复杂度为 O(n),空间复杂度也为 O(n)了。
然而我想告诉你的是,采用位运算来做,绝对高逼格!
两个相同的数异或的结果是 0,一个数和 0 异或的结果是它本身,所以我们把这一组整型全部异或一下,
例如这组数据是:1, 2, 3, 4, 5, 1, 2, 3, 4。其中 5 只出现了一次,其他都出现了两次,把他们全部异或一下,结果如下:
由于异或支持交换律和结合律,所以:
1^2^3^4^5^1^2^3^4 = (1^1)^(2^2)^(3^3)^(4^4)^5= 0^0^0^0^5 = 5
int find(int[] arr)
int tmp = arr[0];
for(int i = 1;i < arr.length; i++)
tmp = tmp ^ arr[i];
return tmp;
时间复杂度为 O(n),空间复杂度为 O(1),而且看起来很牛逼。
4. m的n次方
如果让你求解 m 的 n 次方,并且不能使用系统自带的 pow 函数(Math.pow(底数,几次方)),你会怎么做呢?
思路1:
int pow(int n)
int tmp = 1;
for(int i = 1; i <= n; i++)
tmp = tmp * m;
return tmp;
时间复杂度为 O(n)
我举个例子吧,例如 n = 13,则 n 的二进制表示为 1101, 那么 m 的 13 次方可以拆解为:
m^1101 = m^0001 * m^0100 * m^1000。
我们可以通过 & 1和 >>1 来逐位读取 1101,为1时将该位代表的乘数累乘到最终结果。直接看代码吧,反而容易理解:
int pow(int n)
int sum = 1;
int tmp = m;
while(n != 0)
if(n & 1 == 1)
sum *= tmp;
tmp *= tmp;
n = n >> 1;
return sum;
时间复杂度近为 O(logn),而且看起来很牛逼。
5.求绝对值
private int abs(int x)
int y;
y = x >> 31;
// return (x ^ y) - y;
return (x +y)^y;
等价于:Math.abs()
6.取模运算
a % (2^n) 等价于 a & (2^n - 1)
System.out.println(4 & (2 ^ 2 - 1));//结果为0
System.out.println(4 % 4); //结果为0
7. 乘法运算
a * (2^n) 等价于 a << n
8.除法运算转化成位运算
a / (2^n) 等价于 a>> n
System.out.println(4 << 2); //4乘以2^2 = 4X4=16
System.out.println(4 >> 2); //4除以2^2 =4除以4=1
9.求相反数
(~x+1)
System.out.println(~7 + 1); //结果为:-7
10. HashMap中哈希算法的使用
//重新计算哈希值
static final int hash(Object key)
int h;
//key如果是null 新hashcode是0 否则 计算新的hashcode
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
- ^按位异或运算,只要位不同结果为1,不然结果为0;
>>>
无符号右移:右边补0
11.经典面试题
问题:有1000个一模一样的瓶子,其中有999瓶是普通的水,有一瓶是毒药。任何喝下毒药的生物都会在一星期之后死亡。现在有一些老鼠(无穷),给你一个星期时间,问最少需要几只老鼠可以找出这瓶有毒药的水?
解答:2 ^ 10 = 1024 > 1000,所以,最少需要10个老鼠。
将1-1000表示为二进制形式:
1234 5678
1 0000 0001
2 0000 0010
3 0000 0011
4 0000 0100
5 0000 0101
6 0000 0110
7 0000 0111
8 0000 1000
9 0000 1001
10 0000 1010
11 0000 1011
12 0000 1100
13 0000 1101
14 0000 1110
15 0000 1111
……
如上所示:1号老鼠喝第一位为1的水
2号老鼠喝第二位为1的水
3号老鼠喝第三位为1的水
4号老鼠喝第四位为1的水
5号老鼠喝第五位为1的水
6号老鼠喝第六位为1的水
7号老鼠喝第七位为1的水
8号老鼠喝第八位为1的水
9号老鼠喝第九位为1的水
10号老鼠喝第十位为1的水
如果i号老鼠死了,则第i位为1。否则,为0。最后得到的数字即为所求。
扩展:如果你有两个星期的时间(换句话说你可以做两轮实验),为了从1000个瓶子中找出毒药,你最少需要几只老鼠?注意,在第一轮实验中死掉的老鼠,就无法继续参与第二轮实验了。
提示:用三进制数表示即可。
12.Android中项目中使用场景
MeasureSpec
MeasureSpec
代表一个32位int值,高2位代表SpecMode
,低30位代表SpecSize
,SpecMode
是指测量模式,而SpecSize
是指在某种测量模式下的规格大小。(引用自《Android开发艺术探索》)
在这个类里面,一开始就声明了两个基本变量以及不同测量模式的值,已经用到了位运算中的左移。
private static final int MODE_SHIFT = 30;
// 声明位移量
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
// 后期截取SpecMode或SpecSize时使用的变量
// 3对应的二进制是11,左移30位后,int值的前2位就都是1,后30位为0
public staic final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
// 三种测量模式对应的值
//获取 MeasureSpec
public static int makeMeasureSpec(int size, int mode)
// API 17 之前,忽略此条件
if (sUseBrokenMakeMeasureSpec)
return size + mode;
else
return (size & ~MODE_MASK) | (mode & MODE_MASK);
//获取 SpecMode
public static int getMode(int measureSpec)
return (measureSpec & MODE_MASK);
//获取 SpecSize
public static int getSize(int measureSpec)
return (measureSpec & ~MODE_MASK);
原理分析:
在 MeasureSpec 类中,getMode 方法是将参数 measureSpec 与 MODE_MASK 进行与运算,MODE_MASK 可以理解为 SpecMode 的掩码,运算的结果是保留measureSpec 的高两位,剩下的后 30 位置 0,得到的是 MeasureMode。
getSize 方法是先将 MODE_MASK 取反再跟 measureSpec 进行与运算,结果是高两位为 0 低 30 位不变的值,即 SpecSize。
makeMeasureSpec 方法中,size & ~MODE_MASK 的结果是 size 的 SpecSize,mode & MODE_MASK 的结果是 SpecMode,将他们进行或操作,得到的就是是两者的叠加值。
其他运用场景,后期继续完善~
参考:
以上是关于位运算的运用场景使用总结的主要内容,如果未能解决你的问题,请参考以下文章