[中间件~大厂面试题] 腾讯三面,40亿的QQ号如何去重

前言: 在Spring Boot框架下,可以使用以下方法来去重40亿个QQ号.请注意:QQ号码的理论最大值为 2 32 − 1 2^{32} - 1 2321,大概是43亿左右。

文章目录

  • 提前总结(总分总~~~)
  • 最粗鲁的方式
    • 1. 使用HashSet去重:
    • 2. 使用Java 8的Stream去重:
    • 3. 使用数据库的去重功能:
  • 限制1GB内存,文件的方式
    • 4. 文件分片
    • 5. 外部排序算法
  • 使用中间件 redis
    • 6. bitmap
    • 7. 布隆过滤器
  • 分析一下布隆过滤器以及bitmap存储40亿个QQ号需要的内存
    • 布隆过滤器:
    • 位图(Bitmap):
  • 总结
    • 1. 使用 HashSet 去重:
    • 2. 使用 Java 8 的 Stream 去重:
    • 3. 使用数据库的去重功能:
    • 4. 文件分片:
    • 5. 外部排序算法:
    • 6. 位图(Bitmap):
    • 7. 布隆过滤器:

提前总结(总分总~~~)

  • 如果限制在1GB内存,并且不依赖外部存储或中间件, HashSetJava 8 Stream 都无法满足要求。
  • 文件分片和外部排序算法可以适应1GB内存限制,但涉及到额外的文件操作和排序步骤。
  • 使用数据库的去重功能可能需要额外的存储开销。
  • Redis作为中间件可以满足内存限制,但需要依赖Redis服务。
  • 布隆过滤器是一种适用于大规模数据去重的高效数据结构,可以根据预期插入数量和误判率进行内存估算。

最粗鲁的方式

1. 使用HashSet去重:

Set<String> qqSet = new HashSet<>();
// 假设qqList是包含40亿个QQ号的列表
for (String qq : qqList) {qqSet.add(qq);
}
List<String> distinctQQList = new ArrayList<>(qqSet);

2. 使用Java 8的Stream去重:

List<String> distinctQQList = qqList.stream().distinct().collect(Collectors.toList());

3. 使用数据库的去重功能:

  1. 首先,创建一个数据库表来存储QQ号,可以使用MySQL或其他关系型数据库。
  2. 将40亿个QQ号逐个插入数据库表中,数据库会自动去重。
  3. 最后,从数据库中读取去重后的QQ号列表:
// 假设使用Spring Data JPA进行数据库操作
@Repository
public interface QQRepository extends JpaRepository<QQEntity, Long> {List<QQEntity> findAll();
}// 在业务逻辑中使用QQRepository获取去重后的QQ号列表
List<QQEntity> distinctQQList = qqRepository.findAll();

限制1GB内存,文件的方式

4. 文件分片

  1. 将40亿个QQ号分成多个较小的分段,每个分段可以包含一定数量的QQ号。
  2. 针对每个分段,使用HashSet进行去重,但是只在内存中保留部分分段的去重结果,而不是全部。
  3. 将每个分段的去重结果写入磁盘文件,以释放内存空间。
  4. 对下一个分段进行去重操作,重复上述步骤,直到处理完所有分段。
  5. 最后,将所有磁盘文件中的去重结果合并成最终的去重结果。
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;public class QQDuplicateRemover {private static final int MAX_MEMORY_USAGE_MB = 1024; // 最大内存使用限制,单位:MBprivate static final int MAX_SEGMENT_SIZE = 1000000; // 每个分段的最大数量public static void main(String[] args) {// 假设qqList是包含40亿个QQ号的列表List<String> qqList = loadQQList();// 分段处理int segmentCount = (int) Math.ceil((double) qqList.size() / MAX_SEGMENT_SIZE);List<File> segmentFiles = new ArrayList<>();for (int i = 0; i < segmentCount; i++) {List<String> segment = qqList.subList(i * MAX_SEGMENT_SIZE, Math.min((i + 1) * MAX_SEGMENT_SIZE, qqList.size()));Set<String> segmentSet = new HashSet<>(segment);segment.clear(); // 释放内存File segmentFile = writeSegmentToFile(segmentSet);segmentFiles.add(segmentFile);segmentSet.clear();}// 合并去重结果List<String> distinctQQList = mergeSegments(segmentFiles);// 输出去重后的QQ号列表for (String qq : distinctQQList) {System.out.println(qq);}}// 加载40亿个QQ号列表的示例方法private static List<String> loadQQList() {// TODO: 实现加载40亿个QQ号列表的逻辑,返回List<String>return new ArrayList<>();}// 将分段的去重结果写入磁盘文件private static File writeSegmentToFile(Set<String> segmentSet) {File segmentFile;BufferedWriter writer = null;try {segmentFile = File.createTempFile("qq_segment_", ".txt");writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(segmentFile), StandardCharsets.UTF_8));for (String qq : segmentSet) {writer.write(qq);writer.newLine();}} catch (IOException e) {throw new RuntimeException("Failed to write segment to file.", e);} finally {if (writer != null) {try {writer.close();} catch (IOException e) {e.printStackTrace();}}}return segmentFile;}// 合并分段文件并去重private static List<String> mergeSegments(List<File> segmentFiles) {List<String> distinctQQList = new ArrayList<>();PriorityQueue<BufferedReader> fileReaders = new PriorityQueue<>(Comparator.comparing(QQDuplicateRemover::readNextLine));for (File segmentFile : segmentFiles) {try {BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(segmentFile), StandardCharsets.UTF_8));String line = reader.readLine();if (line != null) {fileReaders.offer(reader);}} catch (IOException e) {throw new RuntimeException("Failed to read segment file: " + segmentFile.getName(), e);}}String currentQQ = null;while (!fileReaders.isEmpty()) {BufferedReader reader = fileReaders.poll();String qq = readNextLine(reader);if (!qq.equals(currentQQ)) {distinctQQList.add(qq);currentQQ = qq;}String nextQQ = readNextLine(reader);if (nextQQ != null) {fileReaders.offer(reader);} else {try {reader.close();} catch (IOException e) {e.printStackTrace();}}}return distinctQQList;}// 读取下一行数据private static String readNextLine(BufferedReader reader) {try {return reader.readLine();} catch (IOException e) {throw new RuntimeException("Failed to read line from segment file.", e);}}
}

这个示例代码中,使用了分段处理的思路。首先,将40亿个QQ号划分为较小的分段,并使用HashSet进行去重。然后,将每个分段的去重结果写入临时文件,以释放内存。最后,合并所有分段文件并去重,得到最终的去重结果。

请注意,示例代码中的loadQQList()方法是一个占位方法,需要根据实际情况实现另外,这个示例代码中使用了临时文件来存储分段的去重结果。在实际应用中,你可能需要根据需求进行适当的修改,例如指定输出文件路径、处理异常情况等。此外,由于内存限制为1GB,你可能需要根据实际情况调整MAX_SEGMENT_SIZE的大小,以确保每个分段在内存中的大小不超过限制。

这只是一个基本的示例代码,供你参考和理解分段处理的思路。根据实际需求和具体情况,你可能需要进行更多的优化和改进。

5. 外部排序算法

  1. 将40亿个QQ号划分为多个小文件,每个文件包含一部分QQ号。
  2. 对每个小文件进行内部排序,可以使用归并排序等算法。
  3. 合并排序后的小文件,使用归并排序的思想,逐步合并文件,同时去除重复的QQ号。
  4. 最终得到去重后的结果。
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;public class QQDuplicateRemover {private static final int MAX_MEMORY_USAGE_MB = 1024; // 最大内存使用限制,单位:MBprivate static final int MAX_SEGMENT_SIZE = 1000000; // 每个分段的最大数量public static void main(String[] args) {// 假设qqList是包含40亿个QQ号的列表List<String> qqList = loadQQList();// 外部排序去重List<File> sortedSegmentFiles = externalSort(qqList);// 合并去重结果List<String> distinctQQList = mergeSortedSegments(sortedSegmentFiles);// 输出去重后的QQ号列表for (String qq : distinctQQList) {System.out.println(qq);}}// 加载40亿个QQ号列表的示例方法private static List<String> loadQQList() {// TODO: 实现加载40亿个QQ号列表的逻辑,返回List<String>return new ArrayList<>();}// 外部排序算法private static List<File> externalSort(List<String> qqList) {List<File> segmentFiles = new ArrayList<>();int segmentCount = (int) Math.ceil((double) qqList.size() / MAX_SEGMENT_SIZE);// 将数据分段并排序for (int i = 0; i < segmentCount; i++) {List<String> segment = qqList.subList(i * MAX_SEGMENT_SIZE, Math.min((i + 1) * MAX_SEGMENT_SIZE, qqList.size()));Collections.sort(segment);File segmentFile = writeSegmentToFile(segment);segmentFiles.add(segmentFile);}// 逐步合并排序后的分段文件while (segmentFiles.size() > 1) {List<File> mergedSegmentFiles = new ArrayList<>();for (int i = 0; i < segmentFiles.size(); i += 2) {File segmentFile1 = segmentFiles.get(i);File segmentFile2 = (i + 1 < segmentFiles.size()) ? segmentFiles.get(i + 1) : null;File mergedSegmentFile = mergeSegments(segmentFile1, segmentFile2);mergedSegmentFiles.add(mergedSegmentFile);}segmentFiles = mergedSegmentFiles;}return segmentFiles;}// 将分段数据写入临时文件private static File writeSegmentToFile(List<String> segment) {File segmentFile;BufferedWriter writer = null;try {segmentFile = File.createTempFile("qq_segment_", ".txt");writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(segmentFile), StandardCharsets.UTF_8));for (String qq : segment) {writer.write(qq);writer.newLine();}} catch (IOException e) {throw new RuntimeException("Failed to write segment to file.", e);} finally {if (writer != null) {try {writer.close();} catch (IOException e) {e.printStackTrace();}}}return segmentFile;}// 合并两个分段文件private static File mergeSegments(File segmentFile1, File segmentFile2) {BufferedReader reader1 = null;BufferedReader reader2 = null;BufferedWriter writer = null;File mergedSegmentFile;try {mergedSegmentFile = File.createTempFile("qq_segment_", ".txt");reader1 = new BufferedReader(new InputStreamReader(new FileInputStream(segmentFile1), StandardCharsets.UTF_8));reader2 = new BufferedReader(new InputStreamReader(new FileInputStream(segmentFile2), StandardCharsets.UTF_8));writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(mergedSegmentFile), StandardCharsets.UTF_8));String qq1 = reader1.readLine();String qq2 = reader2.readLine();while (qq1 != null && qq2 != null) {if (qq1.compareTo(qq2) < 0) {writer.write(qq1);writer.newLine();qq1 = reader1.readLine();} else if (qq1.compareTo(qq2) > 0) {writer.write(qq2);writer.newLine();qq2 = reader2.readLine();} else {writer.write(qq1);writer.newLine();qq1 = reader1.readLine();qq2 = reader2.readLine();}}// 处理文件剩余的数据while (qq1 != null) {writer.write(qq1);writer.newLine();qq1 = reader1.readLine();}while (qq2 != null) {writer.write(qq2);writer.newLine();qq2 = reader2.readLine();}} catch (IOException e) {throw new RuntimeException("Failed to merge segment files.", e);} finally {try {if (reader1 != null) {reader1.close();}if (reader2 != null) {reader2.close();}if (writer != null) {writer.close();}} catch (IOException e) {e.printStackTrace();}}return mergedSegmentFile;}// 合并排序后的分段文件并去重private static List<String> mergeSortedSegments(List<File> segmentFiles) {List<String> distinctQQList = new ArrayList<>();PriorityQueue<BufferedReader> fileReaders = new PriorityQueue<>(Comparator.comparing(QQDuplicateRemover::readNextLine));for (File segmentFile : segmentFiles) {try {BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(segmentFile), StandardCharsets.UTF_8));String line = reader.readLine();if (line != null) {fileReaders.offer(reader);}} catch (IOException e) {throw new RuntimeException("Failed to read segment file: " + segmentFile.getName(), e);}}String currentQQ = null;while (!fileReaders.isEmpty()) {BufferedReader reader = fileReaders.poll();String qq = readNextLine(reader);if (!qq.equals(currentQQ)) {distinctQQList.add(qq);currentQQ = qq;}String nextQQ = readNextLine(reader);if (nextQQ != null) {fileReaders.offer(reader);} else {try {reader.close();} catch (IOException e) {e.printStackTrace();}}}return distinctQQList;}// 读取下一行数据private static String readNextLine(BufferedReader reader) {try {return reader.readLine();} catch (IOException e) {throw new RuntimeException("Failed to read line from segment file.", e);}}
}

这个示例代码中使用了外部排序算法来处理40亿个QQ号的去重问题。首先,将数据分段,并在内存中对每个分段进行排序。然后,逐步合并排序后的分段文件,直到只剩下一个文件为止。最后,对合并后的分段文件进行去重操作,得到最终的去重结果。

请注意,示例代码中的loadQQList()方法是一个占位方法,需要根据实际情况实现另外,这个示例代码中使用了临时文件来存储分段和合并后的结果。在实际应用中,你可能需要根据需求进行适当的修改,例如指定输出文件路径、处理异常情况等。此外,由于内存限制为1GB,你可能需要根据实际情况调整MAX_SEGMENT_SIZE的大小,以确保每个分段在内存中的大小不超过限制。

这只是一个基本的示例代码,供你参考和理解外部排序算法的实现。根据实际需求和具体情况,你可能需要进行更多的优化和改进。

使用中间件 redis

6. bitmap

Redis位图(Bitmap)是一种特殊的数据结构,用于处理位级别的操作。你可以借助Redis位图来解决去重问题,包括处理大规模数据的去重。

以下是使用Redis位图解决去重问题的一般步骤:

  1. 将需要去重的数据转换为唯一标识,例如将QQ号转换为对应的整数或字符串。

  2. 使用Redis的位图数据结构,创建一个位图键(Key)用于存储去重标记。你可以使用Redis的SETBIT命令将位图中的特定位设置为1,表示该标识已经存在。

  3. 遍历数据集,对于每个唯一标识,使用SETBIT命令设置位图中对应的位。如果该位已经被设置为1,表示标识已经存在,可以进行相应的处理(例如丢弃或记录重复数据)。

  4. 完成遍历后,可以使用位图的其他命令(如BITCOUNT)获取去重后的数量,或者使用GETBIT命令检查特定位是否被设置,以判断某个标识是否存在。

下面是一个使用Redis位图进行去重的示例代码(使用Java Redis客户端Jedis):

import redis.clients.jedis.Jedis;public class RedisBitmapDuplicateRemover {private static final String BITMAP_KEY = "duplicate_bitmap";public static void main(String[] args) {// 假设qqList是包含40亿个QQ号的列表List<String> qqList = loadQQList();// 初始化Redis连接Jedis jedis = new Jedis("localhost");// 去重计数器int duplicateCount = 0;// 遍历qqList进行去重for (String qq : qqList) {long bit = Long.parseLong(qq); // 假设QQ号被转换为长整型// 检查位图中的对应位是否已经被设置boolean isDuplicate = jedis.getbit(BITMAP_KEY, bit);if (isDuplicate) {// 处理重复数据duplicateCount++;} else {// 设置位图中的对应位为1,表示标识已经存在jedis.setbit(BITMAP_KEY, bit, true);// 处理非重复数据}}// 获取去重后的数量long distinctCount = jedis.bitcount(BITMAP_KEY);System.out.println("Duplicate count: " + duplicateCount);System.out.println("Distinct count: " + distinctCount);// 关闭Redis连接jedis.close();}// 加载40亿个QQ号列表的示例方法private static List<String> loadQQList() {// TODO: 实现加载40亿个QQ号列表的逻辑,返回List<String>return new ArrayList<>();}
}

在这个示例中,我们首先将QQ号转换为长整型,并使用Redis位图存储去重标记。然后遍历QQ号列表,对于每个QQ号,我们使用GETBIT命令检查位图中对应的位是否已经被设置。如果已经被设置,表示该QQ号重复,我们可以进行相应的处理。如果位图中对应的位未被设置,表示该QQ号是首次出现,我们使用SETBIT命令将位图中对应的位设置为1,表示标识已经存在。

最后,我们使用BITCOUNT命令获取位图中被设置为1的位的数量,即去重后的数量。同时,我们还可以使用其他位图命令来进行更多的操作,例如使用GETBIT命令检查特定位是否被设置。

请注意,示例代码中的loadQQList()方法仍然是一个占位方法,需要根据实际情况实现。此外,你需要确保已经安装了Redis并正确配置了Redis连接。

总结而言,借助Redis位图,你可以高效地解决大规模数据的去重问题。

7. 布隆过滤器

使用布隆过滤器(Bloom Filter)可以高效地进行快速查找和去重操作,而无需存储实际的数据。以下是使用布隆过滤器实现去重的一般步骤:

初始化布隆过滤器:确定布隆过滤器的大小(比特位数)和哈希函数的数量。

将需要去重的数据转换为唯一标识,例如将QQ号转换为对应的整数或字符串。

对每个唯一标识,使用多个不同的哈希函数进行哈希计算,得到多个哈希值。

将得到的多个哈希值对应的位设置为1,表示该标识已经存在。

当需要判断某个标识是否存在时,对该标识使用相同的哈希函数进行哈希计算,并检查对应的位是否都为1。如果有任何一个位为0,则标识不存在;如果所有位都为1,则标识可能存在(存在一定的误判率)。

下面是一个使用布隆过滤器实现去重的示例代码(使用Java Bloom Filter库 Guava):

import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;public class BloomFilterDuplicateRemover {private static final int EXPECTED_INSERTIONS = 1000000; // 预期插入数量private static final double FPP = 0.01; // 期望误判率public static void main(String[] args) {// 初始化布隆过滤器BloomFilter<CharSequence> bloomFilter = BloomFilter.create(Funnels.stringFunnel(), EXPECTED_INSERTIONS, FPP);// 假设qqList是包含大量QQ号的列表List<String> qqList = loadQQList();// 去重计数器int duplicateCount = 0;// 遍历qqList进行去重for (String qq : qqList) {if (bloomFilter.mightContain(qq)) {// 处理重复数据duplicateCount++;} else {bloomFilter.put(qq);// 处理非重复数据}}// 获取去重后的数量long distinctCount = qqList.size() - duplicateCount;System.out.println("Duplicate count: " + duplicateCount);System.out.println("Distinct count: " + distinctCount);}// 加载大量QQ号列表的示例方法private static List<String> loadQQList() {// TODO: 实现加载大量QQ号列表的逻辑,返回List<String>return new ArrayList<>();}
}

在这个示例中,我们使用Guava库中的Bloom Filter实现去重功能。首先,我们初始化布隆过滤器 bloomFilter,指定预期插入数量和期望误判率。然后,遍历QQ号列表 qqList,对于每个QQ号,我们首先使用 mightContain() 方法判断该QQ号是否可能已经存在于布隆过滤器中。如果返回 true,表示该QQ号可能已经存在,我们可以进行相应的处理。如果返回 false,表示该QQ号很可能不存在,我们使用 put() 方法将其插入布隆过滤器中,并进行相应的处理。

最后,我们可以通过计算重复数据的数量,以及使用列表总数量减去重复数量,得到去重后的数量。

请注意,示例代码中的 loadQQList() 方法仍然是一个占位方法,需要根据实际情况实现。此外,你需要确保已经引入了Guava库并正确配置了依赖项。

总结而言,布隆过滤器是一种高效的去重数据结构,适用于大规模数据的去重操作。然而,布隆过滤器存在一定的误判率,因此在需要高精确度的场景下,可能需要额外的校验手段来确认数据的存在与否。

分析一下布隆过滤器以及bitmap存储40亿个QQ号需要的内存

布隆过滤器和位图(Bitmap)都是常见的数据结构,用于高效地存储和查询大量数据。下面我会分析一下布隆过滤器和位图存储40亿个QQ号所需的内存。

布隆过滤器:

要估算布隆过滤器需要的具体内存量,需要考虑以下几个因素:

  1. 期望的插入数量(Expected Insertions):这是指预计会插入到布隆过滤器中的元素数量。

  2. 期望的误判率(False Positive Probability):这是指布隆过滤器允许的判断错误的概率,也就是将一个不存在的元素误判为存在的概率。

  3. 哈希函数的数量(Hash Functions Count):布隆过滤器使用多个哈希函数来将输入元素映射到位数组中的多个位置,这有助于减小误判率。

布隆过滤器的内存占用可以通过以下公式近似计算:

M = − ( N ∗ l n ( p ) ) / ( l n ( 2 ) 2 ) M = -(N * ln(p)) / (ln(2)^2) M=(Nln(p))/(ln(2)2)

其中,M 是所需的比特位数(内存占用),N 是预期插入数量,p 是期望误判率。

请注意,该公式仅提供了一个近似值,并且假设哈希函数的输出是均匀分布的。实际上,布隆过滤器的性能和内存占用与哈希函数的选择、哈希函数之间的相互独立性等因素也有关系。

例如,假设我们希望将40亿个QQ号存储在布隆过滤器中,并且期望误判率为0.01%(0.0001),我们可以将这些值代入公式进行估算:

M = ( 40 亿 ∗ l n ( 0.0001 ) ) / ( l n ( 2 ) 2 ) M = (40亿 * ln(0.0001)) / (ln(2)^2) M=(40亿ln(0.0001))/(ln(2)2)
B y t e s = c e i l ( M / 8 ) Bytes = ceil(M / 8) Bytes=ceil(M/8)

根据计算结果,布隆过滤器大约需要占用约 596.05MB 的内存。

需要注意的是,这只是一个估算值,实际的内存占用可能会有所偏差。同时,为了避免误判率过高,可能需要使用更多的哈希函数,从而增加内存占用。

因此,具体的内存占用量还需根据实际需求和误判率要求进行调整和测试。

位图(Bitmap):

位图的内存分析源于腾讯三面:40 亿个 QQ 号码如何去重?

位图是一种使用比特位来表示数据存在与否的数据结构。对于40亿个QQ号,每个QQ号可以用一个唯一的整数或哈希值来表示。
如果我们使用32位整数来表示每个QQ号,那么每个整数需要占用32个比特位。因此,存储40亿个QQ号所需的内存为:
来看绝招!我们可以对hashmap进行优化,采用bitmap这种数据结构,可以顺利地同时解决时间问题和空间问题。
在很多实际项目中,bitmap经常用到。我看了不少组件的源码,发现很多地方都有bitmap实现,bitmap图解如下:

在这里插入图片描述

这是一个unsigned char类型,可以看到,共有8位,取值范围是[0, 255],如上这个unsigned char的值是255,它能标识0~7这些数字都存在。
同理,如下这个unsigned char类型的值是254,它对应的含义是:1~7这些数字存在,而数字0不存在:

在这里插入图片描述

由此可见,一个unsigned char类型的数据,可以标识0~7这8个整数的存在与否。以此类推:

一个unsigned int类型数据可以标识0~31这32个整数的存在与否。
两个unsigned int类型数据可以标识0~63这64个整数的存在与否。
显然,可以推导出来:512MB大小足够标识所有QQ号码的存在与否,请注意:QQ号码的理论最大值为2^32 - 1,大概是43亿左右。
接下来的问题就很简单了:用512MB的unsigned int数组来记录文件中QQ号码的存在与否,形成一个bitmap,比如:

bitmapFlag[123] = 1
bitmapFlag[567] = 1
bitmapFlag[123] = 1
bitmapFlag[890] = 1

实际上就是:

bitmapFlag[123] = 1
bitmapFlag[567] = 1
bitmapFlag[890] = 1

然后从小到大遍历所有正整数(4字节),当bitmapFlag值为1时,就表明该数是存在的。

总结

1. 使用 HashSet 去重:

这是一种简单粗暴的方法,使用内置的哈希集合数据结构。
需要将40亿个QQ号全部加载到内存中,因此可能会超出1GB的内存限制。
优点是实现简单,查询性能较好,但内存占用较高。

2. 使用 Java 8 的 Stream 去重:

利用 Java 8 的流操作特性,通过 distinct() 方法去重。
同样需要将40亿个QQ号全部加载到内存中,可能会超出1GB的内存限制。
优点是简单易用,但内存占用较高。

3. 使用数据库的去重功能:

利用数据库的去重功能,将QQ号作为唯一索引或使用 DISTINCT 关键字进行查询。
需要将数据存储在数据库中,可能需要额外的存储开销。
优点是适用于大规模数据,但需要依赖数据库系统。

4. 文件分片:

将数据进行分片存储,每个文件存储部分QQ号,然后逐个文件进行去重。
可以通过逐个读取文件并使用 HashSet 进行去重,最后合并结果。
可以适应1GB内存限制,但需要额外的文件操作和合并步骤。

5. 外部排序算法:

使用外部排序算法,将数据划分为多个部分,在排序过程中进行去重。
可以适应1GB内存限制,但需要额外的排序和合并步骤。

6. 位图(Bitmap):

使用位图存储40亿个QQ号,每个QQ号使用一个比特位表示存在与否。
需要大约512MB的内存来存储40亿个QQ号。

7. 布隆过滤器:

使用布隆过滤器存储40亿个QQ号,以较小的内存(596.05MB )占用来实现去重功能。
布隆过滤器的具体内存占用取决于预期插入数量和期望误判率,可以根据公式进行估算。

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

1、手把手教你学会使用 FlinkSQL客户端

目录 1、FlinkSQL客户端的功能 2、FlinkSQL客户端启动参数配置 2.1 基本语法 2.2 相关参数([MODE])&#xff1a; 2.3 相关参数(embedded [OPTIONS])&#xff1a; 3、启动Flink的sql-client 3.1 启动时使用初始化脚本 3.2 启动时指定依赖的jar包 3.3 基于yarn-session模…

解决java.io.FileNotFoundException: HADOOP_HOME and hadoop.home.dir are unset.的错误

文章目录 1. 复现错误2. 分析错误3. 解决问题3.1 下载Hadoop3.2 配置Hadoop3.3 下载winutils3.4 配置winutils 1. 复现错误 今天在运行同事给我的项目&#xff0c;但在项目启动时&#xff0c;报出如下错误&#xff1a; java.io.FileNotFoundException: java.io.FileNotFoundEx…

密码技术 (5) - 数字签名

一. 前言 前面在介绍消息认证码时&#xff0c;我们知道消息认证码虽然可以确认消息的完整性&#xff0c;但是无法防止否认问题。而数字签名可以解决否认的问题&#xff0c;接下来介绍数字签名的原理。 二. 数字签名的原理 数字签名和公钥密码一样&#xff0c;也有公钥和私钥&am…

【分布式事务】

文章目录 解决分布式事务的思路seata四种模式1. XA模式2. AT模式AT模式与XA模式的区别是什么&#xff1f;脏写问题 3. TCC模式事务悬挂和空回滚 4. SAGA模式 四种模式对比口述AT模式与TCC模式高可用 什么是分布式事务&#xff1f; 分布式事务&#xff0c;就是指不是在单个服务或…

JAVA 异常分类及处理

1 概念 如果某个方法不能按照正常的途径完成任务&#xff0c;就可以通过另一种路径退出方法。在这种情况下会抛出一个封装了错误信息的对象。此时&#xff0c;这个方法会立刻退出同时不返回任何值。另外&#xff0c;调用 这个方法的其他代码也无法继续执行&#xff0c;异常处理…

vSAN7.0更换硬盘步骤

更换容量盘 预先检查 查看故障硬盘 清单->集群->监控->vsan->skyline运行->物理磁盘->运维运行状况 检查数据同步状态 清单->集群->监控->vsan->重新同步对象&#xff0c;数值全为0表示未重建。 数据迁移检查 清单->集群->监控->…

ili9431液晶 tft_espi图形库演示 时钟、天气、滚动、气象图标

米思齐tft_spi模块库演示程序。心知天气、阿里云时钟、WiFi信号强度检测、1分钟滚屏、更新天气时间为15分钟、加入天气图标。更新天气次数。断网检测 。此程序为tft_eSPI图形库演示、如感觉好可以自行优化。 ili9431tft_espi库是用于ESP32和ESP8266芯片的TFT LCD驱动程序库&am…

基于Java的厨艺交流平台设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…

初级篇—第一章初识数据库

文章目录 为什么要使用数据库数据库与数据库管理系统数据库的相关概念数据库与数据库管理系统的关系 常用的数据库为什么如此多的厂商要选用MySQL&#xff1f;MySQL的目录 RDBMS 与 非RDBMS关系型数据库(RDBMS)非关系型数据库(非RDBMS) 关系型数据库设计规则表、记录、字段表的…

mathtype如何嵌入到word中?详细mathtype安装步骤教程

mathtype是一款功能特别强大的数学方式编辑软件&#xff0c;为用户提供各种强大的数学公式符号帮助用户进行计算&#xff0c;并且速度很快。有小伙伴知道mathtype如何嵌入到word中吗&#xff0c;这里小编就给大家详细介绍一下mathtype嵌入到word中的方法&#xff0c;有需要的小…

【JVM】第五篇 垃圾收集器G1和ZGC详解

导航 一. G1垃圾收集算法详解1. 大对象Humongous说明2. G1收集器执行一次GC运行的过程步骤3. G1垃圾收集分类4. G1垃圾收集器参数设置5. G1垃圾收集器的优化建议6. 适合使用G1垃圾收集器的场景?二. ZGC垃圾收集器详解1. NUMA与UMA2. 颜色指针3. ZGC的运作过程4. ZGC垃圾收集器…

【源码】hamcrest 源码阅读及空对象模式、模板方法模式的应用

文章目录 前言1. 类图概览2. 源码阅读2.1 抽象类 BaseMatcher2.1 接口 Description提炼模式&#xff1a;空对象模式 2. 接口 Description 与 SelfDescribing 配合使用提炼模式 模板方法 后记 前言 hamcrest &#xff0c;一个被多个测试框架依赖的包。听说 hamcrest 的源码质量…

flutter开发实战-webview插件flutter_inappwebview使用

flutter开发实战-webview插件flutter_inappwebview使用 在开发过程中&#xff0c;经常遇到需要使用WebView&#xff0c;Webview需要调用原生的插件来实现。常见的flutter的webview插件是webview_flutter&#xff0c;flutter_inappwebview。之前整理了一下webview_flutter&…

OpenCV之直线曲线拟合

直线拟合fitLine void fitLine( InputArray points, OutputArray line, int distType,double param, double reps, double aeps ); points:二维点的数组或vector line:输出直线,Vec4f (2d)或Vec6f (3d)的vector distType:距离类型 param:距离参数 reps:径向的精度参数 a…

多线程学习笔记(一)

文章目录 1 线程基础知识复习2 CompletableFuture1、Future和Callable接口2、FutureTask3、对Future的改进4、案例精讲——电商5、常用方法6、CompetableFutureWithThreadPool【重要】 3 锁1、乐观锁和悲观锁2、synchronized 8锁案例3、公平锁和非公平锁4、可重入锁5、死锁及排…

微服务moleculer01

1.官网地址&#xff1a; Moleculer - Progressive microservices framework for Node.js 2. github代码地址&#xff1a; GitHub - moleculerjs/moleculer: :rocket: Progressive microservices framework for Node.js Moleculer是基于Node.js的一款快速、多功能的微服务框…

【c++随笔07】常量、变量、static

【c随笔07】常量、变量、static 1、常量、变量1.1、声明变量1.2、使用常量 2、static介绍2.1、static 局部变量2.2、static 全局变量2.3、C static静态成员变量2.4、C static静态成员函数详解 原创地址&#xff0c;https://zhengjunxue.blog.csdn.net/article/details/13167770…

如何保持终身学习

文章目录 2.1. 了解你的大脑2.2 学习是对神经元网络的塑造2.3 大脑的一生 3.学习的心里基础3.1 固定思维与成长思维3.2 我们为什么要学习 4. 学习路径4.1 构建知识模块4.2 大脑是如何使用注意力的4.3 提高专注力4.4 放松一下&#xff0c;学的更好4.5 巩固你的学习痕迹4.6 被动学…

毛玻璃跟随鼠标移动

效果展示 页面结构组成 从上述的效果图可以看出&#xff0c;此页面的布局比较简单&#xff0c;采用常规的布局就可以实现 CSS / JavaScript 知识点 backdrop-filter 属性回顾mousemove 事件 实现页面布局 <section><h2>Frosted Glass</h2><div class…

Ae 效果:CC Slant

扭曲/CC Slant Distort/CC Slant CC Slant &#xff08;CC 倾斜&#xff09;主要用于创建图像的倾斜效果&#xff0c;可以改变图像的视觉角度&#xff0c;使得图像看起来像是被倾斜或者拉伸。 ◆ ◆ ◆ 效果属性说明 Slant 倾斜 用于控制倾斜源图像的程度。 默认值为 0&#…