相信做网站时,一定会经常遇到这样的问题:

一个网站的用户,可能会拥有多种不同的状态。

比如是否进行实名认证,是否有正在进行的订单,是否是vip等等等等。

这类状态的特点是,只有两种:是、否。

而且对于同一用户来说,这些状态相互独立,并不能用单一的状态去概括,因为他们之间存在多种排列组合方式。

如果说我们为每个状态都在数据库中添加一个字段,那么随着状态的增多,无穷无尽的数据库字段会等待着我们去添加。

这样明显很麻烦。

那么有没有其他比较简单的方法呢?

这时候我想到了二进制。

二进制的根本,就是0和1,逢2进位。

而我们的业务场景中,这些状态的表示也无非两种:有、没有。

所以说,为何不将二者相结合呢?

假设我们有四种不同的状态:是否实名认证,是否视频认证,是否vip,是否绑定手机。

假设0代表否,1代表是,每种状态用一位数字来代替。

那么我就可以用一个四位二进制数来表示这些状态。

关于其顺序,我们可以自己定义,就假设按照上面列出的顺序来排列。

那么我们可以用0000来代表四种状态都没有完成的用户状态。

如若用户完成了视频认证,绑定了手机,但是没有实名认证且不是vip,那么可以用0101来表示。

这样的话,每一个四位的二进制数都可以巧妙地表示出一种特定的状态组合。

如果用一个long类型来表示这些状态的话,总共可以表示63种不同的状态(第一位为符号位)。

那这样,数据库中只需要存储一个二进制数所对应的十进制数,那我们就可以通过转换,得到对应用户当前拥有的状态了。

如果说理清了思路,那么接下来的问题就是,我们如何给这些用户添加对应的状态呢?

用户刚开始的时候拥有的状态都是从0开始,我们如何找到对应的状态码并在保证不修改其他状态的情况下将所需状态改为1呢?

这时候需要用到或运算。

我们先将上述四种状态的设定列出来

public final static Long REAL_AUTH = 1L << 0; // 用户实名认证 二进制状态码:0001
  public final static Long VIDEO_AUTH = 1L << 1; // 用户视频认证 二进制状态码:0010
  public final static Long IS_VIP = 1L << 2;// 用户是否vip 二进制状态码:0100
  public final static Long BIND_PHONE = 1L << 3;// 用户是否是否绑定手机 二进制状态码:1000

如果说当前用户需要添加绑定手机事件,那么我们只需要将手机的二进制状态码1000与当前用户的状态码进行或运算即可。

假设当前用户完成了实名认证以及是vip,那么他当前的状态码应该为0101。

将0101与1000进行或运算可以得到1101的结果。(第一位0或1得1,第二位1或0得1,第三位0或0得0,第四位1或0得1)。

那么用户的绑定手机状态通过运算已经成功添加到了用户的状态中。

我们可以将其提炼成方法。

/**
	 * @param states
	 *            已有状态值
	 * @param value
	 *            需要添加状态值
	 * @return 新的状态值
	 */

public static long addState(long states, long value) {
    return (states | value);
  }

通过将已有状态值和需要添加的状态值传入,我们便可以得到添加成功返回的状态值。

光有添加状态还不够,vip可能会出现到期的情况,那么到期的状态我们必须去掉。

这时候可以通过异或运算(相同为0,不同为1)解决。

假设刚才状态码为1101的用户vip到期,现在需要去除vip状态。

那么我们可以将1101与0100进行异或运算得到1001的结果。(第一位1异或0得1,第二位1异或1得0,第三位0异或0得0,第四位1异或0得1)。

同样可以提炼一个方法:

/**
   * @param states
   *            已有状态值
   * @param value
   *            需要删除状态值
   * @return 新的状态值
   */
  public static long removeState(long states, long value) {
    return states ^ value;
  }

添加删除解决后,如果说用户想知道自己当前是否已经绑定手机,是否实名认证,那还需要提供一个查询方法。

查询可以通过与运算实现。

如果说需要查询的状态被查询者拥有,那么该状态无论是状态码还是用户已有状态值,都应为1,进行与运算即可获得>0的结果。

反之二者至少有一个为0,与运算只能得到0的结果。

以vip为例,当状态为1101,即开通vip时,将1101与0100进行与运算,得到的结果是0100(第一位1与0得0,第二位1与1得1,第三位0与0得0,第四位1与0得0)。

此时状态码的结果不为0.

反之我们用上述去除vip的状态1001与vip状态码0100进行运算。

得到的结果为0(第一位1与0得0,第二位0与1得0,第三位0与0得0,第四位1与0得0)。

因此我们可以得出结论,当二者进行与运算结果为0时,该状态为否,当结果不为0时,该状态为是。

可以再提炼一个方法:

/**
	 * @param states
	 *            所有状态值
	 * @param value
	 *            需要判断状态值
	 * @return 是否存在
	 */
public static boolean hasState(long states, long value) {
    return (states & value) != 0;
  }

将三个方法综合运用我们便可以最小代价轻松完成以上需求。