【Java】位图 & 布隆过滤器

位图

初识位图

位图, 实际上就是将二进制位作为哈希表的一个个哈希桶的数据结构, 由于二进制位只能表示 0 和 1, 因此通常用于表示数据是否存在.

如下图所示, 这个位图就用于标识 0 ~ 14 中有什么数字存在

在这里插入图片描述

可以看到, 我们这里相当于是把下标作为了 key-value 的一员. 但是这样同样也使得位图这个数据结构非常有局限性, 因为下标只能是整数. 因此通常来说, 位图都是用于存储整数是否存在的.

那么位图这么有局限性, 我们为何要使用它呢?

实际上, 我们可以看到, 存储一个数字是否存在, 我们只需要消耗 1 个比特位, 而如果我们使用正常的一个哈希表的键值对来存储的话, 首先是一个整型就是 4 个字节, 其次就是一个 boolean 类型, 我们暂定为 1 字节大小. 而这样相当于就占了 5 个字节, 总共为 40 个比特位.

那么结论也很明显了, 位图存储一个数据是否存在, 只需要消耗 1 比特空间, 相较于直接使用哈希表键值对存储, 大大提升了空间的使用效率.

实现思路

首先要实现位图, 我们肯定需要一个能很好操作比特位的数据类型, 那么在这一点上, 只要是整型都可以轻易的做到, 因此 byte, short, int, long 都是可行的

但是如果只使用单个数, 那肯定是不够的, 以 int 为例, 单个 int 就只有 32 位, 那假如说要存 100 个数字呢? 因此这里最好我们使用一个数组来操作. 我们这里就采用 byte 数组来实现(因为画图好画)

那么由于 byte 是 8 位, 因此我们就可以知道 byte[0] (这里没有数组名因此这样简称) 对应着的是前 8 位, 代表着 0 ~ 7 这八个数字. 而 byte[1] 则往后对应, 对应着 8 ~ 15, 后续同理… 后续我们通过使用 num / 8 就可以轻松的获取到对应下标

在这里插入图片描述

但是接下来另一个问题来了, 我们如何操作到位图中的每一个位呢? 如果是最简单的思路, 其实就是靠数字 1 来操作, 因为数字 1 的二进制位为 0000 0001 其中这个单独的 1 位置可以非常便于我们操作, 并且相较于其他的数字可读性非常的高.

那么此时我们会发现一个问题, 假如我想要令 byte[0] 中代表 0 的一位为 1, 那么此时我们还需要通过左移 1 来实现, 如下图所示

在这里插入图片描述

这样有没有问题呢? 当然没有, 不过依旧是比较的反直觉, 明明我们修改的数应该是 0 但是却反而需要通过移位来做, 因此我们这里就采用在每一个数字里面里面, 下标都是逆序的设计方式

在这里插入图片描述

可以看到此时在 byte[0] 里面, 下标就是从 7 依次减到 0, 而在 byte[1] 里面, 下标从 15 减到 8. 随后如果我们要修改 0 对应的数字, 需要左移的位就是 0 % 8, 其他的同理

当然, 这部分的设计是完全偏向于个人喜好的, 如果想要实现刚开始的顺序记录, 也不是不可以. 不过此处后续将会采用内部下标逆序的设计方式来书写代码, 感兴趣的可以自行实现顺序的版本.

位的基本操作

由于要操作位图, 相关的位操作我们必须要提前了解下, 这样才会便于我们后续代码的书写. 当然这里介绍的是具体的位相关操作, 如果对二进制位没有基本了解以及位运算也没有基本了解的, 需要自行先去了解一下相关内容

前提要求, 下面的所有第 x 位指的是从右往左, 从 0 开始来计数的

例如 0000 0001

其中 1 在第 0 位

  1. 给定一个数 n, 确定它的二进制中第 x 位是 0 还是 1

这个其实非常简单, 直接用一个 1 按位与即可, 要么把 1 左移 x 位后按位与, 要么把 n 右移 x 位后按位与

(1 << x) & n == 1 或 (n >> x) & n == 1

这个操作主要用于位图的查找功能


  1. 将一个数 n 的二进制中的第 x 位修改为 1

要把第 x 位修改为 1, 使用一个 1 左移 x 位后, 采用按位或操作即可

n |= (1 << x)

这个操作主要用于位图的记录功能


  1. 将一个数 n 的二进制中的第 x 位修改为 0

这个要修改为 0, 其实还是要用 1 左移 x 位, 不过要再取一次反, 然后采用按位与的操作

n &= (~(1 << x))

这个操作主要用于位图的删除功能

位图实现

其实目前所有的基础知识都介绍完了, 尤其是上面的三个基础操作, 接下来实现代码应该是非常简单的

初始化

public class MyBitSet {
   
    private byte[] bits;

    private static final int DEFAULT_SIZE = 8;

    public MyBitSet() {
   
        this(DEFAULT_SIZE);
    }
    public MyBitSet(int size) {
   
        bits = new byte[size / 8 + 1];
    }
}

存储元素

// 将某个数存入位图
public void set(int index) {
   
    int byteIndex = index / 8;
    // 扩容
    if(byteIndex >= bits.length){
   
        bits = Arrays.copyOf(bits, byteIndex + 1);
    }
    int bitIndex = index % 8;

    bits[byteIndex] |= (byte) (1 << bitIndex);
}

查找元素

// 查看某个数在位图中是否存在
public boolean get(int index) {
   
    int byteIndex = index / 8;
    int bitIndex = index % 8;
    return (bits[byteIndex] & (1 << bitIndex)) != 0;
}

删除元素

// 将某个数从位图中清除
public void clear(int index) {
   
    int byteIndex = index / 8;
    int bitIndex = index % 8;
    bits[byteIndex] &= (byte) ~(1 << bitIndex);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值