如何用 BitSet 求解质数问题

本文介绍了如何在ArrayList中利用BitSet高效地去除元素,并通过暴力求解展示了如何找出200000以内的质数。此外,探讨了布隆过滤器在缓存穿透中的应用,突显了BitSet在内存优化和特定场景中的优势。
摘要由CSDN通过智能技术生成


之前写过一篇《BitSet 源码解析》,

由于篇幅所限,没有给出使用的例子,

简单找个例子说明下。

一、ArrayList 中 BItSet的使用

ArrayListremoveIf 方法的实现中,

很巧妙的用了 BitSet

 public boolean removeIf(Predicate<? super E> filter) {
     Objects.requireNonNull(filter);
     int removeCount = 0;
     final BitSet removeSet = new BitSet(size);
     final int expectedModCount = modCount;
     final int size = this.size;
     for (int i=0; modCount == expectedModCount && i < size; i++) {
         @SuppressWarnings("unchecked")
         final E element = (E) elementData[i];
         if (filter.test(element)) {
             removeSet.set(i);
             removeCount++;
         }
     }
     if (modCount != expectedModCount) {
         throw new ConcurrentModificationException();
     }

     // shift surviving elements left over the spaces left by removed elements
     final boolean anyToRemove = removeCount > 0;
     if (anyToRemove) {
         final int newSize = size - removeCount;
         for (int i=0, j=0; (i < size) && (j < newSize); i++, j++) {
             i = removeSet.nextClearBit(i);
             elementData[j] = elementData[i];
         }
         for (int k=newSize; k < size; k++) {
             elementData[k] = null;  // Let gc do its work
         }
         this.size = newSize;
         if (modCount != expectedModCount) {
             throw new ConcurrentModificationException();
         }
         modCount++;
     }

     return anyToRemove;
 }

这段代码的大概逻辑如下:

遍历数组,标记出要删除的下标
数组数据原地迁移
空位设置为null,方便后续GC回收

   BitSet removeSet = new BitSet(size);
   
   if (filter.test(element)) {
       removeSet.set(i); // 标记需要删除的下标,
       removeCount++;
   }

数组原地迁移数据,空间复杂度就是 O(1),厉害

  for (int i=0, j=0; (i < size) && (j < newSize); i++, j++) {
      i = removeSet.nextClearBit(i);
      elementData[j] = elementData[i];
  }
  for (int k=newSize; k < size; k++) {
      elementData[k] = null;  // Let gc do its work
  }

假设原数组长度是10,其中 下标 3,4,7 是要删除的位置。

在这里插入图片描述

i = removeSet.nextClearBit(i); 这个方法,
从入参下标开始,找到第1个,值为0的下标,将其返回。

结合上图 nextClearBit(2) 返回 2,nextClearBit(3) 返回5

如果不明白,看下 《BitSet 源码解析

第1次循环,i = 0, j=0,elementData[0] = elementData[0];
第2次循环,i = 1, j=1,elementData[1] = elementData[1];
第3次循环,i = 2, j=2,elementData[2] = elementData[2];
第4次循环,i = 5, j=3,elementData[3] = elementData[5];
第5次循环,i = 6, j=4,elementData[4] = elementData[6];
第6次循环,i = 8, j=5,elementData[5] = elementData[8];
第6次循环,i = 9, j=6,elementData[6] = elementData[9];

它就是这么实现的原地复制。

  for (int k=newSize; k < size; k++) {
      elementData[k] = null;  // Let gc do its work
  }

这段代码比较简单,略!

从时间复杂度来讲,第1次循环,标记删除元素,

第2次循环,迁移数据,时间复杂度应该是 O(n)。

数组本身就是这个特点,也不大可能再被优化了。

从空间复杂度来说,借助 BitSet 实现原地复制,

空间复杂度,大约是 O(1) 了,这是很棒的实现。

二、列举出 200000 以内所有质数。

方法有很多,说一个暴力求解的,比如说 100 以内的质数

2 * 3
2 * 4

2 * 50

3 * 4
3 * 5

3 * 34

4 * 5
4 * 6

4 * 25

9 * 10
9 * 11

10 * 10

把 100 以内,所有的数相乘,得到所有的 和数,其它的就是质数

    public static void main(String[] args) {
        BitSet bitSet = new BitSet(n);
        int n = 100;
        double sqrt = Math.sqrt(n); // 求出平方根
        int i = 2;
        for(; i <= sqrt; i++){
            for(int j = i; i * j <= n; j++){
                bitSet.set(i*j);
            }
        }
        for(int k = 3; k <= n; k = bitSet.nextClearBit(++k)){
            System.out.println(k);
        }
    }

这只是个大概的思路,有兴趣可以优化下。


三、布隆过滤器

面试中如果问到缓存穿透问题,多多少少会提下布隆过滤器。

布隆过滤器,可以减少绝大多数缓存穿透问题。

相关的文章,网上很多,有兴趣可以自己找下。

大概的场景就是,对于某个 key 值,缓存中没有,

就查数据库,数据库也没有。本来这个没什么问题。

如果这种查询特别多,还是很消耗数据库的性能。

把数据库存在的值,做一个布隆过滤器,

如果一个key 值,布隆过滤器里没有,数据库里一定没有。

反之不亦然,这也是布隆过滤器的缺点。你用它的优点就好了。

总结

BitSet 做存储时,可大大降低内存的使用,在特定的场景中,很有用。

本质是位运算,没有特别难的地方,

它不是银弹,还是用在合适的地方好。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值