Java位运算符及相关问题

位运算符

Posted by Sunfy on 2021-09-23
Words 2.3k and Reading Time 9 Minutes
Viewed Times
Viewed Times
Visitors In Total

一、位运算符

Java中支持的位运算符

&:按位与

操作数1 操作数2 &
0 0 0
0 1 0
1 0 0
1 1 1

总结:只有两个操作数都为1时,按位与操作的结果才为1,否则为0。(有0结果就为0)

|:按位或

操作数1 操作数2 \
0 0 0
0 1 1
1 0 1
1 1 1

总结:只要两个操作数中有一个为1,按位或的结果就为1,否则为0。(有1结果就为1)

~:按位非

操作数 ~
0 1
1 0
操作数1 操作数2 ^
0 0 0
0 1 1
1 0 1
1 1 0

总结:两个操作数相异则为1,相同则为0。

<<:左移

符号位不变,低位补0.

image-20220324140131742

>>: 右移

低位溢出,符号位不变,高位溢出补符号位.

image-20220324140255497

>>>: 无符号右移

低位溢出,高位补0。无符号为的意思就是把符号位当作数字位看待.

image-20220324140346226

二、使用位运算符解决问题

1、求一个整数的二进制表示中1的个数

最常用的方法就是/2 ,%2法,其实我们还可以通过位运算符>>>和&来解决这个问题

1
2
我们知道&运算符,只有在两个操作数都是1的情况下结果才为1.那么我们可以让该数>>>(无符号右移) 同时 &上1
只要>>>后的数不为0,就一直& 1
1
2
3
4
5
6
7
8
9
10
public static void fun(int n) {
int count = 0;
while(n != 0) {
if((n & 1) != 0) {
count++;
}
n = n >>> 1; //>>>无符号右移,避免死循环
}
System.out.println(count);
}

上述方法并不是最优,我们还可以用另外一种方法:

1
2
3
4
5
6
7
8
public static void fun(int n) {
int count = 0;
while(n != 0) {
count++;
n = n & (n - 1); //一个整数n,每次 n & (n - 1) 就会少一个 1
}
System.out.peintln(count);
}

2、分别输出一个整数二进制表示的奇数位序列和偶数位序列

1
2
3
4
5
6
7
8
public static void fun(int n) {
for(int i = 31;i >= 0;i -= 2) {
System.out.println(((n >> i) & 1) + " ");
}
for(int i = 30;i >= 0;i -= 2) {
System.out.peintln(((n >> i) & 1 ) + " ");
}
}

3、一组数据中,有一个数据只出现了一次,其他数据都出现了两次,求只出现一次的数据是哪个

1
2
3
4
5
6
7
public static void fun(int[] array) {
int res = 0;
for(int i = 0;i < array.length;i++) {
res = res ^ array[i];
}
System.out.peintln(res);
}

三、位运算符与加减运算符之间的关系及实际运用。

关系:位运算符比加减运算符运算速度更快。一般能用位运算的就用位运算符。

实际运用:

实例1:输入一个整数,输出该数二进制表示中1的个数,如:9的二进制表示是1001,有2位是1,因此如果输入9,该函数输出2.

输入 二进制表示 输出
9 0000 0000 0000 0000 0000 0000 0000 1001 2

第一种思路:使用 >> 运算符,将数据右移,不断与数字 1做与 & 运算,若与运算结果为1,则该位置二进制表示为1,否则为0;

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Test {
public static void main(String[] args)
{
int a=9;
System.out.println("数字 "+a+" 在内存中得二进制表示为:"+Integer.toBinaryString(a)+
" 二进制表示法包含的位数是:"+ getNumOf1(a));
}
public static int getNumOf1(int n)
{
int number=0;
while(n!=0)
{
if ((n&1)==1)
number++;
n=n>>1;
}
return number;
}
}

程序执行结果:

1
2
数字 9 在内存中得二进制表示为:1001  二进制表示法包含的位数是:2
Process finished with exit code 0

问题:
存在问题,移位运算中,当n时正数时,右移的话,最左面补0
正值>> 左面补0 负值>> 左面补1 正负<< 右面补0

在本段代码中,当a是负数时,右移的话,最左面补位补的是1,一直右移就一直补1.会造成死循环 (1000)>>(1100)>>(1110)>>(1111)>>…>>1111。思考:有没有什么方法,让正负数 在 >> 操作时,左面均补位0 ? 答案: >>> 操作。

另外,如果我们首先判断数据是否是负值,负值的话,将数据变为相反数,即正值再判断1的个数,这种方法是不可取的,因为在内存中数据存储的是补码。不是源码。我们判断的是数据的补码中 1 出现的次数。将数据变为相反数后,数据在内存中表示,不止符号位发生变化,数据位也发生变化。

第二种思路:使用 >>> 运算符,将数据右移,不断与数字 1做与 & 运算,若与运算结果为1,则该位置二进制表示为1,否则为0;

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Test {
public static void main(String[] args)
{
int a=9;
int b=-9;
System.out.println("数字 "+a+" 在内存中得二进制表示为:"+Integer.toBinaryString(a)+
" 二进制表示法包含的位数是:"+ getNumOf1(a));
System.out.println("数字 "+b+" 在内存中得二进制表示为:"+Integer.toBinaryString(b)+
" 二进制表示法包含的位数是:"+ getNumOf1(b));
}
public static int getNumOf1(int n)
{
int number=0;
while(n!=0)
{
if ((n&1)==1)
number++;
n=n>>>1;
}
return number;
}
}

程序执行结果:

1
2
3
数字 9 在内存中得二进制表示为:1001  二进制表示法包含的位数是:2
数字 -9 在内存中得二进制表示为:11111111111111111111111111110111 二进制表示法包含的位数是:31
Process finished with exit code 0

第三种思路:使用 << 运算符,flag=1,将flag左移,不断与数字 n做与 & 运算,若与运算结果不为0,则该位置二进制表示为1,否则为0直至flag为0;

这里红色字体:与运算结果不为0。与运算结果情况有两种

  1. 与数据位 & ,结果大于0;

  2. 与符号位 & ,若数据是负数,即符号位为1,则与结果- 2147483648(写个实例看看为何是这个数字),即: 是负数 ,若数据是正数,即符号位为0,则与结果为0。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Test {
public static void main(String[] args)
{
int a=9;
int b=-9;
System.out.println("数字 "+a+" 在内存中得二进制表示为:"+Integer.toBinaryString(a)+
" 二进制表示法包含的位数是:"+ getNumOf1(a));
System.out.println("数字 "+b+" 在内存中得二进制表示为:"+Integer.toBinaryString(b)+
" 二进制表示法包含的位数是:"+ getNumOf1(b));
}
public static int getNumOf1(int n)
{
int flag=1;
int number=0;
while(flag!=0)
{
if((flag&n) != 0)
number++;
flag=flag<<1;
}
return number;
}
}

程序执行结果:

1
2
3
数字 9 在内存中得二进制表示为:1001  二进制表示法包含的位数是:2
数字 -9 在内存中得二进制表示为:11111111111111111111111111110111 二进制表示法包含的位数是:31
Process finished with exit code 0

第四种思路:有没有什么方法,只在1出现时计算,0时不计算?

对于任意数据n,n-1操作是将n的二进制表示中,最右面的1变为0,且最右面1的后面部分全取反。n=n & (n-1),则是将n的二进制表示中,最右面的1的后面全变为0。利用这一特性,统计二进制表示中1的个数。

n n-1 n=n & (n-1) 二进制表示中1出现的次数
11 - 0001 0011 10 - 0001 0010 10 - 0001 0010 1
10 - 0001 0010 9 - 0001 0001 8 - 0001 0000 2
8 - 0001 0000 7- 0000 1111 0 - 0000 0000 3
0 - 0000 0000 结束 3

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Test {
public static void main(String[] args)
{
int a=9;
int b=-9;
System.out.println("数字 "+a+" 在内存中得二进制表示为:"+Integer.toBinaryString(a)+
" 二进制表示法包含的位数是:"+ getNumOf1(a));
System.out.println("数字 "+b+" 在内存中得二进制表示为:"+Integer.toBinaryString(b)+
" 二进制表示法包含的位数是:"+ getNumOf1(b));
}
public static int getNumOf1(int n)
{
int number=0;
while(n!=0)
{
number++;
n=n&(n-1);
}
return number;
}
}

程序执行结果:

1
2
3
数字 9 在内存中得二进制表示为:1001  二进制表示法包含的位数是:2
数字 -9 在内存中得二进制表示为:11111111111111111111111111110111 二进制表示法包含的位数是:31
Process finished with exit code 0

其他应用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**用一条语句判断一个整数是不是2的整数次方 */
public static boolean is2(int number)
{
if((number&(number-1))==0)
return true;
return false;
}
/**判断两个整数m和n,计算需要改变m的二进制表示中的多少位才能得到n */
public static int Getn(int m,int n)
{
int dif=m^n;//一共有多少不同的位
//求dif中的1的位置
int number=0;
while(dif!=0)
{
number++;
dif=dif&(dif-1);
}
return number;
}
/** 题目:不使用加减乘除,完成两个数字的加法操作*/
public static int add(int num1, int num2) {
int sum = 0;
while (num2 != 0) {
sum = (num1 ^ num2);
num2 = (num1 & num2) << 1;
num1 = sum;
}
return num1;
}
/** n除以2 n/2 ,只取整数部分 (n是正数情况下成立) */
n>>1;
/** n乘以2 n/2 (正负数均成立)*/
n<<1;

Copyright 2021 sunfy.top ALL Rights Reserved

...

...

00:00
00:00