前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java的二进制位操作整理 顶

Java的二进制位操作整理 顶

作者头像
算法之名
发布2019-08-20 10:04:26
7310
发布2019-08-20 10:04:26
举报
文章被收录于专栏:算法之名算法之名

由于 Java 是跨平台语言,所以 JVM 表现下的基础数据字节长度其实都是一致的。

int:4 个字节。 (1个字节是8位)

short:2 个字节。

long:8 个字节。

byte:1 个字节。

float:4 个字节。

double:8 个字节。

char:2 个字节。

boolean:boolean属于布尔类型,在存储的时候不使用字节,仅仅使用 1 位来存储,范围仅仅为0和1,其字面量为true和false。

原码 反码 补码 我们已经知道了一个 int 型数值是 4 个字节。每个字节有 8 位。但对于一个 int 或者其它整数类型如 (long)的数值而言还要注意的是,它的最高位是符号位。

最高位为0表示正数。 最高位为1表示负数 原码 将一个数字转换成二进制就是这个数值的原码。 int a = 5; //原码 0000 0000 0000 0000 0000 0000 0000 0101

int b = -3; //原码 1000 0000 0000 0000 0000 0000 0000 0011

反码 分两种情况:正数和负数

正数 正数的反码就是原码。 负数 负数的反码是在原码的基础上,符号位不变 其它位都取反。 5 的原码:0000 0000 0000 0000 0000 0000 0000 0101

-3 的原码:1000 0000 0000 0000 0000 0000 0000 0011 -3 的反码:1111 1111 1111 1111 1111 1111 1111 1100

补码 仍然分正数和负数两种情况

正数 正数的补码就是原码。 负数 负数的补码在反码的基础上加1。 5 的补码:0000 0000 0000 0000 0000 0000 0000 0101

-3 的反码:1111 1111 1111 1111 1111 1111 1111 1100 -3 的补码: 1111 1111 1111 1111 1111 1111 1111 1101 计算机在进行数值运算的时候,是通过补码表示每个数值的。

通过程序运行可以验证

代码语言:javascript
复制
public class Code {
    public static void main(String[] args) {
        int a = 5;
        int b = -3;
        String formata = String.format("%032d", Integer.parseInt(Integer.toBinaryString(a)));
        System.out.println(formata);
        System.out.println(Integer.toBinaryString(b));
    }
}

运行结果

00000000000000000000000000000101 11111111111111111111111111111101

如果a和b相加

100000000000000000000000000000010 此处为33位,溢出,第一位1放弃,变为

00000000000000000000000000000010 刚好为2, 5-3=2 没毛病。

程序验证

代码语言:javascript
复制
public class Code {
    public static void main(String[] args) {
        int a = 5;
        int b = -3;
        String formata = String.format("%032d", Integer.parseInt(Integer.toBinaryString(a)));
        System.out.println(formata);
        System.out.println(Integer.toBinaryString(b));
        System.out.println(String.format("%032d",Integer.parseInt(Integer.toBinaryString(a + b))));
    }
}

运行结果

00000000000000000000000000000101 11111111111111111111111111111101 00000000000000000000000000000010

位运算符 &、|、~、^、>>、<<、>>>

& 与运算符 规则 与运算时,进行运算的两个数,从最低位到最高位,一一对应。如果某 bit 的两个数值对应的值都是 1,则结果值相应的 bit 就是 1,否则为 0.

0 & 0 = 0,

0 & 1 = 0,

1 & 1 = 1 3 & 5 = 1 这是因为 (高位0省略)

0000 0011

&

0000 0101

=

0000 0001 按照规则,将两个数值按照低位到高位一一对齐运算,因为只有第 0 位都为 1,所以计算结果为 1.

程序验证

代码语言:javascript
复制
public class Code {
    public static void main(String[] args) {
        int a = 5;
        int b = 3;
        String formata = String.format("%032d", Integer.parseInt(Integer.toBinaryString(a & b)));
        System.out.println(formata);
        System.out.println(a & b);
    }
}

运行结果

00000000000000000000000000000001 1

判断一个数的奇偶性可以对1取与运算,结果为1为奇数,0为偶数

| 或运算符 规则 或运算时,进行运算的两个数,从最低位到最高位,一一对应。如果某 bit 的两个数值对应的值只要 1 个为 1,则结果值相应的 bit 就是 1,否则为 0。

0 | 0 = 0,

0 | 1 = 1,

1 | 1 = 1 3 | 5 = 7 这是因为 (高位省略)

0000 0011

|

0000 0101

=

0000 0111

程序验证

代码语言:javascript
复制
public class Code {
    public static void main(String[] args) {
        int a = 5;
        int b = 3;
        String formata = String.format("%032d", Integer.parseInt(Integer.toBinaryString(a | b)));
        System.out.println(formata);
        System.out.println(a | b);
    }
}

运行结果

00000000000000000000000000000111 7

~ 取反运算符

规则 对操作数的每一位进行操作,1 变成 0,0 变成 1。

代码语言:javascript
复制
~5 =>  0000 0101   ~  => 1111 1010

5取反后十进制数为 (高位省略)

1111 1010 (最高位为1,肯定为负数,此时为补码) 1111 1001 (补码转反码-1) 1000 0110 (反码转原码,此时为-6)

所以5取反后为-6

程序验证

代码语言:javascript
复制
public class Code {
    public static void main(String[] args) {
        int a = 5;;
        System.out.println(Integer.toBinaryString(~a));
        System.out.println(~a);
    }
}

运行结果

11111111111111111111111111111010 -6

^ 异或运算符 规则 两个操作数进行异或时,对于同一位上,如果数值相同则为 0,数值不同则为 1。

1 ^ 0 = 1,

1 ^ 1 = 0,

0 ^ 0 = 0; 3 ^ 5 = 6,这是因为 (高位省略)

0000 0011

^

0000 0101

=

0000 0110

程序验证

代码语言:javascript
复制
public class Code {
    public static void main(String[] args) {
        int a = 5;
        int b = 3;   
        System.out.println(String.format("%032d",Integer.parseInt(Integer.toBinaryString(a ^ b))));
        System.out.println(a ^ b);
    }
}

运行结果

00000000000000000000000000000110 6

值得注意的是 3 ^ 5 = 6,而 6 ^ 5 = 3

0000 0110

^

0000 0101

=

0000 0011

代码语言:javascript
复制
public class Code {
    public static void main(String[] args) {
        int a = 5;
        int b = 6;
        System.out.println(String.format("%032d",Integer.parseInt(Integer.toBinaryString(a ^ b))));
        System.out.println(a ^ b);
    }
}

运行结果

00000000000000000000000000000011 3

针对这个特性,我们可以将异或运算作为一个简单的数据加密的形式。比如,将一个mp4文件所有数值与一个种子数值进行异或得到加密后的数据,解密的时候再将数据与种子数值进行异或一次就可以了。

所以说异或运算可以作为简单的加解密运算算法。可以说很多对称加密算法都是根据异或进行扩展的,之后在DES,AES加密原理中会有说到。

>> 右移运算符 规则 a >> b 将数值 a 的二进制数值从 0 位算起到第 b - 1 位,整体向右方向移动 b 位,符号位不变,正数高位空出来的位补数值 0,负数补1。

5 >> 1 ===> 0000 0000 0000 0101 >> 1 = 0000 0000 0000 0010 = 2 7 >> 2 ===> 0000 0000 0000 0111 >> 2 = 0000 0000 0000 0001 = 1 9 >> 3 ===> 0000 0000 0000 1001 >> 3 = 0000 0000 0000 0001 = 1 11 >> 2 ===> 0000 0000 0000 1011 >> 2 = 0000 0000 0000 0010 = 2 规律:a >> b = a / ( 2 ^ b ) ,所以 5 >> 1= 5 / 2 = 2,11 >> 2 = 11 / 4 = 2。 (此处2 ^ b为2的b次方)

我们来看一下负数的右移

-5 >> 1

1000 0000 0000 0101 (-5原码)

1111 1111 1111 1010 (-5反码)

1111 1111 1111 1011 (-5补码)

1111 1111 1111 1101 右移1位,符号位不变,负数高位补1

1111 1111 1111 1100 补码转反码

1000 0000 0000 0011 反码转原码,所以-5右移1位为-3

程序验证

代码语言:javascript
复制
public class Code {
    public static void main(String[] args) {
        int a = -5;
        System.out.println(Integer.toBinaryString(a >> 1));
        System.out.println(a >> 1);
    }
}

运行结果

11111111111111111111111111111101 -3

右移还有一个无符号右移>>>,对于正数来说,>>和>>>没有什么区别,但对于负数来说差别却是巨大的

-5 >>> 1 (此处必须使用32位来计数)

1000 0000 0000 0000 0000 0000 0000 0101 -5原码

1111 1111 1111 1111 1111 1111 1111 1010 -5反码

1111 1111 1111 1111 1111 1111 1111 1011 -5补码

0111 1111 1111 1111 1111 1111 1111 1101 无符号右移1位,高位补0

因为此时高位为0,已经为正数了,所以0111 1111 1111 1111 1111 1111 1111 1101就是结果的原码,十进制为2147483645

具体为(2 ^ 30)+(2 ^ 29) +(2 ^ 28)+(2 ^ 27)+(2^ 26)+(2 ^ 25)+(2 ^ 24)+(2 ^ 23)+(2 ^ 22)+(2 ^ 21)+(2 ^ 20)+(2 ^ 19)+(2 ^ 18)+(2 ^ 17)+(2 ^ 16)+(2 ^ 15)+(2 ^ 14)+(2 ^ 13)+(2 ^ 12)+(2 ^ 11)+(2 ^ 10)+(2 ^ 9)+(2 ^ 8)+(2 ^ 7)+(2 ^ 6)+(2 ^ 5)+(2 ^ 4)+(2 ^ 3)+(2 ^ 2)+1 =2147483645

程序验证

代码语言:javascript
复制
public class Code {
    public static void main(String[] args) {
        int a = -5;
        System.out.println("0" + Integer.toBinaryString(a >>> 1));
        System.out.println(a >>> 1);
    }
}

运行结果

01111111111111111111111111111101 2147483645

<< 左移运算符 规则 a << b 将数值 a 的二进制数值从 0 位算起到第 b - 1 位,整体向左方向移动 b 位,符号位不变,低位空出来的位补数值 0。

5 << 1 ===> 0000 0000 0000 0101 << 1 = 0000 0000 0000 1010 = 10 7 << 2 ===> 0000 0000 0000 0111 << 2 = 0000 0000 0001 1100 = 28 9 << 3 ===> 0000 0000 0000 1001 << 3 = 0000 0000 0100 1000 = 72 11 << 2 ===> 0000 0000 0000 1011 << 2 = 0000 0000 0010 1100 = 44 规律: a << b = a * (2 ^ b) (此处2 ^ b为2的b次方)

综合上面两个可以看到,如果某个数值(正数)右移 n 位,就相当于拿这个数值去除以 2 的 n 次幂。如果某个数值(正数)左移 n 位,就相当于这个数值乘以 2 ^ n。

我们来看一下负数左移

-5 << 1

1000 0000 0000 0101 (-5原码)

1111 1111 1111 1010 (-5反码)

1111 1111 1111 1011 (-5补码)

1111 1111 1111 0110 左移1位

1111 1111 1111 0101 补码转反码

1000 0000 0000 1010 反码转原码,为-10

所以-5 << 1 = -10

程序验证

代码语言:javascript
复制
public class Code {
    public static void main(String[] args) {
        int a = -5;
        System.out.println(Integer.toBinaryString(a << 1));
        System.out.println(a << 1);
    }
}

运行结果

11111111111111111111111111110110 -10

由此看来,负数左移规律 -a << b = -a * (2 ^ b) (此处2 ^ b为2的b次方)

检测第K位是否为1 (以下运行结果均未补高位0)

已知数n,检测其第K位(右起)是否为1,可以用以下表达式:

n & (1 << k - 1) 结果为0,说明第K位为0;结果不为0,说明第K位为1。

比如我要检测965的二进制第3位是否为1

代码语言:javascript
复制
public class Code {
    public static void main(String[] args) {
        int a = 965;
        int k = 1 << 3 - 1;
        System.out.println(Integer.toBinaryString(a));
        System.out.println(Integer.toBinaryString(a & k));
    }
}

运行结果

1111000101 100

说明第3位为1

如果我要检测第4位

代码语言:javascript
复制
public class Code {
    public static void main(String[] args) {
        int a = 965;
        int k = 1 << 4 - 1;
        System.out.println(Integer.toBinaryString(a));
        System.out.println(Integer.toBinaryString(a & k));
    }
}

运行结果

1111000101 0

说明第4位为0

给一个数的第K位设置为1

对于一个给定的操作数n,设置其第k位为1,可以用

n | (1 << k - 1)

比如我要设置965的第4位为1

代码语言:javascript
复制
public class Code {
    public static void main(String[] args) {
        int a = 965;
        int k = 1 << 4 - 1;
        System.out.println(Integer.toBinaryString(a));
        System.out.println(Integer.toBinaryString(a | k));
        System.out.println(a | k);
    }
}

运行结果

1111000101 1111001101 973

第k位清零

将给定操作数n的第k位清零,可以用表达式

n & ~(1 << k - 1)

比如我要将965的第3位清零

代码语言:javascript
复制
public class Code {
    public static void main(String[] args) {
        int a = 965;
        int k = 1 << 3 - 1;
        System.out.println(Integer.toBinaryString(a));
        System.out.println(Integer.toBinaryString(a & ~k));
        System.out.println(a & ~k);
    }
}

运行结果

1111000101 1111000001 961

切换第k位(1变0,0变1)

切换给定操作数n的第k位,可以用表达式

n ^ (1 << k-1)

比如我要切换965的第4位

代码语言:javascript
复制
public class Code {
    public static void main(String[] args) {
        int a = 965;
        int k = 1 << 4 - 1;
        System.out.println(Integer.toBinaryString(a));
        System.out.println(Integer.toBinaryString(a ^ k));
        System.out.println(a ^ k);
    }
}

运行结果

1111000101 1111001101 973

切换第3位

代码语言:javascript
复制
public class Code {
    public static void main(String[] args) {
        int a = 965;
        int k = 1 << 3 - 1;
        System.out.println(Integer.toBinaryString(a));
        System.out.println(Integer.toBinaryString(a ^ k));
        System.out.println(a ^ k);
    }
}

运行结果

1111000101 1111000001 961

切换值为1的最右位

切换给定操作数n的值为1的最右位,可以使用表达式

n & n - 1

还是965

代码语言:javascript
复制
public class Code {
    public static void main(String[] args) {
        int a = 965;
        int k = a - 1;
        System.out.println(Integer.toBinaryString(a));
        System.out.println(Integer.toBinaryString(a & k));
        System.out.println(a & k);
    }
}

运行结果

1111000101 1111000100 964

隔离值为1的最右位

隔离给定操作数n的值为1的最右位,可以使用表达式n & -n.

这次我们使用966

代码语言:javascript
复制
public class Code {
    public static void main(String[] args) {
        int a = 966;
        int k = -a;
        System.out.println(Integer.toBinaryString(a));
        System.out.println(Integer.toBinaryString(a & k));
        System.out.println(a & k);
    }
}

运行结果

1111000110 10 2

最右位为1是在第2位上,所以隔离为10,末位0无意义,只是为了说明最右位为1是第2位而已。

隔离值为0的最右位

隔离给定操作数n的值为0的最右位,可以使用表达式

~n & n + 1

这次依然使用965

代码语言:javascript
复制
public class Code {
    public static void main(String[] args) {
        int a = 965;
        int k = a + 1;
        System.out.println(Integer.toBinaryString(a));
        System.out.println(Integer.toBinaryString(~a));
        System.out.println(Integer.toBinaryString(k));
        System.out.println(Integer.toBinaryString(~a & k));
        System.out.println(~a & k);
    }
}

运行结果

1111000101 11111111111111111111110000111010 1111000110 10 2

结果10仅表示最右位为0的为第2位

检查某个数是否是2的幂

给定一个数n,检查其是否满足2 ^ n(2的n次方)的形式,可以使用表达式

if (n & n - 1 == 0)

我们使用968来测试一下

代码语言:javascript
复制
public class Code {
    public static void main(String[] args) {
        int a = 968;
        int k = a - 1;
        System.out.println(Integer.toBinaryString(a));
        if ((a & k) == 0) {
            System.out.println("a是2的幂");
        }else {
            System.out.println("a不是2的幂");
        }
    }
}

运行结果

1111001000 a不是2的幂

我们用32768来测试一下

代码语言:javascript
复制
public class Code {
    public static void main(String[] args) {
        int a = 32768;
        int k = a - 1;
        System.out.println(Integer.toBinaryString(a));
        if ((a & k) == 0) {
            System.out.println("a是2的幂");
        }else {
            System.out.println("a不是2的幂");
        }
    }
}

运行结果

1000000000000000 a是2的幂

将某个数乘以2的幂

对于一个给定的数n,将其乘以2 ^ k (2的k次方),可以使用表达式

n << k

假如我们用965乘以2的5次方

代码语言:javascript
复制
public class Code {
    public static void main(String[] args) {
        int a = 965;
        int k = 5;
        System.out.println(Integer.toBinaryString(a));
        System.out.println((int)(a * Math.pow(2,k)));
        System.out.println(a << k);
    }
}

运行结果

1111000101 30880 30880

将某个数除以2的幂

给定操作数n,将其除以2 ^ k (2的k次方),可以使用表达式

n >> k

假如我们用965除以2的5次方

代码语言:javascript
复制
public class Code {
    public static void main(String[] args) {
        int a = 965;
        int k = 5;
        System.out.println(Integer.toBinaryString(a));
        System.out.println((int)(a / Math.pow(2,k)));
        System.out.println(a >> k);
    }
}

运行结果

1111000101 30 30

找到给定操作数的模

给定操作数n,计算其k(这里k一般为2的幂)的模(% k),可以使用表达式n & k - 1

取数965对2的5次方取模

代码语言:javascript
复制
public class Code {
    public static void main(String[] args) {
        int a = 965;
        int k = 5;
        System.out.println(Integer.toBinaryString(a));
        System.out.println((int)(a % Math.pow(2,k)));
        System.out.println(a & ((1 << k) - 1));
    }
}

运行结果

1111000101 5 5

位值1的计数

统计位值1的计数方法有很多种,这里主要讲2种

1、按位处理

比如计算998的二进制数中有多少个1

代码语言:javascript
复制
public class Location {
    public static void main(String[] args) {
        int n = 998;
        System.out.println(Integer.toBinaryString(n));
        int count = 0;
        while (n != 0) {
            //count总是加末位的0或者1
            count += n & 1;
            //n右移1位
            n >>= 1;
        }
        System.out.println(count);
    }
}

运行结果

1111100110 7

2、使用切换方法:n & n - 1

代码语言:javascript
复制
public class Count {
    public static void main(String[] args) {
        int n = 998;
        System.out.println(Integer.toBinaryString(n));
        int count = 0;
        while (n != 0) {
            count++;
            //n和n-1每与操作一次就会消耗掉n的一个前位1
            n &= n - 1;
        }
        System.out.println(count);
    }
}

运行结果

1111100110 7

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 位运算符 &、|、~、^、>>、<<、>>>
    • ~ 取反运算符
    相关产品与服务
    数据库
    云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档