海量数据面试题
⭐️前言⭐️
本篇文章主要针对在面试时可能涉及到的海量数据的面试题,该类型面试题常常考虑通过位图、布隆过滤器或者哈希的方式来解决。
🍉欢迎点赞 👍 收藏 ⭐留言评论
🍉博主将持续更新学习记录收获,友友们有任何问题可以在评论区留言
🍉博客中涉及源码及博主日常练习代码均已上传GitHub
📍内容导读📍
- 🍅 海量数据面试题
- 1. 哈希切割
- 2. 位图应用
- 2.1 只出现一次的整数
- 解法一:哈希切割+哈希表
- 解法二:两个位图
- 解法三:2-bit位图(两个模拟实现)
- 2.2 两个文件交集
- 解法一:哈希切割
- 解法二:位图
- 2.3 交集、并集和差集
- 2.4 不超过2次的所有整数
- 3. 布隆过滤器
- 精确算法和近似算法
- 4. 爬虫URL去重
- 1. 设计思路
- 2. 数据结构
- 3. 核心方法
- 4. 时间和空间复杂度分析
- 5. 优点和局限性
🍅 海量数据面试题
1. 哈希切割
给一个超过100G大小的log file, log中存着IP地址, 设计算法找到出现次数最多的IP地址? 与上题条件相同,如何找到top K的IP?
1、找到出现次数最多的IP地址
思路:
- 哈希切割大文件:根据IP地址进行哈希分区,将大文件切分为多个小文件,每个小文件的大小可以控制在内存可处理的范围内。
- 统计每个小文件中的IP频率:使用哈希表统计每个小文件中的IP出现次数。
- 合并结果:对所有小文件的结果进行汇总,找出出现次数最多的IP。
实现步骤:
1、第一遍扫描文件,进行哈希切割
- 计算每个IP的哈希值,将其划分到不同的小文件中。
2、第二遍遍历小文件,统计IP频率
- 使用哈希表统计IP出现的次数。
3、合并结果,找出出现次数最多的IP
- 遍历所有小文件的统计结果,找出出现次数最多的IP。
代码示例:
import java.io.*;
import java.util.HashMap;
import java.util.Map;public class MostFrequentIP {private static final int NUM_PARTITIONS = 100; // 假设划分为100个小文件// 哈希切割大文件public static void partitionLogFile(String inputFile) throws IOException {BufferedReader reader = new BufferedReader(new FileReader(inputFile));BufferedWriter[] writers = new BufferedWriter[NUM_PARTITIONS];// 初始化写入流for (int i = 0; i < NUM_PARTITIONS; i++) {writers[i] = new BufferedWriter(new FileWriter("partition_" + i + ".txt"));}// 按行读取日志文件String line;while ((line = reader.readLine()) != null) {String ip = extractIP(line);int partitionIndex = Math.abs(ip.hashCode()) % NUM_PARTITIONS;writers[partitionIndex].write(ip + "\n");}// 关闭所有写入流for (BufferedWriter writer : writers) {writer.close();}reader.close();}// 从日志行中提取IP地址(假设每行都是IP地址)private static String extractIP(String line) {return line.trim();}// 统计每个小文件中的IP频率public static Map<String, Integer> countIPs(String partitionFile) throws IOException {Map<String, Integer> ipCountMap = new HashMap<>();BufferedReader reader = new BufferedReader(new FileReader(partitionFile));String ip;while ((ip = reader.readLine()) != null) {ipCountMap.put(ip, ipCountMap.getOrDefault(ip, 0) + 1);}reader.close();return ipCountMap;}// 找出出现次数最多的IPpublic static String findMostFrequentIP() throws IOException {String mostFrequentIP = null;int maxCount = 0;// 遍历所有小文件的统计结果for (int i = 0; i < NUM_PARTITIONS; i++) {Map<String, Integer> ipCountMap = countIPs("partition_" + i + ".txt");for (Map.Entry<String, Integer> entry : ipCountMap.entrySet()) {if (entry.getValue() > maxCount) {maxCount = entry.getValue();mostFrequentIP = entry.getKey();}}}return mostFrequentIP;}public static void main(String[] args) throws IOException {String inputFile = "large_log_file.txt"; // 假设大文件名为large_log_file.txt// 1. 分区partitionLogFile(inputFile);// 2. 找出出现次数最多的IPString mostFrequentIP = findMostFrequentIP();System.out.println("出现次数最多的IP是:" + mostFrequentIP);}
}
2、找到Top K个出现次数最多的IP
思路:
1.哈希切割大文件:与找到最多的IP相同。
2.统计每个小文件的IP频率:使用哈希表统计每个小文件中的IP频率。
3.合并结果:使用最小堆(Min Heap)来维护前K个频率最高的IP。
实现步骤:
1.第一遍扫描文件,进行哈希切割:与上面一致。
2.第二遍遍历小文件,统计IP频率并维护Top K
- 使用哈希表统计小文件中的IP频率。
- 使用最小堆来维护全局Top K。
3.返回Top K结果。
代码实现:
import java.io.*;
import java.util.*;public class TopKFrequentIPs {private static final int NUM_PARTITIONS = 100; // 假设划分为100个小文件private static final int K = 10; // 假设找前10个IP// 哈希切割大文件public static void partitionLogFile(String inputFile) throws IOException {BufferedReader reader = new BufferedReader(new FileReader(inputFile));BufferedWriter[] writers = new BufferedWriter[NUM_PARTITIONS];for (int i = 0; i < NUM_PARTITIONS; i++) {writers[i] = new BufferedWriter(new FileWriter("partition_" + i + ".txt"));}String line;while ((line = reader.readLine()) != null) {String ip = extractIP(line);int partitionIndex = Math.abs(ip.hashCode()) % NUM_PARTITIONS;writers[partitionIndex].write(ip + "\n");}for (BufferedWriter writer : writers) {writer.close();}reader.close();}// 从日志行中提取IP地址private static String extractIP(String line) {return line.trim();}// 统计每个小文件中的IP频率并维护Top Kpublic static PriorityQueue<Map.Entry<String, Integer>> findTopK() throws IOException {PriorityQueue<Map.Entry<String, Integer>> minHeap = new PriorityQueue<>(K, Comparator.comparingInt(Map.Entry::getValue));for (int i = 0; i < NUM_PARTITIONS; i++) {Map<String, Integer> ipCountMap = countIPs("partition_" + i + ".txt");// 维护最小堆中的Top Kfor (Map.Entry<String, Integer> entry : ipCountMap.entrySet()) {if (minHeap.size() < K) {minHeap.offer(entry);} else if (entry.getValue() > minHeap.peek().getValue()) {minHeap.poll();minHeap.offer(entry);}}}return minHeap;}// 统计每个小文件中的IP频率public static Map<String, Integer> countIPs(String partitionFile) throws IOException {Map<String, Integer> ipCountMap = new HashMap<>();BufferedReader reader = new BufferedReader(new FileReader(partitionFile));String ip;while ((ip = reader.readLine()) != null) {ipCountMap.put(ip, ipCountMap.getOrDefault(ip, 0) + 1);}reader.close();return ipCountMap;}public static void main(String[] args) throws IOException {String inputFile = "large_log_file.txt"; // 假设大文件名为large_log_file.txt// 1. 分区partitionLogFile(inputFile);// 2. 找到Top K个出现次数最多的IPPriorityQueue<Map.Entry<String, Integer>> topK = findTopK();List<Map.Entry<String, Integer>> result = new ArrayList<>(topK);result.sort((a, b) -> b.getValue() - a.getValue()); // 从大到小排序System.out.println("Top K个出现次数最多的IP:");for (Map.Entry<String, Integer> entry : result) {System.out.println(entry.getKey() + " : " + entry.getValue());}}
}
2. 位图应用
2.1 只出现一次的整数
题目:给定100亿个整数,设计算法找到只出现一次的整数?
由于数据量非常大,我们需要考虑内存限制,以及如何在分布式或分段的情况下进行计算。
解法一:哈希切割+哈希表
思路概述:
解决这样的大规模问题可以采用哈希切割(Hash Partitioning)和位图(BitMap)相结合的方式。具体步骤如下:
1、哈希切割数据:将100亿个整数划分为多个小文件,每个文件只包含部分整数。
2、统计每个小文件中的整数频率:在小文件中,使用哈希表记录每个整数出现的次数。
3、合并结果:汇总所有小文件的结果,找出只出现一次的整数。
实现步骤:
1、第一遍扫描文件,进行哈希切割
- 根据整数值的哈希值,将其划分到多个小文件中。
2、第二遍遍历小文件,统计每个整数的频率
- 对每个小文件使用哈希表统计出现次数。
3、合并结果
- 汇总所有小文件的统计结果,找到只出现一次的整数。
代码示例:
import java.io.*;
import java.util.HashMap;
import java.util.Map;public class FindUniqueNumber {private static final int NUM_PARTITIONS = 1000; // 假设划分为1000个小文件// 哈希切割大文件public static void partitionLargeFile(String inputFile) throws IOException {BufferedReader reader = new BufferedReader(new FileReader(inputFile));BufferedWriter[] writers = new BufferedWriter[NUM_PARTITIONS];// 初始化写入流for (int i = 0; i < NUM_PARTITIONS; i++) {writers[i] = new BufferedWriter(new FileWriter("partition_" + i + ".txt"));}// 按行读取大文件String line;while ((line = reader.readLine()) != null) {int num = Integer.parseInt(line.trim());int partitionIndex = Math.abs(num) % NUM_PARTITIONS;writers[partitionIndex].write(num + "\n");}// 关闭所有写入流for (BufferedWriter writer : writers) {writer.close();}reader.close();}// 统计每个小文件中的整数频率public static Map<Integer, Integer> countFrequency(String partitionFile) throws IOException {Map<Integer, Integer> frequencyMap = new HashMap<>();BufferedReader reader = new BufferedReader(new FileReader(partitionFile));String line;while ((line = reader.readLine()) != null) {int num = Integer.parseInt(line.trim());frequencyMap.put(num, frequencyMap.getOrDefault(num, 0) + 1);}reader.close();return frequencyMap;}// 找出只出现一次的整数public static Integer findUniqueNumber() throws IOException {for (int i = 0; i < NUM_PARTITIONS; i++) {Map<Integer, Integer> frequencyMap = countFrequency("partition_" + i + ".txt");// 遍历哈希表,找到只出现一次的整数for (Map.Entry<Integer, Integer> entry : frequencyMap.entrySet()) {if (entry.getValue() == 1) {return entry.getKey(); // 返回找到的第一个只出现一次的整数}}}return null; // 没有找到只出现一次的整数}public static void main(String[] args) throws IOException {String inputFile = "large_numbers_file.txt"; // 假设大文件名为large_numbers_file.txt// 1. 哈希切割大文件partitionLargeFile(inputFile);// 2. 找到只出现一次的整数Integer uniqueNumber = findUniqueNumber();if (uniqueNumber != null) {System.out.println("只出现一次的整数是:" + uniqueNumber);} else {System.out.println("没有找到只出现一次的整数。");}}
}
解法二:两个位图
如果整数的范围是已知且不大(如0到2^31-1),可以使用位图来记录每个整数的频率。这里我们用两个位图分别记录出现1次和出现2次及以上的整数。
实现步骤
1、初始化两个位图bitMap1
和bitMap2
。
2、第一遍扫描:对于每个整数num:
- 如果
bitMap1
中对应的位为0,则将其置为1。 - 如果
bitMap1
中对应的位已经为1,则将bitMap2
对应的位置为1。
3、第二遍扫描:找出只在bitMap1
中为1且在bitMap2
中为0的整数。
代码示例:
import java.io.*;
import java.util.BitSet;public class FindUniqueUsingBitMap {private static final int MAX_RANGE = Integer.MAX_VALUE; // 假设整数范围为0到2^31-1// 用两个位图记录频率private BitSet bitMap1 = new BitSet(MAX_RANGE);private BitSet bitMap2 = new BitSet(MAX_RANGE);// 第一遍扫描数据public void scanFile(String inputFile) throws IOException {BufferedReader reader = new BufferedReader(new FileReader(inputFile));String line;while ((line = reader.readLine()) != null) {int num = Integer.parseInt(line.trim());if (!bitMap1.get(num)) {bitMap1.set(num); // 第一次出现,将bitMap1对应位设为1} else {bitMap2.set(num); // 第二次出现,将bitMap2对应位设为1}}reader.close();}// 找出只出现一次的整数public int findUnique() {for (int i = 0; i < MAX_RANGE; i++) {// 只在bitMap1中为1,且bitMap2中为0if (bitMap1.get(i) && !bitMap2.get(i)) {return i;}}return -1; // 没有找到只出现一次的整数}public static void main(String[] args) throws IOException {String inputFile = "large_numbers_file.txt"; // 假设大文件名为large_numbers_file.txtFindUniqueUsingBitMap findUnique = new FindUniqueUsingBitMap();// 1. 扫描大文件findUnique.scanFile(inputFile);// 2. 找出只出现一次的整数int uniqueNumber = findUnique.findUnique();if (uniqueNumber != -1) {System.out.println("只出现一次的整数是:" + uniqueNumber);} else {System.out.println("没有找到只出现一次的整数。");}}
}
解法三:2-bit位图(两个模拟实现)
解决方案:2-bit 位图
在一个2-bit位图中,每个整数对应两个位来表示其状态:
00
:表示该整数从未出现。01
:表示该整数出现一次。10
:表示该整数出现两次。11
:表示该整数出现三次及以上。
实现思路
1、初始化2-bit位图。
2、扫描输入数据:
- 如果当前整数的状态为
00
,将其状态设置为01
。 - 如果当前整数的状态为
01
,将其状态设置为10
。 - 如果当前整数的状态为
10
,保持不变。
3、再次扫描2-bit位图,找到状态为01的整数,即只出现一次的整数。
Java中没有直接2-bit位图的实现,可以借助两个1-bit位图 BitSet模拟实现2-bit位图的四个状态,同时也可以如下所示自己实现2-bit位图。
2-bit位图模拟实现:
import java.util.Arrays;public class TwoBitMap {private byte[] elem; // 2-bit位图的字节数组public int usedSize; // 用于记录非零位的个数public TwoBitMap() {// 默认给一个大小elem = new byte[1];}/*** 初始化n个整数的2-bit位图* @param n 整数范围*/public TwoBitMap(int n) {elem = new byte[n / 4 + 1]; // 每个字节可存储4个整数的状态}/*** 获取整数num的状态* @param num 整数* @return 状态值(00、01、10或11)*/public int get(int num) {if (num < 0) {throw new IndexOutOfBoundsException();}int byteIndex = num / 4; // 计算字节索引int bitOffset = (num % 4) * 2; // 计算在字节内的偏移量// 提取对应的2-bit状态return (elem[byteIndex] >> bitOffset) & 0b11;}/*** 设置整数num的状态* @param num 整数* @param state 新的状态值(00、01、10或11)*/public void set(int num, int state) {if (num < 0 || state < 0 || state > 3) { // 状态值应为0到3throw new IndexOutOfBoundsException();}int byteIndex = num / 4;int bitOffset = (num % 4) * 2;// 扩容if (byteIndex > elem.length - 1) {elem = Arrays.copyOf(elem, byteIndex + 1);}// 清除当前2-bit状态elem[byteIndex] &= ~(0b11 << bitOffset);// 设置新的2-bit状态elem[byteIndex] |= (state << bitOffset);usedSize++;}/*** 更新整数num的状态* 00 -> 01, 01 -> 10, 10 -> 11, 11保持不变* @param num 整数*/public void update(int num) {int currentState = get(num);// 根据当前状态进行更新if (currentState == 0) {set(num, 1); // 00 -> 01} else if (currentState == 1) {set(num, 2); // 01 -> 10} else if (currentState == 2) {set(num, 3); // 10 -> 11}// 如果是11,则不变}/*** 统计2-bit位图中非零位的个数* @return 非零位的个数*/public int getUsedSize() {return this.usedSize;}public static void main(String[] args) {int[] array = {1, 2, 3, 10, 4, 18, 13, 2, 10, 18, 3}; // 测试用例TwoBitMap twoBitMap = new TwoBitMap(18);// 更新2-bit位图状态for (int i = 0; i < array.length; i++) {twoBitMap.update(array[i]);}// 找出只出现一次的整数System.out.println("只出现一次的整数:");for (int i = 0; i <= 18; i++) {if (twoBitMap.get(i) == 1) { // 01表示只出现一次System.out.println(i);}}// 找出只出现两次的整数System.out.println("只出现两次的整数:");for (int i = 0; i <= 18; i++) {if (twoBitMap.get(i) == 2) { // 10表示只出现两次System.out.println(i);}}// 找出出现三次及以上的整数System.out.println("出现三次及以上的整数:");for (int i = 0; i <= 18; i++) {if (twoBitMap.get(i) == 3) { // 11表示出现三次及以上System.out.println(i);}}}
}
2.2 两个文件交集
题目:
给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集?
解法一:哈希切割
思路概述
我们可以将两个大文件进行哈希切割,将整数划分成多个较小的分区,每个分区的数据量可以控制在内存可处理的范围内。然后在每个分区内使用位图或哈希表计算交集。
实现步骤
- 哈希切割
- 首先对两个文件进行哈希切割,将大文件划分成多个较小的分区,每个分区可以独立处理。
- 通过哈希函数,将相同的整数映射到相同的对应分区文件中,这样可以确保相同的整数出现在相同的分区。
- 统计交集
- 针对每个分区文件,使用位图(BitMap)或哈希表来存储和查找交集。
- 合并结果
- 对每个分区的结果进行汇总,得到全局的交集。
具体代码实现(Java)
代码说明
- 使用哈希切割分区文件。
- 在分区文件中寻找交集。
- 合并各个分区的交集。
import java.io.*;
import java.util.BitSet;
import java.util.HashSet;
import java.util.Set;public class FindIntersection {private static final int NUM_PARTITIONS = 100; // 假设划分为100个小文件private static final int MAX_RANGE = Integer.MAX_VALUE; // 假设整数范围为0到2^31-1// 哈希切割大文件public static void partitionFile(String inputFile, String outputPrefix) throws IOException {BufferedReader reader = new BufferedReader(new FileReader(inputFile));BufferedWriter[] writers = new BufferedWriter[NUM_PARTITIONS];// 初始化写入流for (int i = 0; i < NUM_PARTITIONS; i++) {writers[i] = new BufferedWriter(new FileWriter(outputPrefix + "_" + i + ".txt"));}// 按行读取大文件并进行哈希切割String line;while ((line = reader.readLine()) != null) {int num = Integer.parseInt(line.trim());int partitionIndex = Math.abs(num) % NUM_PARTITIONS;writers[partitionIndex].write(num + "\n");}// 关闭所有写入流for (BufferedWriter writer : writers) {writer.close();}reader.close();}// 找出分区文件的交集public static Set<Integer> findIntersectionInPartition(String file1, String file2) throws IOException {Set<Integer> set1 = new HashSet<>();Set<Integer> intersection = new HashSet<>();// 读取第一个分区文件,将整数存入集合BufferedReader reader1 = new BufferedReader(new FileReader(file1));String line;while ((line = reader1.readLine()) != null) {int num = Integer.parseInt(line.trim());set1.add(num);}reader1.close();// 读取第二个分区文件,查找交集BufferedReader reader2 = new BufferedReader(new FileReader(file2));while ((line = reader2.readLine()) != null) {int num = Integer.parseInt(line.trim());if (set1.contains(num)) {intersection.add(num);}}reader2.close();return intersection;}// 合并所有分区的交集public static Set<Integer> findTotalIntersection(String filePrefix1, String filePrefix2) throws IOException {Set<Integer> totalIntersection = new HashSet<>();// 遍历所有分区for (int i = 0; i < NUM_PARTITIONS; i++) {String partitionFile1 = filePrefix1 + "_" + i + ".txt";String partitionFile2 = filePrefix2 + "_" + i + ".txt";// 找出当前分区的交集Set<Integer> partitionIntersection = findIntersectionInPartition(partitionFile1, partitionFile2);totalIntersection.addAll(partitionIntersection);}return totalIntersection;}public static void main(String[] args) throws IOException {String inputFile1 = "large_file_1.txt"; // 假设大文件1的路径String inputFile2 = "large_file_2.txt"; // 假设大文件2的路径// 1. 对两个大文件进行哈希切割partitionFile(inputFile1, "partition_file_1");partitionFile(inputFile2, "partition_file_2");// 2. 找出两个文件的交集Set<Integer> totalIntersection = findTotalIntersection("partition_file_1", "partition_file_2");// 3. 输出交集System.out.println("交集中的整数:");for (int num : totalIntersection) {System.out.println(num);}}
}
代码解释
-
哈希切割(
partitionFile
方法):- 将大文件划分为多个较小的分区文件。
- 根据整数的哈希值,将其分配到不同的分区文件中,确保相同的整数会被分配到相同的分区。
-
查找分区交集(
findIntersectionInPartition
方法):- 对每个分区文件,使用
HashSet
存储第一个文件的整数。 - 再次遍历第二个文件的相应分区,找到两个分区文件的交集。
- 对每个分区文件,使用
-
合并所有分区的交集(
findTotalIntersection
方法):- 遍历所有分区的交集,合并结果得到全局交集。
解法二:位图
思路:
1、遍历第一个文件,将第一个文件的每个数据读取出来,存放到bitSet当中
2、遍历第二个文件,每次读第一个数据,就看bitSet中,之前是否存在
3、如果存在,就是交集
2.3 交集、并集和差集
参考2.2中的解法2,其实我们可以发现,如果把两个文件中的数据都读取出来,存放到位图中,那两个位图去做按位与操作,就可以获取到交集
按位与操作,只有都是1才是1,那么这个时候对应位上是1,说明两个文件中都有该数据
相同的思路,如果我们对两个位图进行其他操作,可以得到其他集合;
比如两个位图按位或操作,就可以得到并集;两个集合按位异或操作,就可以得到差集
按位或,即两个位图的同一位置上,有一个1则整体都是1,那最后按位或后的位图,一个位置上有1,则说明对应数据在两个文件中至少有一个数据存在,该位图即为并集;
按位异或,即两个位图进行无进位加法操作,0的还是0,都是1的也变0,只有其中一个是1的还保留1,那最后得到的这个位图中的每一个1,都代表两个中只有其中一个有,即为差集
2.4 不超过2次的所有整数
题目:
位图应用变形:1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整数
解法1:哈希切割
同2.1哈希切割,把数据用哈希,分成多个小文件,然后分组统计最后汇总
解法2:使用两个位图
用两个位图的同一个位置,去维护四个状态,模拟一个2-bit位图;
00
:表示该整数从未出现。01
:表示该整数出现一次。10
:表示该整数出现两次。11
:表示该整数出现三次及以上。
先第一遍遍历,把数据的出现次数状态,存到两个位图中。
然后二次遍历,排除11
状态的,其他的全部计数
解法二代码示例:
import java.io.*;
import java.util.BitSet;public class FindIntegersWithMax2Occurrences {private static final int MAX_RANGE = Integer.MAX_VALUE; // 假设整数范围是0到2^31-1private BitSet bitSet1; // 第一个BitSetprivate BitSet bitSet2; // 第二个BitSetpublic FindIntegersWithMax2Occurrences(int maxRange) {// 初始化两个BitSetbitSet1 = new BitSet(maxRange);bitSet2 = new BitSet(maxRange);}/*** 更新整数的2-bit位图状态* @param num 整数*/private void updateBitSets(int num) {boolean bit1 = bitSet1.get(num);boolean bit2 = bitSet2.get(num);if (!bit1 && !bit2) {// 当前状态是00,更新为01bitSet1.set(num);} else if (bit1 && !bit2) {// 当前状态是01,更新为10bitSet1.clear(num);bitSet2.set(num);} else if (!bit1 && bit2) {// 当前状态是10,更新为11bitSet1.set(num);}// 如果是11,则保持不变}/*** 第一次遍历文件,更新2-bit位图的状态* @param inputFile 输入文件*/public void firstPass(String inputFile) throws IOException {BufferedReader reader = new BufferedReader(new FileReader(inputFile));String line;while ((line = reader.readLine()) != null) {int num = Integer.parseInt(line.trim());updateBitSets(num);}reader.close();}/*** 第二次遍历文件,找出出现次数不超过2次的整数* @param inputFile 输入文件*/public void secondPass(String inputFile) throws IOException {BufferedReader reader = new BufferedReader(new FileReader(inputFile));String line;System.out.println("出现次数不超过2次的整数:");while ((line = reader.readLine()) != null) {int num = Integer.parseInt(line.trim());boolean bit1 = bitSet1.get(num);boolean bit2 = bitSet2.get(num);// 如果状态是00、01或10,输出该整数if (!(bit1 && bit2)) {System.out.println(num);}}reader.close();}public static void main(String[] args) throws IOException {String inputFile = "large_numbers_file.txt"; // 假设大文件名为large_numbers_file.txtFindIntegersWithMax2Occurrences finder = new FindIntegersWithMax2Occurrences(MAX_RANGE);// 1. 第一次遍历文件,更新2-bit位图状态finder.firstPass(inputFile);// 2. 第二次遍历文件,找出出现次数不超过2次的整数finder.secondPass(inputFile);}
}
3. 布隆过滤器
精确算法和近似算法
题目:
给两个文件,分别有100亿个query,我们只有1G内存,如何找到两个文件交集?分别给出精确算法和近似算法
精确算法:
使用哈希切割,分别把两个文件,用相同的哈希方式,切割成同样份数的小文件,再用一一对应的小文件取交集,最后把所有交集汇总起来即可。
近似算法:
1、把第一个文件当中的query映射到布隆过滤器中
2、读取第二个文件,每个query都去布隆过滤器中查找
3、因为可能存在误判,所以这个数据有可能没有,但是被误判为了有。
4. 爬虫URL去重
题目:
多线程并发爬虫时,对url做去重,让你设计整个数据结构和核心方法,思考查询时间复杂度、空间复杂度等因素
可以使用 布隆过滤器(Bloom Filter) 结合 HashSet 来设计多线程并发爬虫中的 URL 去重方案。这种方案在保证较快的查询速度的同时,也能有效控制内存消耗。
1. 设计思路
- 布隆过滤器:用来判断某个 URL 是否可能已经存在,可以节省空间,但有一定的误判率。
- HashSet:在布隆过滤器判断为可能存在时进行二次确认,以确保去重的准确性。
2. 数据结构
- 布隆过滤器(BloomFilter):一个基于哈希的位数组,能够高效地判断元素是否存在,空间复杂度低。
add(url)
: 将 URL 添加到布隆过滤器中。mightContain(url)
: 检查 URL 是否可能存在。
- HashSet:存储经过布隆过滤器验证的 URL,用于消除误判。
contains(url)
: 检查 URL 是否已存在。add(url)
: 将 URL 添加到集合中。
3. 核心方法
以下是Java中实现的核心代码:
import java.util.BitSet;
import java.util.HashSet;
import java.util.concurrent.locks.ReentrantLock;public class UrlDeduplication {// 布隆过滤器位数组private final BitSet bloomFilter;// 位数组的大小private final int size;// HashSet用于消除误判private final HashSet<String> urlSet;// ReentrantLock实现线程安全private final ReentrantLock lock;// 哈希函数数量private static final int HASH_COUNT = 3;// 初始化布隆过滤器和HashSetpublic UrlDeduplication(int size) {this.size = size;this.bloomFilter = new BitSet(size);this.urlSet = new HashSet<>();this.lock = new ReentrantLock();}// 计算哈希值private int[] hash(String url) {int[] hashValues = new int[HASH_COUNT];for (int i = 0; i < HASH_COUNT; i++) {hashValues[i] = Math.abs(url.hashCode() + i) % size;}return hashValues;}// 添加URL到布隆过滤器和HashSetpublic void addUrl(String url) {int[] hashValues = hash(url);lock.lock(); // 加锁保证线程安全try {// 更新布隆过滤器for (int hash : hashValues) {bloomFilter.set(hash);}// 更新HashSeturlSet.add(url);} finally {lock.unlock(); // 解锁}}// 判断URL是否存在public boolean containsUrl(String url) {int[] hashValues = hash(url);lock.lock(); // 加锁保证线程安全try {// 检查布隆过滤器for (int hash : hashValues) {if (!bloomFilter.get(hash)) {return false; // 肯定不存在}}// 可能存在,再次确认return urlSet.contains(url);} finally {lock.unlock(); // 解锁}}
}
4. 时间和空间复杂度分析
- 时间复杂度
addUrl(url)
:- 布隆过滤器:O(k),其中 k 为哈希函数的数量。
- HashSet:O(1)。
- 总体:O(k) + O(1) ≈ O(1)。
containsUrl(url)
:- 布隆过滤器:O(k)。
- HashSet:O(1)。
- 总体:O(k) + O(1) ≈ O(1)。
- 空间复杂度
- 布隆过滤器:O(n),其中 n 为位数组的大小。
- HashSet:O(m),其中 m 为实际存储的 URL 数量。
- 总体:O(n + m)。
5. 优点和局限性
- 优点
- 具有较高的空间效率,能够有效去重。
- 支持多线程并发访问。
- 局限性
- 布隆过滤器可能产生误判,需要使用 HashSet 进行二次确认。
- 随着数据量增加,HashSet 的内存占用会变大。
这种方案在多线程并发环境中较为高效,可以在爬虫系统中实现较好的性能与去重效果。
⭐️最后的话⭐️
总结不易,希望uu们不要吝啬你们的👍哟(^U^)ノ~YO!!如有问题,欢迎评论区批评指正😁
相关文章:

海量数据面试题
⭐️前言⭐️ 本篇文章主要针对在面试时可能涉及到的海量数据的面试题,该类型面试题常常考虑通过位图、布隆过滤器或者哈希的方式来解决。 🍉欢迎点赞 👍 收藏 ⭐留言评论 🍉博主将持续更新学习记录收获,友友们有任何…...

基于SSM积分商城管理系统的设计与实现(源码+lw+部署文档+讲解等)
前言 伴随着基础网络设施的不断进步和终端电子设备的高度普及,互联网用户规模越来越大。现在人们越来越离不开计算机网络、互联网所带来的好处了,现如今不同的网站系统遍地都是,现在已经不同于以往的传统的管理方式了,只有跟上时代…...
MLP预售开启,革新去中心化通信生态:智能手机与AI Agent齐上阵
2024年10月22日,Matrix Layer Protocol(MLP)宣布其备受期待的第一期产品正式进入预售阶段。随着Web3世界的不断发展,去中心化技术已经深入到我们日常生活的方方面面。作为Web3世界中炙手可热的创新项目,Matrix Layer P…...
js获取浏览器指纹
Canvas指纹法 来源:https://www.cnblogs.com/leijing0607/p/8044218.html 从根本上来说,每一种浏览器都会使用不同的图像处理引擎,不同的导出选项,不同的压缩等级,所以每一台电脑绘制出的图形都会有些许不同…...
乐尚代驾的项目问题
订单状态如果在流转的过程中卡住了,怎么办? 卡住的原因有可能是: 网络问题 网络不稳定或中断可能导致订单状态更新的请求无法及时发送或接收。例如,司机端在更新代驾车辆信息时,如果网络出现故障,可能无法…...
uniapp app.onshow 和 onMounted一样用吗
在uni-app中,onShow和onMounted并不完全相同,它们分别属于应用生命周期和组件生命周期。 应用生命周期中的onShow 在uni-app中,onShow是应用生命周期的一部分,它会在应用启动或从后台进入前台时触发。这意味着它不仅仅局限于页…...

基于Mysql、JavaScript、PHP、ajax开发的MBTI性格测试网站(前端+后端)
源码地址:https://download.csdn.net/download/2302_79553009/89933699 项目简介 本项目旨在构建一个基于MBTI(迈尔斯-布里格斯性格分类指标)理论的在线平台——“16Personalities”。该平台利用PHP、MySQL、JavaScript等技术栈开发…...

【问题解决】连接mysql时报错caching_sha2_password can not load
一, 问题 在连接Mysql时报错, caching_sha2_password can not load 二,问题原因 报错信息 "caching_sha2_password can not load" 通常出现在尝试连接到使用 MySQL 8.0 或更高版本的数据库时,因为从 MySQL 8.0 开始&a…...

【瑞吉外卖】-day01
目录 前言 第一天项目启动 获取资料 创建项目 编辑 连接本地数据库 连接数据库 修改用户名和密码 编辑创建表 创建启动类来进行测试 导入前端页面 创建项目所需目录 检查登录功能 登录界面 登录成功 登录失败 代码 退出功能 易错点 前言 尝试一下企业级项…...

钉钉与金蝶云星空数据集成:提高企业付款申请单处理效率
钉钉数据集成到金蝶云星空:付款申请单的自动下推生成 在企业日常运营中,如何高效地管理和处理付款申请单是一个关键问题。为了提升这一流程的效率,我们采用了轻易云数据集成平台,将钉钉中的付款申请单数据无缝对接到金蝶云星空系…...

GIT使用list
清空当前commit区 方法 1:软重置到初始状态 如果希望保留文件内容,但清空所有 commit 历史,可以使用以下命令: git reset --soft $(git rev-list --max-parents0 HEAD)解释: --soft 表示重置 commit 历史ÿ…...
JavaSE:数组深入学习与复习
学习参考 1、可变参数传递 数组可以是int等基本数据类型,也可以是String等引用类型 package com.test;public class Main {public static void main(String [] args){int [] a {1,2,3,4,5};test(78,90,12,34,56,78,90,12,34,56,78);}public static void test(i…...

Redis 事务 总结
前言 相关系列 《Redis & 目录》(持续更新)《Redis & 事务 & 源码》(学习过程/多有漏误/仅作参考/不再更新)《Redis & 事务 & 总结》(学习总结/最新最准/持续更新)《Redis & 事务…...
go sdk的安装或者升级
背景 由于 go 语言的官方sdk还在不断的更新迭代中,有的时候相对应的生态相关的依赖包也在不断的升级,如果很长一段时间不升级自己的本地的go sdk 那么就有可能在拉取代码的时候出现错误,因此有的时候可能需要我们适当的升级下自己的sdk&…...
mongo实操笔记
这个链接我用了其在Windows下的下载安装 是可以的 ,不过我太懒了,没有弄成自启动 Windows安装MongoDB_mongodb windows安装-CSDN博客 下面这个链接就更好了,我用了其与springboot整合的测试。可以直接操作mongodb了。 SpringBoot整合Mongo…...

前端算法:树(力扣144、94、145、100、104题)
目录 一、树(Tree) 1.介绍 2.特点 3.基本术语 4.种类 二、树之操作 1.遍历 前序遍历(Pre-order Traversal):访问根节点 -> 遍历左子树 -> 遍历右子树。 中序遍历(In-order Traversal…...
深度学习速通系列:如何使用bert进行超长中文文本命名实体识别
要将超长中文文本按最大 BERT 输入长度进行分割,并使用 bert-chinese-ner 模型进行命名实体识别,可以遵循以下步骤。以下是一个 Python 代码示例,利用 Hugging Face 的 transformers 库来实现: 安装必要的库 如果你还没有安装 Hu…...
【感知模块】深度神经网络实现运动预测
系列文章目录 提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加 TODO:写完再整理 文章目录 系列文章目录前言运动预测(Motion Prediction)感知中的运动预测(深度神经网络)前言 认知有限,望大家多多包涵,有什么问题也希望能够与大家多交流,共同成长! …...

智能优化算法-蝗虫优化算法(GOA)(附源码)
目录 1.内容介绍 2.部分代码 3.实验结果 4.内容获取 1.内容介绍 蝗虫优化算法 (Grasshopper Optimization Algorithm, GOA) 是一种基于群体智能的元启发式优化算法,由Saremi等人于2017年提出。GOA模拟了蝗虫群的觅食、迁徙和社会互动行为,用于解决复杂…...

TVM前端研究--Relay
文章目录 深度学习IR梳理1. IR属性2. DL前端发展3. DL编译器4. DL编程语言Relay的主要内容一、Expression in Relay1. Dataflow and Control Fragments2. 变量3. 函数3.1 闭包3.2 多态和类型关系3.3. Call4. 算子5. ADT Constructors6. Moudle和Global Function7. 常量和元组8.…...

网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...

多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...

简易版抽奖活动的设计技术方案
1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...
React Native 开发环境搭建(全平台详解)
React Native 开发环境搭建(全平台详解) 在开始使用 React Native 开发移动应用之前,正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南,涵盖 macOS 和 Windows 平台的配置步骤,如何在 Android 和 iOS…...

【入坑系列】TiDB 强制索引在不同库下不生效问题
文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现录音机应用
1. 项目配置与权限设置 1.1 配置module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.MICROPHONE","reason": "录音需要麦克风权限"},{"name": "ohos.permission.WRITE…...

C++使用 new 来创建动态数组
问题: 不能使用变量定义数组大小 原因: 这是因为数组在内存中是连续存储的,编译器需要在编译阶段就确定数组的大小,以便正确地分配内存空间。如果允许使用变量来定义数组的大小,那么编译器就无法在编译时确定数组的大…...
41道Django高频题整理(附答案背诵版)
解释一下 Django 和 Tornado 的关系? Django和Tornado都是Python的web框架,但它们的设计哲学和应用场景有所不同。 Django是一个高级的Python Web框架,鼓励快速开发和干净、实用的设计。它遵循MVC设计,并强调代码复用。Django有…...

Excel 怎么让透视表以正常Excel表格形式显示
目录 1、创建数据透视表 2、设计 》报表布局 》以表格形式显示 3、设计 》分类汇总 》不显示分类汇总 1、创建数据透视表 2、设计 》报表布局 》以表格形式显示 3、设计 》分类汇总 》不显示分类汇总...

代理服务器-LVS的3种模式与调度算法
作者介绍:简历上没有一个精通的运维工程师。请点击上方的蓝色《运维小路》关注我,下面的思维导图也是预计更新的内容和当前进度(不定时更新)。 我们上一章介绍了Web服务器,其中以Nginx为主,本章我们来讲解几个代理软件:…...