海量数据问题 / 巧妙算法

1. 20亿个非负数的 int 型整数,确认指定的数 t 是否存在?

  • 2 的 32 次方 大概是 40亿。

(1)建立个 boolean 类型的数组,20亿个整数作为下标,为 20 亿个下标对应的数组元素赋值 true。然后看 t 下标的元素。时间复杂度 O(n),空间复杂度 O(n)。

(2) bitmap: 采用一个二进制位来存储数据的方法。
    需要 20 亿个 位 ,1 表示这个数存在,0 表示不存在。Java 中封装了 bitSet,可以调用它的 get 、 set 方法。时间复杂度 O(n),空间复杂度变为 1/8。

(3)
    先假设这 20亿个数 开始放在一个文件中。
    然后将这 40亿个数 分成两类:

  1. 最高位为0
  2. 最高位为1
        并将这两类分别写入到两个文件中,其中一个文件中数的个数<=20亿,而另一个>=20亿;与要查找的数的最高位比较并接着进入相应的文件再查找
        再然后把这个文件为又分成两类: 次高位为1… …0…
        并将这两类分别写入到两个文件中,其中一个文件中数的个数<=10亿,而另一个>=10亿; 与要查找的数的次最高位比较并接着进入相应的文件再查找。 … 以此类推,就可以找到了,时间复杂度为O(logn)。
Java bitSet 实现原理

    底层是一个 Long(8字节,64位)的数组中。

(1)set 源代码:
public void set(int bitIndex) {

    if (bitIndex < 0)
throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);

    int wordIndex = wordIndex(bitIndex);
    expandTo(wordIndex);

    words[wordIndex] |= (1L << bitIndex); // Restores invariants

    checkInvariants();
}

     wordIndex 计算 代码如下:

private static int wordIndex(int bitIndex) {
    return bitIndex >> ADDRESS_BITS_PER_WORD;
}

    这里 ADDRESS_BITS_PER_WORD 的值是6,因为 :给 set 方法传入值,就需要判断这个值要放到数组的哪个位置,也就是求下标,Long类型是 64位 ,所以一个Long 可以存储 64 个数,求下标就只需要计算:bitIndex / 64 即可,这里正是使用 >> 来代替除法(因为位运算要比除法效率高)。而64正好是2的6次幂


private void expandTo(int wordIndex) {
    int wordsRequired = wordIndex+1;
    if (wordsInUse < wordsRequired) {
        ensureCapacity(wordsRequired);
        wordsInUse = wordsRequired;
    }
}

    这里的 expandTo 方法就是用来判断 words 数组的长度是否大于当前所计算出来的 wordIndex(简单的说,就是能不能存的下),如果超过当前words数组的长度(记录在实例变量wordsInUse里),也即是存不下,则新加一个long数到 words 里(ensureCapacity(wordsRequired)所实现的。)。

🤞

words[wordIndex] |= (1L << bitIndex); // Restores invariants
(2)get 源码

    (看所给定的 bitIndex 所对应的位数是否为1即可)。先看看代码:

public boolean get(int bitIndex) {
    if (bitIndex < 0)
        throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);

    checkInvariants();
    int wordIndex = wordIndex(bitIndex);
    return (wordIndex < wordsInUse)
        && ((words[wordIndex] & (1L << bitIndex)) != 0);
}

🤞
words[wordIndex] & (1L << bitIndex)。

(3)运用

使用BitSet的原理来实现:
    给定一个包含 n 个不同数字的数组,这些数字分别取自0、1、2、…, n,找到数组中缺失的那个。
    例如,给定nums =[0,1,3]返回2。
    注意:您的算法应该在线性运行时复杂度中运行。您能否仅使用恒定的额外空间复杂度来实现它?

public int missingNumberInByBitSet(int[] array) {
    int bitSet = 0;
    for (int element : array) {
        bitSet |= 1 << element;
    }

    for (int i = 0; i < array.length; i++) {
        if ((bitSet & 1 << i) == 0) {
            return i;
        }
    }
    return 0;
}

    这里是简单实现,使用 int ,而不是 long。

2. 怎么实现 比数大 的 最小的 2 的 n 次方

    要让最高位的1后面的位全变为1。
HashMap 中的源码实现:

static final int tableSizeFor(int cap) {
//保证 2的倍数 -1,后面再 +1 就好了,保存一致性。
//不进行-1操作的话,那么得到的值就是大于给定值的最小2的次幂值。其实就是 *2。
    int n = cap - 1;

//右移 1 位,再做 或,就可以得到
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

3. 10亿个整数找最大的100个?

(1)方法一:top k ,小顶堆-优先队列
    先拿100个数 建堆-小顶堆,然后依次添加剩余元素,如果大于堆顶的数,将这个数替换堆顶,保持小顶堆的结构,这样,遍历完后,堆中的100个数就是所需的最大的100个。建堆时间复杂度是O(mlogm),算法的时间复杂度为O(nlogm)(n为10亿,m为100)。
     优化:
    可以把所有10亿个数据 分组存放,比如分别放在 1000 个文件中。这样处理就可以分别在每个文件的 10^6 个数据中找出最大的 100 个数,合并到一起在再找出最终的结果。
    想优化,可以先去重。
(2)方法二:分治法
     将10亿个数据分成1000份,每份1万个数据,找到每份数据中最大的100个,最后在剩下的100*1000个数据里面找出最大的100个。

4. 如何快速在亿级黑名单中快速定位 URL 地址是否在黑名单中?(每条 URL 平均 64 字节)

    先通过 Hash 函数计算值,把对应的 位 都置为 1,最后只要把每个位相与,如果结果是 ”1“,说明 URL 存在。

5. 目前有10亿数量的自然数,乱序排序,需要对其排序,在 32 位机器上面完成,【资源有限】内存限制为 2 G

    存在 或 不存在—— 0 或 1——二进制,使用 位图 BitMap 【用每一位存储某种状态,适用于大规模数据,但是数据状态又不是很多的情况】bit 数组,可以比直接使用 Int 数组,缩为 32 倍(因为一个整数相当于 4*8=32)。
    比如,7,1,5,3 把要存储的数字相应的下标改为 ”1“:
bit [ 1 ]=1;
bit [ 3 ]=1;
bit [ 5 ]=1;
bit [ 7 ]=1;
    但是这样无法解决有重复数据的排序问题,而另一方面,这又是 天然有序的去重操作。

    布隆过滤器:二进制向量数据结构,它具有很好的 空间 和 时间效率,被用来检测一个元素是不是集合中的一个成员。
    ✨布隆过滤器判断存在的不一定存在,但是判断不存在的一定不存在。False is always false.True is maybe true.✨(因为 哈希碰撞
    布隆过滤器除了一个位数组,还有 k 个哈希函数,当一个元素加入布隆过滤器时,会进行以下操作:
使用 K 个哈希函数对元素值进行 K 次计算,得到 K 个哈希值。
根据得到的哈希值,在位数组中把对应下标的值置为1。
    一般不涉及删除,如果想要删除元素:在每一个二进制下标 加一个计数器,随着赋值 和 删除 累加、递减,然后 insert and delete。

特点:

  • 由于存放的是不完整的数据,所以占用的内存很少,而且新增、查询速度较快。
  • 多个 hash 函数,增大随机性,减少 hash 碰撞的概率。
  • 扩大数组范围,使 hash 值均匀分布,进一步减少 hash 碰撞的概率,随着数据的增多,误判率随之增加。
  • 返回的结果是概率性,不是确切的,只能判断数据是否一定不存在,而无法判断数据是否一定存在。
  • 删除很麻烦。

谷歌提供了 guava 包,支持动态扩容

Redis 每秒钟支持10万条数据的查询,自带布隆过滤器。

6. 统计日活用户

0515这一天,1 和 2号用户在线:

setbit 0515daily 1 1
setbit 0515daily 3 1

获取 0515的日活用户数:

bitCount 0515daily 

得到的结果就是 2,0515这一天,1 和 2号 两名用户在线。

如果是要看连续三天,用户在线的天数:
0516这一天,1 和 2号用户在线:

setbit 0516daily 1 1
setbit 0516daily 2 1

0517这一天,1 和 3号用户在线:

setbit 0517daily 1 1
setbit 0517daily 3 1

其实只有 1 号用户连续登录三天,使用 与 运算 :

bitop and desdaily 0515daily 0516daily 0517daily 

问:如何判断一个用户在一年 365 天登录了几次?
答:为每个用户建立集合,bitCount 即可获取登录天数。
jiajia 第 1 天、第 5 天登录了:

setbit jiajia2020 1 1
setbit jiajia2020 5 1

获取 jiajia 用户 2020 年登录次数:

bitcount jiajia2020

    用 setbit 存储全球人的数据是不行的,因为 setbit 底层是 String ,最大 512 Mb,大概是 42.9 亿

7. 网络爬虫-判断 URL 是否被爬过

共性:
海量数据、有限的存储空间。
    

Redis 有什么问题
缓存雪崩
缓存穿透
缓存击穿

如何快速判断一个数是不是 2 的 n 次方?

如何快速判断一个数的奇偶性?

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值