bitmap原理和实现

前言

我们知道一个1G=1024M,1M=1024K,1K=1024byte,1byte=8bit,所以1个字节等于8bit,也就是8个二进制位,位图法的概念是用一个位(bit)来标记某个数的存放状态,所以节省了大量的空间。

原理

以二进制位来表示数字
例如:第27位为1,第28位为0。表示在map中27存在28不存在

应用场景

大量数据进行排序,查找和去重上可以使用这个策略来降低内存的使用。举例
1:开发一个用户画像系统,实现用户信息的标签化。用户标签包含用户的社会属性,生活习惯,消费行为。
2:redis 用setbit(bitmap)统计活跃用户

数据结构

unsigned int bit[N],在这个数组里面,可以存储N * PHP_INT_SIZE * 8个数据,但是最大的数只能是N * PHP_INT_SIZE * 8-1。例如我们要存储的数据范围为0-63,则我们只需要将N=1,这样就可以把数据存进去,如下图:
在这里插入图片描述
数据为[5,1,7,15,0,4,6,10,14],将这些数据存入这个结构中为
在这里插入图片描述

代码实现

<?php
class BitMap {
    //创建长度为50的数组并用0填充
    private $bitMap;

    private $bitSize;

    public function __construct() {
        $this->bitMap = array_fill(0, 50, 0);
        $this->bitSize = PHP_INT_SIZE * 8;
    }

    /**
     * set
     * @param array $arr
     * @return array
     */
    public function set($arr) {
        $checkRet = $this->checkParams($arr);
        if($checkRet === false) {
            return $this->bitMap;
        }

        foreach ($arr as $item) {
            //获取索引位置
            list($bytePos, $bitPos) = $this->getIndex($item);

            //这个将1是左移多少位
            $position = 1 << $bitPos;

            //两个数按或运算,作用就是把二进制上对应位置的0置为1
            $this->bitMap[$bytePos] = $this->bitMap[$bytePos] | $position;
       }
       return $this->bitMap;
    }

    /**
     * contain
     * @param array $arr
     * @return array
     */
    public function contain($arr) {
        $checkRet = $this->checkParams($arr);
        if($checkRet === false) {
            return $this->bitMap;
        }

        $output = array();
        foreach($arr as $item) {
            //获取索引位置
            list($bytePos, $bitPos) = $this->getIndex($item);

            //这个将1是左移多少位
            $position = 1 << $bitPos;

            //将1左移position后,那个位置自然就是1,然后和以前的数据做&,判断是否为0即可
            $ret = ($this->bitMap[$bytePos] & $position) != 0;
            $output[$item] = (int)$ret;
        }
        return $output;
    }

    /**
     * del
     * @param array $arr
     * @return array
     */
    public function del($arr) {
        $checkRet = $this->checkParams($arr);
        if($checkRet === false) {
            return $this->bitMap;
        }

        foreach($arr as $item) {
            //获取索引位置
            list($bytePos, $bitPos) = $this->getIndex($item);

            //这个将1是左移多少位
            $position = 1 << $bitPos;

            $this->bitMap[$bytePos] &= ~$position;
        }
       return $this->bitMap;
    }

    /**
     * setAndSort output
     * @param array $arr
     * @return array
     */
    public function setAndSort($arr) {
        $checkRet = $this->checkParams($arr);
        if($checkRet === false) {
            return $this->bitMap;
        }

        $this->set($arr);

        $output = array();
        foreach ($this->bitMap as $k => $item){
            for($i = 0; $i < $this->bitSize; $i++){
                $temp = 1 << $i;
                $flag = $temp & $item;

                if ($flag){
                    $output[] = $k * $this->bitSize + $i;
                }
            }
        }
        return $output;
    }

    /**
     * 校验参数
     * @param array $arr
     * @return bool
     */
    private function checkParams($arr) {
        if (!is_array($arr) || empty($arr)) {
            return false;
        }
        return true;
    }

   /**
    * 获取index
    * @param int $item
    * @return array
    */
    private function getIndex($item) {
        //获得byte索引位置
        $bytePos = $item / $this->bitSize;

        //获得bit的索引位置
        $bitPos = $item % $this->bitSize;

        return array(
            $bytePos,
            $bitPos,
        );
    }
}

$bitmap = new BitMap();
print_r($bitmap->set(array(1,2,3,500)));
print_r($bitmap->contain(array(1,2,3)));
print_r($bitmap->setAndSort(array(100,1,3000,2,3)));
print_r($bitmap->del(array(500)));
print_r($bitmap->contain(array(1,2,3,500)));
?>

补充知识

位运算符

$a << $b Shift left(左移) 将 $a 中的位向左移动 $b 次(每一次移动都表示"乘以 2")。 
$a >> $b Shift right(右移) 将 $a 中的位向右移动 $b 次(每一次移动都表示"除以 2")。 
$a | $b Or(按位或) 将把 $a$b 中任何一个为 1 的位设为 1$a & $b And(按位与) 将把 $a$b 中都为 1 的位设为 1$a ^ $b Xor(按位异或) 将把 $a$b 中一个为 1 另一个为 0 的位设为 1~ $a Not(按位取反) 将 $a 中为 0 的位设为 1,反之亦然。 

bitmap的缺点

无法进行非运算
以一个用户数据为例,用户基本信息如下。
在这里插入图片描述
按照年龄标签,可以划分成90、00后两个bitmap
在这里插入图片描述
问题引出
当我想获取非90后用户的数量时,是无法直接非运算的,因为如果直接非运算,得到的数时是8,显然是
不符合我们预期的,我们的预期是1
90后用户数量bitmap图
解决思路
借助一个全量的bitmap
我们给定 90 后用户的 Bitmap,再给定一个全量用户的 Bitmap。最终要求出的是存在于全量用户,但又不存在于 90 后用户的部分。

90后用户
90后用户数量bitmap图
全量用户
全量用户bitmap图
我们可以使用异或操作,即相同位为0,不同位为1
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

bitmap优化

问题引出
如果在一个很长的bitmap里只存一两个用户,那岂不是很浪费空间。

问题解决
谷歌所实现的EWAHCompressedBitmap,对bitmap存储空间做了一定的优化。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值