位运算
相关概念
数据在计算机的存储与表示
java中int占4字节,1字节(Byte)=8位(bit)。可知int有32位二进制组成,如1使用int储存则为00000000 00000000 00000000 00000001。
我们知道int的范围是**-2,147,483,648(-231)**~**2,147,483,647(231 - 1)**;为什么范围最大值为2^31-1呢,首先因为int值是有正负之分的,所有int中取了第一位作为符号位,当首位二进制为0时表示当前是正值,当首位二进制为1时表示当前是负值。那么整数的最大值就是01111111 11111111 11111111 11111111就是2^30 + 2^29 + 2^28 +…+2^1 + 2^0也就是 2^31-1。 那么最小值是怎么来的呢,根据上面我们分析最大值的方法,就会自然联想到最小值为负值,一定是11111111 11111111 11111111 11111111 是- 2^31-1但是我们前面讲到了,我们最小值是- 2^31并没有减1,为什么是这种情况呢,这就需要涉及到我们java中int对数值的存储机制了。在java中int存储的是我们给定的数值的补码,并且java中是以整数的补码进行运算的。至于为什么存储补码我们后面会讲到,我们先来给大家简单介绍一下关于二进制的原码,反码,补码。
二进制数据的原码反码和补码
原码:将一个整数转换为二进制
反码:
正数的反码:与原码相同 10000100 -4原码
负数的反码:符号位不变,其它位取反 11111011 反码
补码: 11111100 补码
正数的补码:与原码相同
负数的补码:反码+1
java中负数怎么表示
前面我们说到java中存储的是整数的补码,正数的原反补相同,但是负数就需要计算出相应的补码。
例如:java中-4对应的存储结果
首先int中4的二进制为:00000000 00000000 00000000 00000100
那么-4的二进制原码为:10000000 00000000 00000000 00000100
反码为:11111111 11111111 11111111 11111011
补码为:11111111 11111111 11111111 11111100
int a = -4;
// 十进制转二进制
String i = Integer.toBinaryString(-4); //11111111 11111111 11111111 11111100
java中为什么使用补码存储计算
化减为加
由于计算中的CPU只有加法器,没有减法器,所以在计算机采用原码做减法是会存在这样的问题:对于1-1=0,看做1+(-1)=0 二进制表示 0001+1001=1010 变成了十进制的负2而不是0。出现这种情况主要是因为计算机不能很好的处理最高位的符号位,补码就是为了解决这个问题出现的。
我们先来回顾一下补码的计算:正数的运算不存在这种符号问题,所以正数的原反补相同,主要是负数的补码是原码除符号位取反再加1。那么为了方便讲解,现在使用1字节长度的数据类型byte来举例,假如有一个负数-3。原码为10000011 反码为11111100,原码与反码的关系可以很容易的看出来:10000011+01111100=11111111
现在我们来讲一个时钟的例子,根据时钟的例子来类比的讲解补码是怎么解决减法问题和负数问题的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kMU0RWfZ-1611967375318)(C:\Users\lixiaoshan\AppData\Roaming\Typora\typora-user-images\image-20210127164118157.png)]
在时钟中,我们怎么去计算时差,比如五点和四点之间的时差5-4=1,很容易就计算出来了,但是我们也可以用5+(-4)=5+8=13,后面的8就是在时钟的12为的定长中-4的补码,减12是因为5加8为13,对于定长为12的时钟来说“溢出”了,13-12就是溢出的时长。由此可知负数的补码 = 定长 - 原码的绝对值,减法运算a - b = a + (-b) = a + (-b的补码)-定长
那么根据时钟的例子我们可以延伸到我们的二进制的情况,在时钟中12既是12,也是0,那么如果我们把byte的二进制存储也想象成时钟的形式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M4niHBxV-1611967375320)(C:\Users\lixiaoshan\AppData\Roaming\Typora\typora-user-images\image-20210127165303707.png)]
我们可以从图中看到,在8位2进制的这个时钟图中,定长是11111111+1,那么假如我们仍然要计算5-4
5 - 4 = 5 + (-4) = 5 + (-4的补码) - (11111111+1 )计算机自动的帮我们获取溢出的部分所以我们就可以得到5 - 4 = 5 + (-4) = 5 + (-4的补码)
关于-128的由来
整数分为正数和负数,那么0算整数还是负数呢?根据二进制的正负数来分,以八位二进制来做例子,+0的二进制为00000000,-0的二进制为10000000。但是我们只需要一个0,于是便人为规定10000000表示-128。 而且1000 0000是-128刚好也可以满足基本的运算。如-127(1000 0001) + (-1)(1111 1111) = -128(1000 0000)。
java支持的位运算符
&:按位与
按位与的运算规则
操作数1 | 操作数2 | 按位与 |
---|---|---|
0 | 0 | 0 |
0 | 1 | 0 |
1 | 0 | 0 |
1 | 1 | 1 |
规则总结:只有两个操作数对应位同为1时,结果为1,其余全为0. (或者是只要有一个操作数为0,结果就为0)。
/*
* 10 00000000 00000000 00000000 00001010
* 12 00000000 00000000 00000000 00001100
* & 00000000 00000000 00000000 00001000 = 8
* */
System.out.println(10 & 12);
/*
* -10 10000000 00000000 00000000 00001010 原
* 11111111 11111111 11111111 11110101 反
* 11111111 11111111 11111111 11110110 补
* 12 00000000 00000000 00000000 00001100
* & 00000000 00000000 00000000 00000100 = 4
* */
System.out.println(-10 & 12);
/*
* -10 10000000 00000000 00000000 00001010 原
* 11111111 11111111 11111111 11110101 反
* 11111111 11111111 11111111 11110110 补
*
* -12 10000000 00000000 00000000 00001100 原
* 11111111 11111111 11111111 11110011 反
* 11111111 11111111 11111111 11110100 补
*
* 11111111 11111111 11111111 11110110 补
* 11111111 11111111 11111111 11110100 补
*
* & 11111111 11111111 11111111 11110100 补
* 11111111 11111111 11111111 11110011 反
* 10000000 00000000 00000000 00001100 = -12
* */
System.out.println(-10 & -12);
|:按位或
按位或的运算规则
操作数1 | 操作数2 | 按位或 |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 1 |
规则总结:只有两个操作数对应位同为0时,结果为0,其余全为1.(或者是只要有一个操作数为1,结果就为1)。
/*
* 10 00000000 00000000 00000000 00001010
* 12 00000000 00000000 00000000 00001100
* | 00000000 00000000 00000000 00001110 = 14
* */
System.out.println(10|12);
/*
* -10 10000000 00000000 00000000 00001010 原
* 11111111 11111111 11111111 11110101 反
* 11111111 11111111 11111111 11110110 补
* 12 00000000 00000000 00000000 00001100
* | 11111111 11111111 11111111 11111110 补
* 11111111 11111111 11111111 11111101 反
* 10000000 00000000 00000000 00000010 = -2
* */
System.out.println(-10 | 12);
/*
* -10 10000000 00000000 00000000 00001010 原
* 11111111 11111111 11111111 11110101 反
* 11111111 11111111 11111111 11110110 补
*
* -12 10000000 00000000 00000000 00001100 原
* 11111111 11111111 11111111 11110011 反
* 11111111 11111111 11111111 11110100 补
*
* 11111111 11111111 11111111 11110110 补
* 11111111 11111111 11111111 11110100 补
*
* | 11111111 11111111 11111111 11110110 补
* 11111111 11111111 11111111 11110101 反
* 10000000 00000000 00000000 00001010 = -10
* */
System.out.println(-10 | -12);
~:按位非
操作数 | 按位非 |
---|---|
0 | 1 |
1 | 0 |
/*
* -12 10000000 00000000 00000000 00001100 原
* 11111111 11111111 11111111 11110011 反
* 11111111 11111111 11111111 11110100 补
* ~ 00000000 00000000 00000000 00001011 = 11
* */
System.out.println(~-12);
^:按位异或
操作数1 | 操作数2 | 按位或 |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
规则总结:同0异1
/*
* -10 10000000 00000000 00000000 00001010 原
* 11111111 11111111 11111111 11110101 反
* 11111111 11111111 11111111 11110110 补
* 12 00000000 00000000 00000000 00001100
* ^ 11111111 11111111 11111111 11111010 补
* 11111111 11111111 11111111 11111001 反
* 10000000 00000000 00000000 00000110 = -6
* */
System.out.println(-10^12);
<<:左位移运算符
算术左移(>>): 左移一位,低位补0。如:2<<2结果为8。
/*
* -10 10000000 00000000 00000000 00001010 原
* 11111111 11111111 11111111 11110101 反
* 11111111 11111111 11111111 11110110 补
* 111111111 11111111 11111111 11101100
* 11111111 11111111 11111111 11101011
* 10000000 00000000 00000000 00010100
*/
System.out.println(-10<<1);
/*
* 10000000 00000000 00000000 00000001
* << 100000000 00000000 00000000 00000010 = 2
* */
System.out.println(Integer.toBinaryString(-2147483647));
System.out.println(-2147483647<<1);
/*
* 01111111 11111111 11111111 11111111
*<< 11111111 11111111 11111111 11111110
* 11111111 11111111 11111111 11111101
* 10000000 00000000 00000000 00000010 = -2
* */
System.out.println(Integer.toBinaryString(2147483647));
System.out.println(2147483647<<1);
>>:右位移运算符。
低位溢出,符号位不变,并用符号位补溢出的高位
/*
* 01111111 11111111 11111111 11111111
*>>28 00000000 00000000 00000000 00000111 = 7
* */
System.out.println(2147483647>>28);
/*
* 10000000 00000000 00000000 00000001
* >> 11000000 00000000 00000000 00000000
* 10111111 11111111 11111111 11111111
* 11000000 00000000 00000000 00000000 = -1073741824
* */
System.out.println(-2147483647>>1);
>>>:无符号右移运算符
低们溢出,高位补0
/*
* 10000000 00000000 00000000 00000001
* >> 01000000 00000000 00000000 00000000 = 1073741824
* */
System.out.println(-2147483647>>>1);
应用
力扣例题
只出现一次的数字
public static int singleNumber(int[] nums) {
int single = 0;
for (int num : nums) {
single ^= num;
}
return single;
}
public static void main(String[] args) {
int[] array=new int[]{4,1,2,1,2};
int res = singleNumber(array);
System.out.println(res);
}
}
原理:
0与任何数异或都为其本身
任何数与自己异或都为0
以8位二进制举例:例如:0^10 、0^-10 、10^10 、-10 ^ -10