用BitMap结构实现快速取差集

2024-01-31 06:12

本文主要是介绍用BitMap结构实现快速取差集,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在流式计算对比基线无数据告警场景中,利用基线数据对比来源数据,如果发现该时间窗口内的数据不在基线数据中则产生告警,因此基线数据和来源数据需要进行对比计算,基线数据去掉来源数据中已有的数据,余下的数据作为产生的告警数据。在数据量较小时直接进行集合运算取差集即可,但是但基线数据和来源数据量达百万甚至千万时则计算缓慢,出现延时,因此需要找到其它方式方法。

基线数据的定义:
基线数据是一组带时分的时序数据,时分是根据配置对24小时进行分割得到,比如配置1分钟内没数据则告警,则24小时按1分钟进行分割,则分割成00:01, 00:02…的时序数据,总共1440条记录,1440条表示每个1分钟窗口内有一条数据。配置5分钟内没数据则告警,则按5分钟进行分割,则分割成00:05, 00:10…的时序数据,每个时间窗口内有且仅有一条数据。每个任务对应一组基线数据。

假设基线数据量为一百万条,窗口内来源数据为九十九万九千条,基线数据中移除来源数据即取差集后则只有一千条。

2.1 集合的取差集方法
1)List.removeAll(sublist)方法取差集

private static void testRemoveAll() {List<String> listA = new ArrayList<>();for(int i=0;i<OneMillion;i++){ //随机创建百万条基线数据String key1="ip(192.168.199.10"+i+")#port("+i+")#service_name(orcl)#time(23:30:00)"+i;listA.add(key1);}//从百万条基线数据中随机获取九十九万九千条作为来源数据List<String> listB = createRandomList(listA,999000);Date date = new Date();listA.removeAll(listB);Date date1 = new Date();System.out.println("testRemoveAll:"+(date1.getTime()-date.getTime())/1000+"秒");System.out.println(listA.size());
}

测试结果:
30分钟未计算出结果,手动kill程序。

2)List.removeAll(new HashSet(sublist))方法取差集
分析removeAll方法的源码,发现removeAll方法中有subList.contain(value)的方法,如果subList为list集合,则调用indexOf()方法,一个一个地遍历查找。最坏时间复杂度为O(总数据量)。如果先将subList转为HashSet,在调用contain方法时,则时间复杂度为O(1),加快对比计算速度。

private static void testRemoveAll() {List<String> listA = new ArrayList<>();for(int i=0;i<OneMillion;i++){ //随机创建百万条基线数据String key1="ip(192.168.xxx.10"+i+")#port("+i+")#service_name(orcl)#time(23:30:00)"+i;listA.add(key1);}//从百万条基线数据中随机获取九十九万九千条作为来源数据List<String> listB = createRandomList(listA,999000);Date date = new Date();//listA.removeAll(listB);listA.removeAll(new HashSet(listB));Date date1 = new Date();System.out.println("testRemoveAll:"+(date1.getTime()-date.getTime())/1000+"秒");System.out.println(listA.size());
}
//0.3秒

测试结果:
计算耗时仅0.3s,这个结果相比上一步有巨大提升。

2.2 BitMap的取差集方法
BitMap,直译为位图,是一种数据结构,代表了有限域中的稠集(Dense Set),每一个元素至少出现一次,没有其他的数据和元素相关联。在索引,数据压缩等方面有广泛应用。
计算机中1 byte = 8 bit,一个比特(bit,称为比特或者位)可以表示1或者0两种值,通过一个比特去标记某个元素的值,而KEY或者INDEX就是该元素,构成一张映射关系图。因为采用了Bit作为底层存储数据的单位,所以可以极大地节省存储空间。同时还支持去重,交集,差集等各种运算Bitmap的实现有很多,例如Java原生的BitSet,第三方的RoaingBitmap等。

1)Java原生的BitSet

private static void testBitSet() {List<String> listA = new ArrayList<>();BitSet bitmap = new BitSet();for(int i=0;i<OneMillion;i++){String key1="ip(192.168.XXX.10"+i+")#port("+i+")#service_name(orcl)#time(23:30:00)"+i;bitmap.set(Math.abs(key1.hashCode()));listA.add(key1);}BitSet bitmap1 = new BitSet();List<String> listB = createRandomList(listA,999000);for(String s:listB){bitmap1.set(Math.abs(s.hashCode()));}Date date = new Date();bitmap.andNot(bitmap1);Date date1 = new Date();System.out.println("testBitSet:"+(double)(date1.getTime()-date.getTime())/1000+"秒");
}
//testBitSet:0.02秒

测试结果:
计算耗时仅0.02s,这个结果相比上一步时间缩短一个数量级。
2)RoaingBitmap
Bitmap的主要缺陷是占用大量的内存空间。Bitmap是一种使用位图来表示数据集合的数据结构,每个位代表一个元素的存在与否。当数据集合很大时,Bitmap需要使用大量的位来表示,导致内存占用较高。
RoaringBitmap是一种改进的Bitmap数据结构,它能够解决Bitmap的内存占用问题。RoaringBitmap使用了一种压缩算法,能够有效地压缩位图数据,减少内存占用。具体来说,RoaringBitmap将位图分成多个块,每个块使用不同的压缩算法进行压缩。这样,当数据集合中存在大量连续的元素时,RoaringBitmap能够更好地压缩数据,减少内存占用。另外,RoaringBitmap还支持快速的位图操作,如并集、交集和差集等,使得对数据集合的操作更加高效。RoaringBitmap还支持动态增长,可以动态地添加和删除元素,而不需要重新分配内存。
总的来说,RoaringBitmap通过压缩算法和优化的位图操作,能够有效地解决Bitmap的内存占用问题,提高了位图数据结构的性能和可扩展性。

private static void testRoaringBitmap() {List<String> listA = new ArrayList<>();RoaringBitmap bitmap = new RoaringBitmap();for(int i=0;i<OneMillion;i++){String key1="ip(192.168.xxx.10"+i+")#port("+i+")#service_name(orcl)#time(23:30:00)"+i;bitmap.add(Math.abs(key1.hashCode()));listA.add(key1);}RoaringBitmap bitmap1 = new RoaringBitmap();List<String> listB = createRandomList(listA,999000);for(String s:listB){bitmap1.add(Math.abs(s.hashCode()));}Date date = new Date();bitmap.andNot(bitmap1);Date date1 = new Date();System.out.println("testRoaringBitmap:"+(double)(date1.getTime()-date.getTime())/1000+"秒");
}
//testRoaringBitmap:0.022秒

测试结果:
计算耗时仅0.022s,这个结果和Java原生的BitSet时间基本相同。
总 结:
通过上述测试对比,相比其它方式采用BitMap和升级的RoaingBitmap在时间上能缩小一个数量级,效果十分明显,但是使用BitMap和RoaingBitmap存在的问题是BitMap结构只支持数字,如果是字符串需要先将字符串转成数字,字符串转数字时有一定的概率出现hash碰撞(不同的字符串转成相同的数字),因此如果能允许一定的误差,用Bitmap和RoaingBitmap是最快的。

这篇关于用BitMap结构实现快速取差集的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

SpringBoot集成redisson实现延时队列教程

《SpringBoot集成redisson实现延时队列教程》文章介绍了使用Redisson实现延迟队列的完整步骤,包括依赖导入、Redis配置、工具类封装、业务枚举定义、执行器实现、Bean创建、消费... 目录1、先给项目导入Redisson依赖2、配置redis3、创建 RedissonConfig 配

Python的Darts库实现时间序列预测

《Python的Darts库实现时间序列预测》Darts一个集统计、机器学习与深度学习模型于一体的Python时间序列预测库,本文主要介绍了Python的Darts库实现时间序列预测,感兴趣的可以了解... 目录目录一、什么是 Darts?二、安装与基本配置安装 Darts导入基础模块三、时间序列数据结构与

Python使用FastAPI实现大文件分片上传与断点续传功能

《Python使用FastAPI实现大文件分片上传与断点续传功能》大文件直传常遇到超时、网络抖动失败、失败后只能重传的问题,分片上传+断点续传可以把大文件拆成若干小块逐个上传,并在中断后从已完成分片继... 目录一、接口设计二、服务端实现(FastAPI)2.1 运行环境2.2 目录结构建议2.3 serv

C#实现千万数据秒级导入的代码

《C#实现千万数据秒级导入的代码》在实际开发中excel导入很常见,现代社会中很容易遇到大数据处理业务,所以本文我就给大家分享一下千万数据秒级导入怎么实现,文中有详细的代码示例供大家参考,需要的朋友可... 目录前言一、数据存储二、处理逻辑优化前代码处理逻辑优化后的代码总结前言在实际开发中excel导入很

SpringBoot+RustFS 实现文件切片极速上传的实例代码

《SpringBoot+RustFS实现文件切片极速上传的实例代码》本文介绍利用SpringBoot和RustFS构建高性能文件切片上传系统,实现大文件秒传、断点续传和分片上传等功能,具有一定的参考... 目录一、为什么选择 RustFS + SpringBoot?二、环境准备与部署2.1 安装 RustF

Nginx部署HTTP/3的实现步骤

《Nginx部署HTTP/3的实现步骤》本文介绍了在Nginx中部署HTTP/3的详细步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录前提条件第一步:安装必要的依赖库第二步:获取并构建 BoringSSL第三步:获取 Nginx

MyBatis Plus实现时间字段自动填充的完整方案

《MyBatisPlus实现时间字段自动填充的完整方案》在日常开发中,我们经常需要记录数据的创建时间和更新时间,传统的做法是在每次插入或更新操作时手动设置这些时间字段,这种方式不仅繁琐,还容易遗漏,... 目录前言解决目标技术栈实现步骤1. 实体类注解配置2. 创建元数据处理器3. 服务层代码优化填充机制详

Python实现Excel批量样式修改器(附完整代码)

《Python实现Excel批量样式修改器(附完整代码)》这篇文章主要为大家详细介绍了如何使用Python实现一个Excel批量样式修改器,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一... 目录前言功能特性核心功能界面特性系统要求安装说明使用指南基本操作流程高级功能技术实现核心技术栈关键函

Java实现字节字符转bcd编码

《Java实现字节字符转bcd编码》BCD是一种将十进制数字编码为二进制的表示方式,常用于数字显示和存储,本文将介绍如何在Java中实现字节字符转BCD码的过程,需要的小伙伴可以了解下... 目录前言BCD码是什么Java实现字节转bcd编码方法补充总结前言BCD码(Binary-Coded Decima

SpringBoot全局域名替换的实现

《SpringBoot全局域名替换的实现》本文主要介绍了SpringBoot全局域名替换的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录 项目结构⚙️ 配置文件application.yml️ 配置类AppProperties.Ja