海量数据面试题

请添加图片描述

⭐️前言⭐️

本篇文章主要针对在面试时可能涉及到的海量数据的面试题,该类型面试题常常考虑通过位图、布隆过滤器或者哈希的方式来解决。

🍉欢迎点赞 👍 收藏留言评论

🍉博主将持续更新学习记录收获,友友们有任何问题可以在评论区留言

🍉博客中涉及源码及博主日常练习代码均已上传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、初始化两个位图bitMap1bitMap2
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内存,如何找到两个文件交集?

解法一:哈希切割

思路概述
我们可以将两个大文件进行哈希切割,将整数划分成多个较小的分区,每个分区的数据量可以控制在内存可处理的范围内。然后在每个分区内使用位图或哈希表计算交集。

实现步骤

  1. 哈希切割
  • 首先对两个文件进行哈希切割,将大文件划分成多个较小的分区,每个分区可以独立处理。
  • 通过哈希函数,将相同的整数映射到相同的对应分区文件中,这样可以确保相同的整数出现在相同的分区。
  1. 统计交集
  • 针对每个分区文件,使用位图(BitMap)哈希表来存储和查找交集。
  1. 合并结果
  • 对每个分区的结果进行汇总,得到全局的交集。

具体代码实现(Java)

代码说明

  1. 使用哈希切割分区文件
  2. 在分区文件中寻找交集
  3. 合并各个分区的交集
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);}}
}

代码解释

  1. 哈希切割(partitionFile方法)

    • 将大文件划分为多个较小的分区文件。
    • 根据整数的哈希值,将其分配到不同的分区文件中,确保相同的整数会被分配到相同的分区。
  2. 查找分区交集(findIntersectionInPartition方法)

    • 对每个分区文件,使用HashSet存储第一个文件的整数。
    • 再次遍历第二个文件的相应分区,找到两个分区文件的交集。
  3. 合并所有分区的交集(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!!如有问题,欢迎评论区批评指正😁

请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/58256.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

pytorh学习笔记——cifar10(九)使用torhvision的标准resnet模型

之前的demo都是模仿和简化了已有的模型&#xff0c;也可以直接调用orhvision的标准模型&#xff0c;代码将更加简单。 新建resnet18.py import torch.nn as nn from torchvision import modelsclass ResNet18(nn.Module):def __init__(self, num_classes10):super(ResNet18, …

json与python中字典的互相转化

json的定义 JSON &#xff08;JavaScript Object Notation&#xff09; &#xff0c;是一种轻量级的数据交换格式。它的使用范围很广&#xff0c;并成为 ECMA 标准&#xff0c;可以被使用在多种编程语言中&#xff0c;用于前后端之间的数据传输、存储和交换数据。可以说是“用…

如何快速分析音频中的各种频率成分

从视频中提取音频 from moviepy.editor import VideoFileClip# Load the video file and extract audio video_path "/mnt/data/WeChat_20241026235630.mp4" video_clip VideoFileClip(video_path)# Extract audio and save as a temporary file for further anal…

Rust 力扣 - 5. 最长回文子串

文章目录 题目描述题解思路题解代码题解链接 题目描述 题解思路 从中心点先寻找和中心点相等的左右端点&#xff0c;在基于左右端点进行往外扩散&#xff0c;直至左右端点不相等或者越界&#xff0c;然后左右端点这个范围内就是我们找寻的回文串&#xff0c;我们遍历中心点&am…

在Java中,需要每120分钟刷新一次的`assetoken`,并且你想使用Redis作为缓存来存储和管理这个令牌

学习总结 1、掌握 JAVA入门到进阶知识(持续写作中……&#xff09; 2、学会Oracle数据库入门到入土用法(创作中……&#xff09; 3、手把手教你开发炫酷的vbs脚本制作(完善中……&#xff09; 4、牛逼哄哄的 IDEA编程利器技巧(编写中……&#xff09; 5、面经吐血整理的 面试技…

macOS 15 Sequoia dmg格式转用于虚拟机的iso格式教程

想要把dmg格式转成iso格式&#xff0c;然后能在虚拟机上用&#xff0c;最起码新版的macOS镜像是不能用UltraISO&#xff0c;dmg2iso这种软件了&#xff0c;你直接转放到VMware里绝对读不出来&#xff0c;办法就是&#xff0c;在Mac系统中转换为cdr&#xff0c;然后再转成iso&am…

Unity3D学习FPS游戏(3)玩家第一人称视角转动和移动

前言&#xff1a;上一篇实现了角色简单的移动控制&#xff0c;但是实际游戏中玩家的视角是可以转动的&#xff0c;并根据转动后视角调整移动正前方。本篇实现玩家第一人称视角转动和移动&#xff0c;觉得有帮助的话可以点赞收藏支持一下&#xff01; 玩家第一人称视角 修复小问…

NAT技术和代理服务器

NAT IP原理 之前我们讨论了, IPv4协议中, IP地址数量不充足的问题 NAT技术当前解决IP地址不够用的主要手段, 是路由器的一个重要功能;NAT能够将私有IP对外通信时转为全局IP. 也就是就是一种将私有IP和全局IP相互转化的技术方法:很多学校, 家庭, 公司内部采用每个终端设置私有…

批处理操作的优化

原来的代码 Override Transactional(rollbackFor Exception.class) public void batchAddQuestionsToBank(List<Long> questionIdList, Long questionBankId, User loginUser) {// 参数校验ThrowUtils.throwIf(CollUtil.isEmpty(questionIdList), ErrorCode.PARAMS_ERR…

2023IKCEST第五届“一带一路”国际大数据竞赛--社交网络中多模态虚假 媒体内容核查top11

比赛链接&#xff1a;https://aistudio.baidu.com/competition/detail/1030/0/introduction PPT链接&#xff1a;https://www.ikcest.org/bigdata2024/zlxz/list/page.html 赛题 社交网络中多模态虚假媒体内容核查 背景 随着新媒体时代信息媒介的多元化发展&#xff0c;各种内容…

GitHub Star 数量前 5 的开源应用程序生成器

欢迎来的 GitHub Star 数量排名系列文章的第 7 篇——最受欢迎的应用程序生成器。 之前我们已经详细探讨过&#xff1a;在 GitHub 上最受欢迎的——无代码工具、低代码项目、内部工具、CRUD项目、自部署项目和 Airtable 开源替代品。累计超过 50 个优质项目&#xff01;&#…

橘子多开同步器 v6.0 免费版

下载&#xff1a; 【1】https://drive.uc.cn/s/ddb0774e92924?public1 【2】https://pan.quark.cn/s/b5b1aae8c331 橘子多开同步器是一款专门为了游戏工作室而打造的免费游戏客户端多开同步工具&#xff0c;涵盖了包括客户端多开、客户端键鼠同步、智能防封等功能。 功能介…

Linux 进程优先级 进程切换

目录 优先级 概念 为什么优先级要限制在一定范围内 进程切换 方式 EIP寄存器(程序计数器) 进程在运行时会使用寄存器来保存临时数据 进程的上下文是什么&#xff1f; 进程的上下文保存到哪&#xff1f; 内核栈或专门的上下文结构也在内核空间&#xff1f;那为什么不直…

海外逆向代购:新机遇下的跨境赚钱之道

所谓逆向代购&#xff0c;即利用海外客源&#xff0c;将中国的优质商品反向代购至海外市场&#xff0c;实现跨境赚钱的同时&#xff0c;也让更多中国商品走向世界。 近年来&#xff0c;随着中国经济的飞速发展和消费水平的不断提升&#xff0c;中国商品在全球市场上的认可度越来…

一个简单的例子,说明Matrix类的妙用

在Android、前端或者别的平台的软件开发中&#xff0c;有时会遇到类似如下需求&#xff1a; 将某个图片显示到指定的区域&#xff1b;要求不改变图片本身的宽高比&#xff0c;进行缩放&#xff1b;要求最大限度的居中填充到显示区域。 以下示意图可以简单描绘该需求 以Androi…

ETL、ELT和反向ETL都有什么不同?怎么选择?

数据处理是现代企业中不可或缺的一部分。随着数据量的不断增长&#xff0c;如何高效地处理、转换和加载数据变得尤为重要。本文将介绍三种常见的数据处理方式&#xff1a;ETL、ELT和反向ETL&#xff0c;帮助读者更好地理解和选择适合自己业务需求的方式。 一、ETL 定义&#…

深入理解 SQL 中的 WITH AS 语法

在日常数据库操作中&#xff0c;SQL 语句的复杂性往往会影响到查询的可读性和维护性。为了解决这个问题&#xff0c;Oracle 提供了 WITH AS 语法&#xff0c;这一功能可以极大地简化复杂查询&#xff0c;提升代码的清晰度。本文将详细介绍 WITH AS 的基本用法、优势以及一些实际…

面对复杂的软件需求:5大关键策略!

面对软件需求来源和场景的复杂性&#xff0c;有效地管理和处理需求资料是确保项目成功的关键&#xff0c;能够提高需求理解的准确性&#xff0c;增强团队协作和沟通&#xff0c;降低项目风险&#xff0c;提高开发效率。反之&#xff0c;项目可能面临需求理解不准确、团队沟通不…

Lucas带你手撕机器学习——套索回归

好的&#xff0c;下面我将详细介绍套索回归的背景、理论基础、实现细节以及在实践中的应用&#xff0c;同时还会讨论其优缺点和一些常见问题。 套索回归&#xff08;Lasso Regression&#xff09; 1. 背景与动机 在机器学习和统计学中&#xff0c;模型的复杂性通常会影响其在…

从简单的demo开始让您逐步了解GetX的用法

目录 前言 一、从demo开始体现下Getx的用法 二、从最简单的功能开始 1.新建一个Flutter工程 2.GetX初体验 1.路由跳转 1.普通路由跳转 2.跳转并从堆栈中销毁当前页面 3.跳转并销毁之前所有页面 4.跳转以及传值 2.更方便的实现SnackBar、Dialog、BottomSheet 三、Ge…