在1G的内存中,对百亿个QQ号去重?

2024-01-10 23:44
文章标签 内存 qq 1g 百亿

本文主要是介绍在1G的内存中,对百亿个QQ号去重?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 一、公共方法
    • 1、生成模拟QQ号
    • 2、读取数据文件
    • 3、测试方法
  • 二、HashSet
  • 三、Java8的Stream
  • 四、Segment
  • 五、BloomFilter
  • 六、BitMap
  • 七、总结

假设QQ号是int类型,那么最多可以有4294967295个,就是43亿左右,QQ号无论多少位,每个数字只占用4个字节(32位)。如果要存储43亿个QQ号,需要的内存空间为43亿乘以4Byte=172亿Byte,172亿字节 ÷ (1024 × 1024 × 1024) ≈ 16GB。这只是存储QQ号本身所需的内存空间,不包括其他数据结构和索引所需的额外空间。

一、公共方法

1、生成模拟QQ号

private static void QQGenerator(int qqCount) {String[] qqs = new String[qqCount];Random random = new Random();for (int i = 0; i < qqCount; i++) {int randomNumber = random.nextInt(Integer.MAX_VALUE);qqs[i] = String.valueOf(randomNumber);}try (BufferedWriter writer = new BufferedWriter(new FileWriter("G:\\qq.txt"))) {for (String qq : qqs) {writer.write(qq + System.lineSeparator());}} catch (IOException e) {e.printStackTrace();}
}

2、读取数据文件

private static List<String> loadData(String s) {List<String> list = new ArrayList<String>();try (BufferedReader reader = new BufferedReader(new FileReader("G:\\qq.txt"))) {String line;while ((line = reader.readLine()) != null) {list.add(line);}} catch (IOException e) {e.printStackTrace();}return list;
}

3、测试方法

// 读取qq.txt里面的内容
List<String> list = loadData("G:\\qq.txt");
long statr = System.currentTimeMillis();
// qqDistinct为实际使用的方式
List<String> distinctQQList = qqDistinct(list);
System.out.println("去重后数量:" + distinctQQList.size());
System.out.println("执行耗时(单位ms):" + (System.currentTimeMillis() - statr));

二、HashSet

假设内存足够的话,可以使用这种方式

HashSet<String> set = new HashSet<>(list);
List<String> distinctQQList = new ArrayList<>(set);

实验情况如下:

实验1:数据量10W
去重后数量:99988
执行耗时(单位ms):28
实验2:数据量100W
去重后数量:999766
执行耗时(单位ms):149

三、Java8的Stream

假设内存足够的话,可以使用这种方式

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

实验情况如下:

实验1:数据量10W
去重后数量:99988
执行耗时(单位ms):63
实验2:数据量100W
去重后数量:999766
执行耗时(单位ms):254

四、Segment

利用归并排序思路,就是先把大文件拆成多个小文件,拆的过程中同时对文件内容去重+排序,再合并文件,合并过程中同时对内容排序。

每批的最大数量计算如下:

1G = 1,073,741,824字节,每个数字占用4个字节,那么1G内存可以存储的数字数量为:1,073,741,824字节 / 4字节 = 268,435,456个数字

代码实现如下:

// 每个分段的最大数量,根据实际情况调整大小即可,这里默认分20批
private static final int MAX_SEGMENT_SIZE = 50000;
private static List<String> distinctBySegment(List<String> list) {// 分段处理,文件拆成多个子文件,并排好序int segmentCount = (int) Math.ceil((double) list.size() / MAX_SEGMENT_SIZList<File> segmentFiles = new ArrayList<>();for (int i = 0; i < segmentCount; i++) {List<String> segment = list.subList(0, Math.min(MAX_SEGMENT_SIZE, lisSet<String> segmentSet = new HashSet<>(segment);// 及时释放内存segment.clear();segment = new ArrayList<>(segmentSet);// 及时释放内存segmentSet.clear();Collections.sort(segment);File segmentFile = writeSegmentToFile(segment);// 及时释放内存segment.clear();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.if (null == segmentFile2) {mergedSegmentFiles.add(segmentFile1);} else {File mergedSegmentFile = mergeTwoSegmentsToNew(segmentFile1, mergedSegmentFiles.add(mergedSegmentFile);}}segmentFiles = mergedSegmentFiles;}// 最终得到segmentFiles只有一个文件,且是排好序去重的File file = segmentFiles.get(0);List<String> distinctQQList = new ArrayList<String>();try (BufferedReader reader = new BufferedReader(new FileReader(file))) {String line;while ((line = reader.readLine()) != null) {distinctQQList.add(line);}} catch (IOException e) {e.printStackTrace();}return distinctQQList;
}
private static File writeSegmentToFile(List<String> segmentSet) {File file = null;try {file = File.createTempFile("qq_egment", ".txt");} catch (IOException e) {e.printStackTrace();}try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {for (String qq : segmentSet) {writer.write(qq + System.lineSeparator());}} catch (IOException e) {e.printStackTrace();}return file;
}
private static File mergeTwoSegmentsToNew(File segmentFile1, File segmentFileFile file = null;try {file = File.createTempFile("qq_new_egment", ".txt");} catch (IOException e) {e.printStackTrace();}try (BufferedReader reader1 = new BufferedReader(new FileReader(segmentFiBufferedReader reader2 = new BufferedReader(new FileReader(segmentFiBufferedWriter writer = new BufferedWriter(new FileWriter(file))) {String qq1 = reader1.readLine();String qq2 = reader2.readLine();while (qq1 != null && qq2 != null) {if (qq1.compareTo(qq2) < 0) {writer.write(qq1 + System.lineSeparator());qq1 = reader1.readLine();} else if (qq1.compareTo(qq2) > 0) {writer.write(qq2 + System.lineSeparator());qq2 = reader2.readLine();} else {writer.write(qq1 + System.lineSeparator());qq1 = reader1.readLine();qq2 = reader2.readLine();}}while (qq1 != null) {writer.write(qq1 + System.lineSeparator());qq1 = reader1.readLine();}while (qq2 != null) {writer.write(qq2 + System.lineSeparator());qq2 = reader2.readLine();}} catch (IOException e) {e.printStackTrace();}return file;
}

实验情况如下:

实验1:数据量10W
去重后数量:99988
执行耗时(单位ms):402
实验2:数据量100W
去重后数量:999766
执行耗时(单位ms):2029

五、BloomFilter

内存估算公式

m = n * ln§ / (ln(2)^2)
m是BloomFilter的位数组大小(以位为单位),n是预期插入的元素数量,p是预期的误判率。

假设预期插入43亿个元素,误判率为0.001(0.1%),根据公式计算,Bloom Filter的位数组大小(m)约为 5,754,602,676 位,即约等于686MB,满足要求,在1G内。

代码实现如下:

// 预期插入的元素数量,这里默认设置为元素的2倍
private static final int EXPECTED_INSERTIONS = 2000000;
// 预期的误判率,must be > 0.0
private static final double FALSE_POSITIVE_RATE = 0.001;
private static List<String> distinctByBloomFilter(List<String> list) {List<String> distinctQQList = new ArrayList<>();BloomFilter<CharSequence> bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), EXPECTED_INSERTIONS, FALSE_POSITIVE_RATE);for (String qq : list) {if (!bloomFilter.mightContain(qq)) {bloomFilter.put(qq);distinctQQList.add(qq);}}return distinctQQList;
}

实验情况如下:

实验1:数据量10W
去重后数量:99988
执行耗时(单位ms):163
实验2:数据量100W
去重后数量:999766
执行耗时(单位ms):1033

六、BitMap

Redis的Bitmap数据结构可以存储2^32个位,需要占用多少内存?
1位表示1byte,那么转为mb,就是2^32*8/1024/1024=512mb,满足要求,在1G内。

8 bit(位) = 1byte(字节)
1024 byte = 1kb
1024 kb = 1Mb
512MB:8 * 1024 * 1024 * 512 = 2^32

private static final String BITMAP_KEY = "duplicate_bitmap";
private static List<String> distinctByRedisBitMap(List<String> list) {List<String> distinctQQList = new ArrayList<>();Jedis jedis = new Jedis("localhost", 6379);// 去重计数for (String qq : list) {long bit = Long.parseLong(qq);boolean isDuplicate = jedis.getbit(BITMAP_KEY, bit);if (!isDuplicate) {// 设置位图中对应位为1,标识已经存在jedis.setbit(BITMAP_KEY, bit, true);distinctQQList.add(qq);}}// 获取去重后的数量long distinctCount = jedis.bitcount(BITMAP_KEY);System.out.println("Distinct count: " + distinctCount);jedis.close();return distinctQQList;
}

实验情况如下:

实验1:数据量10W
去重后数量:99988
执行耗时(单位ms):16331
实验2:数据量100W
去重后数量:999766
执行耗时(单位ms):157840

七、总结

实现方式HashSetStreamSegmentBloomFilterBitmap
10W数据耗时28ms63ms402ms163ms16331ms
100W数据耗时149ms254ms2029ms1033ms157840ms
  • HashSet和Stream性能好,不过内存占用较高,不满足1G内存要求;
  • Segment实现麻烦,需要额外文件,满足1G内存要求;
  • BloomFilter的性能看似还行,满足1G内存要求,但实际上性能和内存占用,取决于预期插入的元素数量和预期的误判率,可能存在一定误差;
  • Bitmap这种数据结构可以存储2^32个位,需要的内存不多,只需要512MB,占用内存最少,满足1G内存要求,但性能不行。

除此之外,还可以使用数据库的去重(唯一索引或DISTINCT关键字查询),但这种需要额外存储开销…

这篇关于在1G的内存中,对百亿个QQ号去重?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/592469

相关文章

Java内存泄漏问题的排查、优化与最佳实践

《Java内存泄漏问题的排查、优化与最佳实践》在Java开发中,内存泄漏是一个常见且令人头疼的问题,内存泄漏指的是程序在运行过程中,已经不再使用的对象没有被及时释放,从而导致内存占用不断增加,最终... 目录引言1. 什么是内存泄漏?常见的内存泄漏情况2. 如何排查 Java 中的内存泄漏?2.1 使用 J

关于Java内存访问重排序的研究

《关于Java内存访问重排序的研究》文章主要介绍了重排序现象及其在多线程编程中的影响,包括内存可见性问题和Java内存模型中对重排序的规则... 目录什么是重排序重排序图解重排序实验as-if-serial语义内存访问重排序与内存可见性内存访问重排序与Java内存模型重排序示意表内存屏障内存屏障示意表Int

如何测试计算机的内存是否存在问题? 判断电脑内存故障的多种方法

《如何测试计算机的内存是否存在问题?判断电脑内存故障的多种方法》内存是电脑中非常重要的组件之一,如果内存出现故障,可能会导致电脑出现各种问题,如蓝屏、死机、程序崩溃等,如何判断内存是否出现故障呢?下... 如果你的电脑是崩溃、冻结还是不稳定,那么它的内存可能有问题。要进行检查,你可以使用Windows 11

Python基于火山引擎豆包大模型搭建QQ机器人详细教程(2024年最新)

《Python基于火山引擎豆包大模型搭建QQ机器人详细教程(2024年最新)》:本文主要介绍Python基于火山引擎豆包大模型搭建QQ机器人详细的相关资料,包括开通模型、配置APIKEY鉴权和SD... 目录豆包大模型概述开通模型付费安装 SDK 环境配置 API KEY 鉴权Ark 模型接口Prompt

NameNode内存生产配置

Hadoop2.x 系列,配置 NameNode 内存 NameNode 内存默认 2000m ,如果服务器内存 4G , NameNode 内存可以配置 3g 。在 hadoop-env.sh 文件中配置如下。 HADOOP_NAMENODE_OPTS=-Xmx3072m Hadoop3.x 系列,配置 Nam

JVM内存调优原则及几种JVM内存调优方法

JVM内存调优原则及几种JVM内存调优方法 1、堆大小设置。 2、回收器选择。   1、在对JVM内存调优的时候不能只看操作系统级别Java进程所占用的内存,这个数值不能准确的反应堆内存的真实占用情况,因为GC过后这个值是不会变化的,因此内存调优的时候要更多地使用JDK提供的内存查看工具,比如JConsole和Java VisualVM。   2、对JVM内存的系统级的调优主要的目的是减少

JVM 常见异常及内存诊断

栈内存溢出 栈内存大小设置:-Xss size 默认除了window以外的所有操作系统默认情况大小为 1MB,window 的默认大小依赖于虚拟机内存。 栈帧过多导致栈内存溢出 下述示例代码,由于递归深度没有限制且没有设置出口,每次方法的调用都会产生一个栈帧导致了创建的栈帧过多,而导致内存溢出(StackOverflowError)。 示例代码: 运行结果: 栈帧过大导致栈内存

理解java虚拟机内存收集

学习《深入理解Java虚拟机》时个人的理解笔记 1、为什么要去了解垃圾收集和内存回收技术? 当需要排查各种内存溢出、内存泄漏问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就必须对这些“自动化”的技术实施必要的监控和调节。 2、“哲学三问”内存收集 what?when?how? 那些内存需要回收?什么时候回收?如何回收? 这是一个整体的问题,确定了什么状态的内存可以

NGINX轻松管理10万长连接 --- 基于2GB内存的CentOS 6.5 x86-64

转自:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=190176&id=4234854 一 前言 当管理大量连接时,特别是只有少量活跃连接,NGINX有比较好的CPU和RAM利用率,如今是多终端保持在线的时代,更能让NGINX发挥这个优点。本文做一个简单测试,NGINX在一个普通PC虚拟机上维护100k的HTTP

PHP原理之内存管理中难懂的几个点

PHP的内存管理, 分为俩大部分, 第一部分是PHP自身的内存管理, 这部分主要的内容就是引用计数, 写时复制, 等等面向应用的层面的管理. 而第二部分就是今天我要介绍的, zend_alloc中描写的关于PHP自身的内存管理, 包括它是如何管理可用内存, 如何分配内存等. 另外, 为什么要写这个呢, 因为之前并没有任何资料来介绍PHP内存管理中使用的策略, 数据结构, 或者算法. 而在我们