二进制状态标志位在Java中的应用

1、二进制

为什么是二进制呢?因为计算机在计算的时候全部都是基于二进制计算的。在Java中声明一个普通的int类型变量:

private int age = 10;

此时的变量 age 是默认十进制的,转化为二进制也就是:1010。如果想要在代码中声明一个十六进制的int类型变量呢?

private int age = 0x10;

此时变量age在10的前面加了一个 “0x” , 这就是告诉计算机我这个age是一个十六进制的10,转化为二进制也就是:0001 0000,由此可见虽然都是10,但是进制不一样,最终的结果也是完全不一样的。如何在代码中直接声明一个二进制的变量呢?这个真没有。。。但是可以通过各种操作来间接实现:

private int age = 1 << 3;

此时变量age是通过位移运算得到的一个变量,他的过程是这样:首先 1 转化为二进制是 0001,将 0001 左移三位,也就变成了 1000,因此此时的变量age实际是十进制的 8 。

2、二进制位运算实现标志位

2.1 二进制数的要求

首先说什么是标志位?在代码中定义一个变量:

private boolean is_eat_food_flag;

定义一个变量 is_eat_food_flag 来表示是否吃饭,然后在代码中就可以对这个变量进行赋值,并且根据这个变量的值做一些 if…else 的判断逻辑,这就是标志位。要想通过二进制数实现标志位的效果,那当然是对二进制数有要求的,并不是任意拿来一个二进制数都能当做标志位,下面看可以当做标志位的二进制数都有哪些:

private static final int FLAG_A = 1;   
private static final int FLAG_B = 1 << 1;
private static final int FLAG_C = 1 << 2;
private static final int FLAG_D = 1 << 3;
private static final int FLAG_E = 1 << 4;

将上边的5个变量全部转化为二进制就是:

0001
0010
0100
1000
0001 0000

他们的规律就是每一个二进制标志位都 “只占一个1” 并且随着 “1” 不断的往前移动可以出现很多的标志位, “只占一个1” 这个条件是实现二进制标志位的精髓所在。

2.2 二进制位运算实现标志位功能

上面我们定义了 A B C D E 五个标志位,下面就通过位运算来感受下二进制标志位的魅力:

public class FlagClass {
	private static final int FLAG_A = 1;   
	private static final int FLAG_B = 1 << 1;
	private static final int FLAG_C = 1 << 2;
	private static final int FLAG_D = 1 << 3;
	private static final int FLAG_E = 1 << 4;
	//注意这个是一个变量,上边的都是静态常量
	private int mFlag = 0;
}

在原来的基础上,我添加了一个变量 mFlag , mFlag 默认值为十进制的 0,转化为二进制也就是:0000 , 下面先让 mFlagFLAG_A“或” 操作:

mFlag |= FLAG_A; // 解释:0000 | 0001 , 所以mFlag最终等于 0001

经过上边的一个操作,mFlag的值由 “0000” 变为 “0001” , 这有什么作用呢?此时的 mFlag 如果分别与 FLAG_A 和 FLAG_B 做 “与” 操作,会有什么结果呢?

int a = mFlag & FLAG_A;  //解释: 0001 & 0001 , 所以 a = 0001
int b = mFLAG & FLAG_B;  //解释: 0001 & 0010 , 所以 b = 0000;

a 和 b 的值一个是 1, 一个是 0 ,这就说明:在第一步中 mFlag |= FLAG_A 的操作中,我们给 mFlag 变量添加了一个 FLAG_A 的标志位,所以在第二步中 a = 1, 由于 mFlag 没有添加 FLAG_B 的标志位, 所以第二步中 b = 0

所以我们就可以通过位运算来做这样的判断操作:

public class FlagClass {
	private static final int FLAG_A = 1;   
	private static final int FLAG_B = 1 << 1;
	private static final int FLAG_C = 1 << 2;
	private static final int FLAG_D = 1 << 3;
	private static final int FLAG_E = 1 << 4;
	//注意这个是一个变量,上边的都是静态常量
	private int mFlag = 0;
	
	public void main() {
		mFlag |= FLAG_B; //添加FLAG_B 的标志位,mFlag变为 0010
		
		if ( (mFlag & FLAG_B) == FLAG_B ) {
			//判断成立,因为上边添加了FLAG_B的标志位
		}
		
		if ( (mFlag & FLAG_C) == FLAG_C ) {
			//判断不成立,因为没有FLAG_C的标志位
		}
		
	   //解释: 先将FLAG_B 按位取反变成 1101, 然后 0010 & 1101 ,结果 mFlag = 0000 , 又变回了初始值, 相当于去掉了 FLAG_B的标志位。
		mFlag &= ~FLAG_B;
		
		if ( (mFlag & FLAG_B) == FLAG_B ) {
			//判断又不成立了,因为上边去掉了FLAG_B的标志位
		}
		
	}
}

3、标志位的应用

好吧,上边的例子还是挺晦涩的,并不能感受到二进制标志位的威力,下面就通过实际的例子感受下。
在一个类中有两个网络请求的异步回调方法:drinkWaterCallbackeatFoodCallback ,这两个回调方法被回调的先后顺序是不定的,并且只有当这两个异步的网络请求回调方法都被调用了,才能调用 playGame 方法,所以普通的实现思路是这样的:

public class Normal {
    /**
     * 是否喝水
     */
    private boolean isDrinkWater = false;
    /**
     * 是否吃饭
     */
    private boolean isEatFood = false;

    /**
     * 喝水的网络请求回调
     */
    public void drinkWaterCallback() {
        isDrinkWater = true;
        if (isEatFood) {
            playGame();
        }
    }

    /**
     * 吃饭的网络请求回调
     */
    public void eatFoodCallback() {
        isEatFood = true;
        if (isDrinkWater) {
            playGame();
        }
    }

    private void playGame() {
        Log.d("", "play game!");
    }
}

普通的实现思路:因为有两个异步的回调方法,所以我需要声明两个 boolean 类型的变量来分别表示这两种状态,然后在每一个回调方法中要做两件事:第一件事就是给自己的状态置为 true , 然后判断另一个标志位的状态从而决定是否调用 playGame 方法。这种实现方式在状态少的时候比较易读,但是如果要增加状态呢?比如在现在两个状态的基础上要新增: 洗漱、做作业、练钢琴 三种状态,那就需要再新增三种状态的标志位,也就是说随着状态的无限增多,我们在类中声明的标志位就会无限增多。那有没有可能 不管有多少状态,我只用一个标志位来实现呢? , 看一下二进制标志位如何实现这个:

public class BinaryFlag {
        /**
         * 是否喝水
         */
        private static final int DRINK_WATER_FLAG = 1 << 1;
        /**
         * 是否吃饭
         */
        private static final int EAT_FOOD_FLAG = 1 << 2;
        /**
         * 注意此处,只用了一个普通的 int 类型的变量flag
         */
        private volatile int mFlag = 0;

        /**
         * 喝水的网络请求回调
         */
        public void drinkWaterCallback() {
            //添加喝水的标志位
            mFlag |= DRINK_WATER_FLAG;
            //判断是否吃饭了
            if ((mFlag & EAT_FOOD_FLAG) == EAT_FOOD_FLAG) {
                playGame();
            }
        }

        /**
         * 吃饭的网络请求回调
         */
        public void eatFoodCallback() {
            //添加吃饭的标志位
            mFlag |= EAT_FOOD_FLAG;
            //判断是否喝水了
            if ((mFlag & DRINK_WATER_FLAG) == DRINK_WATER_FLAG) {
                playGame();
            }
        }

        private void playGame() {
            Log.d("", "play game!");
        }
    }

在二进制标志位的情况下,不管新增多少状态,我只需要增加相应的一个 静态常量的状态 就可以了,至于在某一个时刻我现在类中方法的回调情况只需要一个 mFlag变量 就足可以表示所有的状态了。

4、数据保存

如果这些还不能打动你,试想一种情况,如果现在新增一个需求:需要随时保存类中方法的回调状态到数据库中。按照常规的做法,需要把所有状态的boolean类型的标志位都放在一个实体类中存下来,然后再根据自己数据库类型存储这些数据:

public class Status {
	private boolean isEatFood;
	private boolean isDrinkWater;
	//....洗漱、做作业、弹钢琴
}

常规方法中随着状态的增加,就需要增加对应实体类的属性,这样才能把状态保存。但是如果使用 二进制状态标志位 ,不管有多少状态,我只需要保存一个 int 类型的 mFlag 数据就完事了,因为这个 int 类型的数据中包含了我所有的状态信息,这足够能看出二进制标志位的威力了吧!其实Google的Android SDK中用了大量的二进制标志位,比如说在 View类measure、layout、draw方法 中都贯穿着大量的二进制位运算,看起来很高大上但其实所运用的思想原理就是这么简单。

ok!大功告成!

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值