首页 > 编程语言 >Java学习之位运算(操作)总结

Java学习之位运算(操作)总结

时间:2022-11-01 10:34:22浏览次数:75  
标签:Java 运算 val int 取反 之位 二进制 异或


最近在反思工作第四年的深度,故而写此系列。
其他Java系列文章:

  • ​​Java学习之编译、反编译以及字节码入门​​
  • ​​Java学习之String​​
  • ​​Java学习之JDK9新特性​​

位操作,简单确强大,有一两拨千金奇效;可是平时工作中用得真心不多,故此文章也有备份回顾之意。
在计算机中所有数据都是以二进制的形式储存的。位运算其实就是直接对在内存中的二进制数据进行操作,因此处理数据的速度非常快。

基础概念

进制:位操作是基于二进制;
位运算符,有与、或、异或、取反、左移、右移这6种,只有​​​~​​取反是单目操作符,其它都是双目操作符。注意运算符的优先级。

符号

意义

说明

&

按位与

参与运算的两个位全为1,结果才为1

​|​


两个位都为0,结果才为0

^

异或

两个位相同时为0,相异为1

~

取反,非运算

0变1,1变0

<<

左移

二进制左移,高位抛弃,低位补零,<<后面的数字表示左移次数,左移一位即乘以2

>>

右移

二进制右移,移除的位数将被抛弃,>>后面的数字表示右移次数,右移一位即为除以2,不可用于负奇数

位操作复合操作符:&=、|=、 ^=、<<=、>>=;
位操作只能用于整形数据;

原码、反码、补码

反码:符号位不变,数值位分别"按位取反";
补码:符号位不变,数值位分别"按位取反",再+1,即反码+1;

已知补码求原码:符号位不变,数值位按位取反,末位再加1,即补码的补码等于原码;

二进制应用

交换两数

先给出代码:

int c = 1, d = 2;
c ^= d;
d ^= c;
c ^= d;
System.out.println("c=" + c + "d=" + d);

简单分析:

c = c ^ d;
d = d ^ c = d ^ c ^ d = c;
c = c ^ d = c ^ d ^ c = d;

判断奇偶

根据最未位来决定,为0就是偶数,为1就是奇数:
​​​if ((a & 1) == 0)​​​代替​​if (a % 2 == 0)​​来判断a是不是偶数。

二进制中1的个数

很经典的面试题:输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。

分析:如果一个整数不为0,则此数至少有一位是1。如果把这个整数减1,那原来处在整数最右边的1就会变为0,原来在1后面的所有的0都会变成1(如果最右边的1后面还有0的话),其余所有位将不会受到影响。

举例:一个二进制数1100,从右边数起第三位是处于最右边的一个1。减去1后,第三位变成0,它后面的两位0变成1,而前面的1保持不变,因此得到的结果是1011。发现减1的结果是把最右边的一个1开始的所有位都取反。此时如果再把原来的整数和减去1之后的结果做与运算,从原来整数最右边一个1那一位开始所有位都会变成0。即​​1100&1011=1000​​,把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0。那么一个整数的二进制有多少个1,就可以进行多少次这样的操作。

public static int getNum(int input) {
int res = 0;
while(input != 0) {
res += 1;
input = input & (input- 1);
}
return res;
}

变换符号

变换符号(学术一点的说法,求补码)就是正数变成负数,负数变成正数。根据高中(亦或是大学)知识,补码=源码的反码 + 1:​​~a + 1;​

2的n次幂

2的n次幂,是一个极其特殊的等比数列,在算法题中使用这个特性,有时候会有奇效;

// 初始化,可以是任意有效值
int val = 111;
// 计算2的n次方
int result1 = 2 << (n - 1);


// 判断一个数是否为2的幂次方,是的话,二进制表示时其最高位为1,其余位为0,&一定是0;(val & -val)就是获取最右1的位
int result1 = (val & (val - 1)) == 0;
int result2 = (val & -val) == val;
// 判断一个无符号数是2的n次方-1
int result3 = (val & (val + 1)) == 0;

筛素数法

筛素数法,百度百科定义:是把从1开始的某一范围内的正整数从小到大顺序排列,逐步筛掉非素数留下素数。
筛选思路:把从1开始的、某一范围内的正整数从小到大顺序排列, 1不是素数,首先把它筛掉。剩下的数中选择最小的数是素数,然后去掉它的倍数。依次类推,直到筛子为空时结束。
算法:埃拉托斯特尼筛法、欧拉筛法;不同算法的时间复杂度不同,空间复杂度也不尽相同。
实际上,我们可以使用位操作来实现,使用素数表进行优化来减小其空间占用。

// 打印100以内素数
int max = 100;
// 用bool数组来作标记的,bool型数据占1个字节(8位)
boolean[] flags = new boolean[max];
int [] primes = new int[max / 3 + 1];
int pi = 0;
for (int m = 2; m < max ; m ++) {
if (!flags[m]) {
primes[pi++] = m;
for(int n = m; n < max; n += m) {
flags[n] = true;
}
}
}
System.out.println(Arrays.toString(primes));

用位操作来压缩空间占用将会使空间的占用减少八分之七

int max = 100;
int[] flags2 = new int[max / 32 + 1];
pi = 0;
for (int m = 2; m < max ; m ++) {
if ((((flags2[m / 32] >> (m % 32)) & 1) == 0)) {
primes[pi++] = m;
for(int n = m; n < max; n += m) {
flags2[n / 32] |= (1 << (n % 32));
}
}
}
System.out.println(Arrays.toString(primes));

求绝对值

上面的知识点的延伸,负数取反加1得正数,正数不变,即得绝对值。
因此先移位来取符号位,int i = a » 31;要注意如果a为正数,i等于0,为负数,i等于-1。然后对i进行判断——如果i等于0,直接返回。否之,返回~a+1:
int i = a >> 31;
System.out.println(i == 0 ? a : (~a + 1));
对于任何数,与0异或都会保持不变,与-1即0xFFFFFFFF异或就相当于取反。因此,a与i异或后再减i(因为i为0或-1,所以减i即是要么加0要么加1)也可以得到绝对值。所以可以对上面代码优化下:
int j = a >> 31;
System.out.println((a ^ j) - j);

不用加法求两数之和

或运算;异或运算,同为假,异为真!0和1的异或运算如下:0^0 = 0, 1^0 = 1, 0^1 = 1,1^1 = 0
0和1的二进制加法,
不考虑进位:1+1=0,1+0=1,0+1=1,0+0=0,
在不考虑进位的时候,加法可以用异或实现;考虑有进位,0+0进位是0,1+0、0+1进位是0,1+1进位是1,与运算:0&0=0,0&1=0,1&0=0,1&1=1,在考虑进位运算时,加法和&运算类似;
加法运算可以这样实现:
1)先不考虑进位,按位计算各位累加(用异或实现),得到a;
2)然后计算进位,并将进位的值左移,得到值b。若b为0,则a就是加法运算的结果;若b不为0,则a+b即得结果(递归调用这个过程)。

public int bitAdd(int a, int b) {
if (b == 0) {
return a;
}
int sum = a ^ b;
int carry = (a & b) << 1;
return bitAdd(sum, carry);
}

BitSet

BitSet类:大小可动态改变,取值为true或false的位集合。用于表示一组布尔标志。此类实现一个按需增长的位向量。位 set 的每个组件都有一个 boolean 值。用非负的整数将 BitSet 的位编入索引。可以对每个编入索引的位进行测试、设置或者清除。通过逻辑与、逻辑或和逻辑异或操作,可以使用一个 BitSet 修改另一个 BitSet 的内容。默认情况下,set 中所有位的初始值都是 false。
每个位 set 都有一个当前大小,也就是该位 set 当前所用空间的位数。这个大小与位 set 的实现有关,所以它可能随实现的不同而更改。位 set 的长度与位 set 的逻辑长度有关,并且是与实现无关而定义的。
除非另行说明,否则将 null 参数传递给 BitSet 中的任何方法都将导致 NullPointerException。在没有外部同步的情况下,多个线程操作一个 BitSet 是不安全的。

JDK源码

JDK源码中有许多巧妙利用位运算的地方,翻看源码,会有很大收获的。

ConcurrentHashMap

实例化ConcurrentHashMap时带参数时,会根据参数调整table的大小,确保table的大小总是2的幂次方,调用tableSizeFor的时候每次返回的都是大于等于离该数最近的2的幂次方的数。

// The largest possible table capacity.
private static final int MAXIMUM_CAPACITY = 1 << 30;
// constructor
public ConcurrentHashMap(int initialCapacity) {
int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
MAXIMUM_CAPACITY :
tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
this.sizeCtl = cap;
}
// Returns a power of two table size for the given desired capacity.
private static int tableSizeFor(int c) {
int n = c - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

拓展:求小于等于给定数的最大的一个2的幂次方的数?

private static int tableSizeFor(int n) {
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return n - (n >> 1);
}

参考

​深入Java中的位操作​​​​不用加法求两数之和​​​​二进制实战技巧​

​筛素数
​​​二进制中1的个数​


标签:Java,运算,val,int,取反,之位,二进制,异或
From: https://blog.51cto.com/u_15851118/5811972

相关文章

  • Java学习之NoClassDefFoundError、ClassNotFoundException、NoSuchMethodError
    在菜逼如我短短的三年职业编码生涯中,无数次遇到这两个异常,故而总结一下。Java异常体系大致提一些,不是本文的重点。两者都是标准异常,平时碰到最多的是ClassNotFoundExceptio......
  • Java学习之String
    概述写在前面,工作第四年,重新把基础抓起来吧。String可以说是JDK中最基础的一个类。就记录一些日常开发中最常用的方法。String类是非可变类,其对象一旦创建,就不可销毁。Strin......
  • Java学习之JDK9新特性
    写在前面:现在(2019-01-12)绝大多数的公司或者个人都在使用JDK8,这一点毋庸置疑,但是不排除那些需要自我反省一下的落后者还在使用JDK5~7。毕竟JDK12都出来了。参考​​​JDK12......
  • 面试之基础算法题:求一个数字在给定的已排序数组中出现的起始、终止索引号(Java版)
    题目给定一个升序的整数数组,查找某一个值在数组中出现的索引号,例如,输入数组​​[2,3,3,4,4,5]​​​;查找的数是3,则返回​​[1,2]​​。时间复杂度要求为O(logN)。思路......
  • java操作http请求的三种方式
    java操作http请求的三种方式一、HttpClient步骤:1.获取一个Http客户端CloseableHttpClienthttpClient=HttpClients.createDefault();2.创建一个请求HttpGethttpGet......
  • Java 基于 SpringCloud 数据中台 ETL 工具,可以进行多种常见数据库之间的数据或结构迁
    基于SpringCloud数据中台ETL工具,可以进行多种常见数据库之间的数据或结构迁移提供源端数据库向目的端数据库的批量迁移同步功能,支持数据的全量和增量方式同步。包括:......
  • Java多线程(7):JUC(上)
    您好,我是湘王,这是我的博客园,欢迎您来,欢迎您再来~ 前面把线程相关的生命周期、关键字、线程池(ThreadPool)、ThreadLocal、CAS、锁和AQS都讲完了,现在就剩下怎么来用多线程了......
  • JavaScript中Array.from()方法的用法
    1.介绍作用:将一个伪数组对象或者可迭代的任意对象转换为一个真正的数组语法:Array.from(arrayLike[,mapFunction[,thisArg]])arrayLike:必传参数,指定需要转换为数......
  • Java-什么是多态,多态的具体体现有那些?
    多态:方法或者对象有多种形态,是OOP的第三大特征,是建立在封装和继承之上的多态的具体体现:方法多态重载体现多态重写体现多态对象多态对象的编译类型和运行类型可......
  • 变量类型转换 变量 运算符
    变量类型转换​运算中,不同类型的数据先转化为同一类型,然后进行运算。转换从低级到高级(根据容量来看)。低------------------------------------>高byte,short,c......