在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关键字查询),但这种需要额外存储开销…

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

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

相关文章

【链表】力扣206反转链表

题目 力扣206反转链表 思路图解 代码实现 双指针代码实现 public static ListNode reverseList(ListNode head) {// 初始化pre&#xff0c;curListNode pre null;ListNode cur head;// 当cur为null时&#xff0c;说明反转结束while(cur ! null) {// 临时保存cur.next节点…

Python写冒泡

当你要用Python写冒泡排序算法时&#xff0c;你可以使用下面的代码&#xff1a; def bubble_sort(arr):n len(arr)for i in range(n-1):for j in range(n-i-1):if arr[j] > arr[j1]:arr[j], arr[j1] arr[j1], arr[j]return arr这个函数接受一个数组作为输入&#xff0c;并…

linux开发板静态IP无法ping通外网

硬件平台&#xff1a;韦东山的6ull开发板 问题&#xff1a; 使用网线直连路由器&#xff0c;动态获取IP时能ping通外网&#xff1b; 改为静态IP时&#xff0c;能ping通局域网&#xff0c;但无法ping通外网。 改为静态IP&#xff1a;修改/etc/network/interfaces 测试&#…

CentOS本地部署SQL Server数据库无公网ip环境实现远程访问

文章目录 前言1.安装GeoServer2. windows 安装 cpolar3. 创建公网访问地址4. 公网访问Geo Servcer服务5. 固定公网HTTP地址 前言 GeoServer是OGC Web服务器规范的J2EE实现&#xff0c;利用GeoServer可以方便地发布地图数据&#xff0c;允许用户对要素数据进行更新、删除、插入…

Linux系统——测试端口连通性方法

目录 一、TCP端口连通性测试 1、ssh 2、telnet&#xff08;可能需要安装&#xff09; 3、curl 4、tcping&#xff08;需要安装&#xff09; 5、nc&#xff08;需要安装&#xff09; 6、nmap&#xff08;需要安装&#xff09; 二、UDP端口连通性测试 1、nc&#xff08;…

adb forward使用

adb forward是Android Debug Bridge&#xff08;ADB&#xff09;的一个命令&#xff0c;它可以将设备端口和主机端口之间建立一个转发通道&#xff0c;从而使主机可以通过该通道访问设备端口提供的服务。使用adb forward可以方便地进行端口转发&#xff0c;例如在电脑上运行的应…

spring-boot-admin-server-ui 打包备忘

spring-boot-admin-server-ui 打包备忘 先试一下springboot2.0*&#xff0c;这是一个老项目 ui包里面发现 "node-sass": "^4.11.0",Node.js 版本node-sass 版本16.x6.x15.x5.x14.x4.14.x13.x4.13.x12.x4.12.x11.x4.10.x10.x4.9.x8.x4.5.3 先把node调成1…

【MySQL】MySQL版本8+ 的 with recursive 两种递归语法的使用

力扣题 1、题目地址 1270. 向公司 CEO 汇报工作的所有人 2、模拟表 员工表&#xff1a;Employees Column NameTypeemployee_idintemployee_namevarcharmanager_idint employee_id 是这个表具有唯一值的列。这个表中每一行中&#xff0c;employee_id 表示职工的 ID&#x…

从0到1实战微服务架构之Nacos下载安装

目录 一、前言 二、Nacos概述 三、Nacos架构 3.1 Open API 3.2 Config Service 3.3 Naming Service 3.4 Nacos Core 3.5 Consistency Protocol 四、Nacos部署实践 4.1 Nacos下载 4.2 Nacos部署 五、总结 一、前言 Nacos是一个开源的、易于使用的、功能丰富的平台&a…

19道ElasticSearch面试题(很全)

点击下载《19道ElasticSearch面试题&#xff08;很全&#xff09;》 1. elasticsearch的一些调优手段 1、设计阶段调优 &#xff08;1&#xff09;根据业务增量需求&#xff0c;采取基于日期模板创建索引&#xff0c;通过 roll over API 滚动索引&#xff1b; &#xff08;…

谓词-量词、主析取、主和取范式、前束范式、推理证明

这部分内容&#xff0c;主要需要掌握谓词推理&#xff0c;而前提是掌握将自然语言符号化为谓词、用量词来限定辖域&#xff0c;量词的消去、剩下就是推理过程。还需要掌握的是主析取、主和取范式和前束范式。 存在量词∃&#xff1a;至少有一个 全称量词∀&#xff1a;全都是…

Linux驱动学习—输入子系统

1、什么是输入子系统&#xff1f; 输入子系统是Linux专门做的一套框架来处理输入事件的&#xff0c;像鼠标&#xff0c;键盘&#xff0c;触摸屏这些都是输入设备&#xff0c;但是这邪恶输入设备的类型又都不是一样的&#xff0c;所以为了统一这些输入设备驱动标准应运而生的。…

GB/T 15036-2018 实木地板检测

实木地板是指未经拼接、覆贴的单块木材直接加工而成的地板&#xff0c;实木地板具有脚感舒适&#xff0c;环保等优良的性能&#xff0c;在家庭装修中被广泛使用&#xff0c;尤其是在国内很受欢迎。 GB/T 15036-2018 实木地板测试介绍&#xff1a; 测试项目 测试方法 外观 G…

50天精通Golang(第13天)

反射reflect 一、引入 先看官方Doc中Rob Pike给出的关于反射的定义&#xff1a; Reflection in computing is the ability of a program to examine its own structure, particularly through types; it’s a form of metaprogramming. It’s also a great source of confus…

代码随想录算法训练营第60天|● 84.柱状图中最大的矩形

84. 柱状图中最大的矩形 困难 相关标签 相关企业 给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的高度。每个柱子彼此相邻&#xff0c;且宽度为 1 。 求在该柱状图中&#xff0c;能够勾勒出来的矩形的最大面积。 示例 1: [图片] 输入&#xff1a;heights [2,1,5,6…

springCould中的Bus-从小白开始【11】

目录 &#x1f9c2;1.Bus是什么❤️❤️❤️ &#x1f32d;2.什么是总线❤️❤️❤️ &#x1f953;3.rabbitmq❤️❤️❤️ &#x1f95e;4.新建模块3366❤️❤️❤️ &#x1f373;5.设计思想 ❤️❤️❤️ &#x1f37f;6.添加消息总线的支持❤️❤️❤️ &#x1f9…

世邦IP网络对讲广播系统 uploadjson.php接口处存在任意文件上传漏洞

产品简介 SPON世邦IP网络对讲广播系统是一种先进的通信解决方案&#xff0c;旨在提供高效的网络对讲和广播功能。 漏洞概述 SPON世邦IP网络对讲广播系统 uploadjson.php接口处存在任意文件上传漏洞&#xff0c;未经身份验证的攻击者可利用此漏洞上传恶意后门文件&#xff0c…

【UE Niagara学习笔记】05 - 喷射火焰顶部的蓝色火焰

在上一篇博客&#xff08;【UE Niagara学习笔记】04 - 火焰喷射时的黑烟效果&#xff09;的基础上继续实现在火焰喷射的起点位置生成蓝色火焰的效果。 目录 效果 步骤 1. 创建新的发射器 2. 减少粒子生成数量 3. 减小粒子初始大小 4. 减少粒子喷射距离 5. 减少粒子初始…

Java基础- Function接口

我们来看一个 Function 接口的例子。假设我们有一个任务&#xff0c;需要处理一个员工对象列表&#xff0c;将每个员工的信息格式化为字符串&#xff0c;同时根据一些规则&#xff08;如年龄、工作年限等&#xff09;来过滤员工。这个任务可以通过使用 Function 接口以及流&…

Jenkins持续集成

1. Jenkins插件 Jenkins做持续集成很好用&#xff0c;这里只是为了列一下我们经常使用的插件。目前加的比较少&#xff0c;以后可以逐步完善。 必备插件&#xff1a; 1. Credentials Plugin授权插件&#xff0c;不解释。 2. Matrix Authorization Strategy Plugin 矩阵式授权…