文章目录
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亿个数 分成两类:
- 最高位为0
- 最高位为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 有什么问题
缓存雪崩
缓存穿透
缓存击穿