话不多说直接放代码
class DataMultiplex
{
public function get_is_attribute($x_rank, $attribute)
{
return ($x_rank&$attribute)==$attribute;
}
public function set_add_attribute($x_rank, $attribute)
{
return ($x_rank | $attribute);
}
public function set_del_attribute($x_rank, $attribute)
{
$_temp = ~$attribute;
return ($x_rank & $_temp);
}
// 检查是否是2的n次方
public function check_attribute($attribute){
return !($attribute&($attribute-1));
}
// 属性遍历
public function get_attribute_list($x_rank){
$list = array();
$n=1;
while($x_rank>0){
// &表示按位与,0x开头的表示的是十六进制数,k&0x1表示k与0x1按位与,其效果为取k的二进制中最右边的数字
if($x_rank&0x1){
$list[] = $n;
}
$n = $n<<1;
$x_rank = $x_rank>>1;
}
return $list;
}
// 遍历2 更优方案
public function get_attribute_list2($x_rank){
$list = array();
while($x_rank != 0){
$list[] = $x_rank-($x_rank & ($x_rank-1));
$x_rank = $x_rank & ($x_rank-1);
}
return $list;
}
}
说明
比如有一个账单处理流程, 需要主管审批,经理审批,财务审批,出纳付款
通常数据表会设计成这样
主管审批(是/否) | 经理审批(是/否) | 财务审批(是/否) | 出纳付款(是否) |
---|---|---|---|
0 | 0 | 0 | 0 |
假如该条数据 主管已审批 且 经理已审批
主管审批(是/否) | 经理审批(是/否) | 财务审批(是/否) | 出纳付款(是否) |
---|---|---|---|
1 | 1 | 0 | 0 |
假如要在中间加上总裁审批, 财务总监审批, 免不了又要加字段, 当需求有变化的时候不免显得成本有点高,而且多个字段意味着要多开辟一个存储空间,当数据量大的时候,存储开销也会更大
我们仔细看下 1100 看起来是不是有点感觉了,为了方便理解反转一下 0011
把状态做如下定义:主管已审批:1; 经理已审批:2; 财务已审批:4; 出纳已付完款:8
对于主管已审批 且 经理已审批的 结果就是3, 转成二进制就是 11
对应关系表
数据库字段值 | 二进制表示 | 出纳付款(8) | 财务审批(4) | 经理审批(2) | 主管审批(1) |
---|---|---|---|---|---|
0 | 0000 | N | N | N | N |
1 | 0001 | N | N | N | Y |
2 | 0010 | N | N | Y | N |
3 | 0011 | N | N | Y | Y |
4 | 0100 | N | Y | N | N |
5 | 0101 | N | Y | N | Y |
6 | 0110 | N | Y | Y | N |
7 | 0111 | N | Y | Y | Y |
… | … | … | … | … | … |
15 | 1111 | Y | Y | Y | Y |
是不是豁然开朗了, 我只用一个字段就可以把所有状态存储清楚了, 而且很容易扩展, 原来需要新增一个字段来表示新的业务类型,现在只用新增一个业务类型的定义就好了,值就是2的n次方
mysql里 一个int 是4个字节, 一个字节8位;所以可以表达32个bit位;所以如果值开辟了一个字段来存储属性, 最多可以设置32个属性;超过了需要自行扩展
这不仅仅是一个类, 更是一种系统设计思想, 大家可以发挥想象力, 看自己的系统能不能用上
如何查询
查含有4的属性集合
select * from reimbursement where attribute&4;
查含有1和2 的属性集合
select * from reimbursement where attribute&3=3
添加属性
UPDATE reimbursement SET attribute=attribute|8 where id=6;
删除属性
UPDATE reimbursement SET attribut=attribute^8 where id=6; //删除一个不存在于该属性集合的值时会出错
UPDATE reimbursement SET attribute=(attribute & ~8) where id=6; // 正确
当然使用了位运算就不能用索引
结语
这个其实就是个 bitmap 的典型应用