海量数据面试题
⭐️前言⭐️
本篇文章主要针对在面试时可能涉及到的海量数据的面试题,该类型面试题常常考虑通过位图、布隆过滤器或者哈希的方式来解决。
🍉欢迎点赞 👍 收藏 ⭐留言评论
🍉博主将持续更新学习记录收获,友友们有任何问题可以在评论区留言
🍉博客中涉及源码及博主日常练习代码均已上传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.…...
STM32外设应用
STM32是基于ARM Cortex-M系列内核的微控制器,具有高性能、低功耗和丰富的外设资源。其广泛应用于物联网、工业控制、智能家居和嵌入式系统等领域。本文将简要介绍STM32常用外设的功能及应用实例,帮助大家更好地理解和使用STM32外设。 1. GPIO࿰…...
Docker 部署 Jaeger
Jaeger 的主要作用如下: 分布式追踪 Jaeger 是一个开源的分布式追踪系统,用于监控和排查微服务架构中的复杂问题。它可以跟踪请求在不同服务之间的传播路径,帮助开发者理解系统中各个组件之间的调用关系。 性能分析 通过收集和分析请求的执行…...
使用Python和OpenCV实现火焰检测
使用Python和OpenCV实现火焰检测 项目解释: 此 Python 代码是使用 OpenCV、线程、声音和电子邮件功能的火灾探测系统的简单示例。 以下是它的功能的简单描述: 导入库:代码首先导入必要的库: cv2:用于图像和视频处理…...
uniapp基础笔记
与html区别 uni-app简单来说是 vue的语法 小程序的api。 文件结构 html <!DOCTYPE html> <html><head><meta charset"utf-8" /><title></title><script type"text/javascript"></script><style t…...
函数基础,定义与调用。作用域,闭包函数
一、函数的定义与调用 函数是一段可重复使用的代码块,用于执行特定任务或计算等功能。它可以接受输入参数(形参),并根据参数执行操作后返回结果。 函数的定义 例如在 JavaScript 中可以这样定义函数: function fun…...
【Linux网络编程】 --- Linux权限理解
Welcome to 9ilks Code World (๑•́ ₃ •̀๑) 个人主页: 9ilk (๑•́ ₃ •̀๑) 文章专栏: Linux网络编程 🏠 shell命令以及运行原理 📌 引入例子理解shell 假设八里村有一个人叫张三,他的父亲是这个村的村长…...
Qt/C++ 调用迅雷开放下载引擎(ThunderOpenSDK)下载数据资源
目录导读 前言ThunderOpenSDK 简介参考 xiaomi_Thunder_Cloud 示例ThunderOpenSDK 下载问题 前言 在对以前老版本的exe执行程序进行研究学习的时候,发现以前的软件是使用的ThunderOpenSDK这个迅雷开放下载引擎进行的项目数据下载,于是在网上搜索一番找到…...
深入详解 Java - Spring MVC
在 Java 企业级开发领域,Spring MVC 是一个极为重要的框架,它为构建强大、灵活且高效的 Web 应用程序提供了坚实的基础。本文将深入详解 Java 之 Spring MVC,带你领略其强大之处。 一、Spring MVC 概述 Spring MVC 是 Spring 框架的一个重要模块,全称为 Spring Web Model-V…...
Spring Boot技术中小企业设备管理系统设计与实践
6系统测试 6.1概念和意义 测试的定义:程序测试是为了发现错误而执行程序的过程。测试(Testing)的任务与目的可以描述为: 目的:发现程序的错误; 任务:通过在计算机上执行程序,暴露程序中潜在的错误。 另一个…...
动态渲染组件
引言 在现代前端开发中,动态渲染组件是一种常见的需求,特别是在构建复杂的应用程序时。动态渲染组件允许我们在运行时根据不同的条件或数据来决定渲染哪个组件,从而提高代码的灵活性和可维护性。本文将详细介绍如何在 Vue.js 中实现动态渲染…...
网站建设公司取名/网络信息发布平台
来自:http://www.think-in-g.net/ghawk/blog/2012/02/decoupling-view-controllers-with-key-value-observing/ 首 先,将数据容器剥离到控制器以外。其次,将各个控制器之间的依赖关系切断,在控制器初始化后,通过KVO机制…...
做中介平台网站 需要什么/百度上的广告多少钱一个月
使用场景 提针对零售类公司企业、费用报销和资产招采、经营物资招采、合同招标等共用系统的场景。将业务拆解为申请、采购、入库 3大环节,为跨部门合规管理和 IT 运营提供了便利性。 典型用途:IT 人员监控全流程,各个业务部门按需操作自己的…...
西宁网站制作哪家公司好/制作网页代码大全
. . . 代码展示如下: #include<stdio.h> int main() {int a,b,i,j,k,m;scanf("%d%d",&a,&b);if(a>b){//判断输入的数据谁大谁小ib;ka;} else{ia;kb;}mi;//最大公约数for(i;i>0;i--)//从最小的数开始往0递减if(a%i0 && b%i0)…...
五分钟wordpress/定制网站制作公司
直接在UI线程中开启子线程来更新TextView显示的内容,运行程序我们会发现,如下错 误:android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.翻译过来就是&…...
蓝色网站导航/八上数学优化设计答案
安装git,配置环境变量path,git安装目录下的bin路径和usr/bin路径 注册gitee,用英文 1新建私有仓库名,用英文,仓库介绍可以填一下,点确定 任意页面右键点击git bash here 2配置email和name信息: …...
网站建设与规划专业/淘宝seo是什么意思啊
一: 调出文字工具栏 菜单栏“文字” -> “面板” -> "字符面板"转载于:https://www.cnblogs.com/gavinwu/p/3545194.html...