你不知道的Java的位操作(与JavaScript对比)

791 阅读6分钟

Java的位操作

本文是对javascript版文章的Java翻译以及实现时的一些总结与补充。

注:我个人认为Javajavascript本质上没什么差别,本文Java4.状态控制部分最大的闪光点我认为是运用了反射的知识点。

文章:你不知道的JavaScript位运算符
作者:我是leon
链接:https://juejin.cn/post/7106861315062562830
来源:稀土掘金
1. 位操作符概览

逻辑运算符(两版理论均相同)

javascript版:

运算符描述示例
按位与(AND)两个操作数对应的比特位都是1时,结果才为1,否则为01011 & 0111 = 0011
按位或(OR)两个操作数对应的比特位至少有一个1时,结果为1,否则为01011 | 0111 = 1111
按位异或(XOR)两个操作数对应的比特位有且只有一个1时,结果为1,否则为01011 ^ 0111 = 1100
按位非(NOT)逐个反转操作数的比特位,即0变成1,1变成0~1011 = 0100
左移通过从右推入零向左位移,并使最左边的位脱落。1011 << 1 = 10110
有符号右移通过从左推入最左位的拷贝来向右位移,并使最右边的位脱落。01011 >> 1 = 00101
无符号右移通过从左推入零来向右位移,并使最右边的位脱落。01011 >>> 1 = 00101

Java版:

符号作用说明
&逻辑与“与”,并且的关系,只要表达式中有一个值为false,结果即为false
|逻辑或“或”,或者的关系,只要表达式中有一个值为true,结果即为true
!逻辑非truefalsefalsetrue
逻辑异或相同为false,不同为true
&&短路与作用和&相同,但是有短路效果,如果左边为false,右边就不执行了
||短路或作用和|相同,但是有短路效果,如果左边为true,右边就不执行了
2. 位操作支持多少位

单位换算:1 Byte = 8 bit

image-20200712161157405
int a = 0b11111111;// 二进制
int b = 0377;// 八进制
int c = 255;// 十进制
int d = 0xff;// 十六进制

javascript中,只支持32位二进制数的位操作,也即能处理的最大十进制数字是 4294967295

而在Java中最大支持位数为64位,各类型字节数如上,分别乘8就是位数了。

javascript版:

parseInt('11111111111111111111111111111111'2);  // 4294967295
​
验证下超过32位二进制数的位操作:
// 33位二进制数,得到十进制数字 8589934591
parseInt('111111111111111111111111111111111'2); // 8589934591
 
// 对数字进行无符号位右移
8589934591 >>> 0// 4294967295
4294967295 >>> 0// 4294967295
​
可以看出,数字 8589934591 和 4294967295 进行 无符号位右移0位 操作,得到的结果是一样的。
产生这样结果的原因,是JS的位操作实现,只支持32位。
​
注意:ECMAScript 中的所有数值都以IEEE754 64位格式存储,只是在位操作的时候 ,需要转换成32位进行操作。

image-20220610161822722

Java版:

Integer.parseInt("1111111111111111111111111111111"2); // 31个1

image-20220610162338070

Integer.parseInt("11111111111111111111111111111111"2); // 32个1报错

image-20220610162459292

// 对数字进行无符号位右移
// 由于Java不像JS哪有多问题,所以不会出现上面JS的结果
8589934591 >>> 0;
4294967295 >>> 0;

image-20220610162731668

3. 负数的无符号右移

-2 >>> 1为什么输出2147483647

`-2`在运算中,是用补码表示,即`1 1111111111111111111111111111110`。
其中,第1位是符号位。 符号位`1`代表当前数字是负数。
​
将`-2`无符号右移1位,则最右边的`0`脱落,剩下31`1111111111111111111111111111111`,接着,在左侧补`0`,得到`01111111111111111111111111111111`。
​
`01111111111111111111111111111111`代表十进制数`2147483647`
4. 状态控制
场景:以下使用`React`+`TypeScript`,实现游戏状态机的状态流转,根据状态渲染对应的操作按钮。
// 游戏状态定义
export enum GAME_STATE{
   INIT = 1 << 0,      // 二进制表示:00001
   JOIN = 1 << 1,      // 二进制表示:00010
   PREPARE = 1 << 2,   // 二进制表示:00100
   PLAY = 1 << 3,      // 二进制表示:01000
}
// 根据状态渲染按钮
function RenderButton({state, changeState}){
	if((state & GAME_STATE.PLAY) === GAME_STATE.PLAY){
		return null;
	}
	
	if((state & GAME_STATE.INIT) === GAME_STATE.INIT){
		return <button 
				onClick={() => changeState(GAME_STATE.JOIN)}
		    >加入游戏</button>
	}
	
	if((state & GAME_STATE.JOIN) === GAME_STATE.JOIN){
		return <button 
				onClick={() => changeState(GAME_STATE.PREPARE)}
			>准备游戏</button>
	}

	if((state & GAME_STATE.PREPARE) === GAME_STATE.PREPARE){
		return <button 
				onClick={() => changeState(GAME_STATE.PLAY)}
			>开始游戏</button>
	}

	return null;
}
  
// 渲染游戏页面
function Page(){
	const state = useRef(GAME_STATE.INIT);

	const changeState = useCallback((newState) => {
		state.current = newState;
	}, [state]);

	return (<div>
		// ....other code
		<RenderButton state={state.current} changeState={changeState} />
	</div>);
}

image-20220610171522551

image-20220610171547414

image-20220610171606346

Java版实现:

public enum GAME_STATE {
    INIT(1 << 0),
    JOIN(1 << 1),
    PREPARE(1 << 2),
    PLAY(1 << 3);

    private int bit;

    GAME_STATE(int i) {
        this.bit = i;
    }

    public int getBit() {
        return bit;
    }

    public void setBit(int bit) {
        this.bit = bit;
    }

    public int andOperation(GAME_STATE state){
        return this.bit & state.bit;
    }
    public int orOperation(GAME_STATE state){
        return this.bit | state.bit;
    }

    public Boolean equalOperation(GAME_STATE state){
        return this.bit == state.bit;
    }

    public Boolean equalOperation(int state){
        return this.bit == state;
    }

}
import java.lang.reflect.Method;

public class Page {

    private static GAME_STATE state = null;

    public static GAME_STATE getInstance() {
        if (state == null) {
            return state = GAME_STATE.INIT;
        }
        return state;
    }

    public void setState(GAME_STATE state) {
        this.state = state;
    }

    public void Render(GAME_STATE state, Method setState) {
        Button button = new Button();
        Button renderButton = button.RenderButton(state, setState);
        System.out.println(renderButton.getMessage());
    }
}
import java.lang.reflect.Method;

public class Button {

    private String message;
    private Method method;

    private void setMessage(String message){
        this.message = message;
    }

    public void setMethod(Method method) {
        this.method = method;
    }

    public String getMessage() {
        return message;
    }

    public Method getMethod() {
        return method;
    }

    public Button RenderButton(GAME_STATE state, Method setState){
        if (GAME_STATE.PLAY.equalOperation(GAME_STATE.PLAY.andOperation(state))) return null;
        if (GAME_STATE.INIT.equalOperation(GAME_STATE.INIT.andOperation(state))){
            Button button = new Button();
            button.setMessage("加入游戏");
            button.setMethod(setState);
            return button;
        }
        if (GAME_STATE.JOIN.equalOperation(GAME_STATE.JOIN.andOperation(state))){
            Button button = new Button();
            button.setMessage("准备游戏");
            button.setMethod(setState);
            return button;
        }
        if (GAME_STATE.PLAY.equalOperation(GAME_STATE.PREPARE.andOperation(state))){
            Button button = new Button();
            button.setMessage("开始游戏");
            button.setMethod(setState);
            return button;
        }
        return null;
    }
}
import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws NoSuchMethodException {
        // 存储一个全局Page State
        // 模拟useRef()
        GAME_STATE state = Page.getInstance();
        Page page = new Page();
        Class<Page> pageClass = Page.class;
        Method setState = pageClass.getMethod("setState", GAME_STATE.class);
        page.Render(state,setState);
    }
}

image-20220610171824010

在这里解析一下:

export enum GAME_STATE{
   INIT = 1 << 0,      // 二进制表示:00001
   JOIN = 1 << 1,      // 二进制表示:00010
   PREPARE = 1 << 2,   // 二进制表示:00100
   PLAY = 1 << 3,      // 二进制表示:01000
}

if((state & GAME_STATE.PLAY) === GAME_STATE.PLAY){
  return null;
}

state & GAME_STATE.PLAY	中如果state刚好为GAME_STATE.PLAY时,
我们回到顶部的那两个表,0 & 0 == 11 & 1 == 11 & 0 == 00 & 1 == 0
01000	GAME_STATE.PLAY
01000 GAME_STATE.PLAY
----- ---------------
01000 GAME_STATE.PLAY

if((state & GAME_STATE.PLAY) === GAME_STATE.PLAY){
  return null;
}
以上代码的意思即为判断state是否就是GAME_STATE.PLAY,其余几个同理,下面我们看一组更简单一点的代码
5. 权限控制

设计一个权限控制,用于不同角色对网站文章的权限分配。

export enum ARTICLE_RULE{
	VIEW = 1 << 0,     // 查看文章
	EDIT = 1 << 1,     // 编辑文章
	PUBLISH = 1 << 2,  // 发布文章
	DELETE = 1 << 3,   // 删除文章
} 

export enum ROLE{
	GUEST = ARTICLE_RULE.VIEW,    // 访客
	ADMIN = ARTICLE_RULE.VIEW | ARTICLE_RULE.EDIT | ARTICLE_RULE.PUBLISH | ARTICLE_RULE.DELETE,  // 超级管理员
	OPERATOR = ARTICLE_RULE.VIEW | ARTICLE_RULE.EDIT | ARTICLE_RULE.PUBLISH// 运营
}
6. 判断奇偶数
奇数,最末尾1位,一定是1。
所以将数字与1(二进制表示为:00000000000000000000000000000001)作位操作&。
如果等于1,则是奇数。

javascript版

function isOdd(number){
    return number & 1 === 1;
}

java版

public Boolean isOdd(int number) {
    return (number & 1) == 1;
}
7. 交换两个变量的值

javascript版

let a = 1;
let b = 2;

a = a ^ b;  // 这一步,a缓存了 a ^ b 的结果
b = a ^ b;  // 等价为: b = a ^ b ^ b, 其中 b ^ b = 0; 所以 b = a ^ 0 = a
a = a ^ b;  // 同上

java版

public void swrap(Integer a, Integer b) {
    a = a ^ b;
    b = a ^ b;
    a = a ^ b;
    System.out.println("a = " + a);
    System.out.println("b = " + b);
}

Java通过方法交换两个值

由于Java的方法参数传递的本质是值传递,也无法像C语言一样通过指针指向地址所在的值,必须通过引用数据类型传参数才能交换,我们这里使用数组这个引用类型进行交换。

public static void swap(int[] array){
    int temp = array[0];
    array[0] = array[1];
    array[1] = temp;
}
​
public static void main(String [] args){
    int [] array = {10,20};
    System.out.println("交换之前");
    System.out.println(array[0] + " " + array[1]);
    swap(array);
    System.out.println("交换之后");
    System.out.println(array[0] + " " + array[1]);
}
8. 判断整数是否相等
function isEqual(number1, number2){
    return (number1 ^ number2) === 0;
}
9. 判断是否为负数
如果是负数,则对数字进行 `无符号右移` 位操作,会变成一个新的数字。
所以,如果是负数,则两个数字不相等。
function isMinus(number){
    return number !== (number >>> 0);
}
10. 正浮点数取整
function toInt(floatNumber){
    return floatNumber >>> 0;
}
11. 正负浮点数取整
function toInt(floatNumber){
    return floatNumber | 0;
}
function toInt(floatNumber){
    return ~~floatNumber;
}
function toInt(floatNumber){
    return floatNumber >> 0;
}
12. ~取反
public class foo {
   public static void main(String[] args) {
       int a = 10;
       System.out.println(~a);
  }
}

首先我们需要知道:

  • 正数原码 = 反码 = 补码
  • 负数反码 = 原码符号位不变,其它位全取反负数补码 = 反码 + 1

1、首先~表示非运算符,就是将该数的所有二进制位全取反。但又由于计算机中是以补码的形式存储的,所以0 1010全取反是1 0101(只是补码形式,还需要转成原码)。 2、此时得到的1 0101只是补码,我们需要将它先转为反码,反码 = 补码-1,得到反码为1 0100。 3、我们得到反码后,将它转为原码,原码 = 反码符号位不变,其它位全取反,得到最终的原码为1 1011,转化为十进制就是-11

总结:只需要记住一个公式即可,永远都不会错哟
(~x) = -(x + 1)
13. 十进制转换成二进制
function dec2bin(dec){
    return (dec).toString(2);
}
14. 二进制转换成十进制
function bin2dec(bin){
    return parseInt(`${bin}`2)
}

参考