认真写文章,用心作分享。java
我的网站:yasinshaw.com面试
公众号:xy的技术圈算法
前面的文章介绍了Redis的五种最经常使用的对象及其底层的数据结构。这篇文章主要介绍一下一个不那么经常使用,却很是适用于一些特殊场景的对象:bitmaps。数据库
先考虑几个常见的场景:编程
这些需求可使用数据库来实现,使用一些日志表,再经过SQL查询出来。但这样会浪费大量的磁盘空间,并且性能还很低。在数据量比较大的状况下,使用数据库来查询分析会很是慢。数组
这里要感谢咱们计算机的科学家大佬们,发明了一种数据结构能够用来解决这类问题。它就是BitMap。也就是这篇文章主要介绍的Redis bitmaps底层使用的数据结构。数据结构
《编程珠玑》中第一篇讲的就是使用BitMap来排序大文件里面的数据。在一些面试里也会有相似的题目。性能
面试题目:一个10G的文件,里面所有是天然数,一行一个,乱序排列,对其排序。在32位机器上面完成,内存限制为2G。网站
LeetCode上有一个算法题,也是可使用BitMap来解决:日志
算法题目:从0到n之间取出n个不一样的数,找出漏掉的那个。注意:你的算法应当具备线性的时间复杂度。你能实现只占用常数额外空间复杂度的算法吗?
BitMap实际上是一个数据结构,它是利用了位运算的高性能,来存储和计算一些信息。接下来咱们来介绍一下BitMap的原理。
BitMap是基于bit位的位置来记录信息的。好比咱们如今有一个8位的BitMap。最开始时,全部位上都是0。
0, 0, 0, 0, 0, 0, 0, 0
而后有这么一个需求:假设今天有id为1,2,4,7这四个用户登陆了咱们的系统,咱们想要把这个登陆信息记录下来,就只须要在相应的位置标记为1就好了:
1, 1, 0, 1, 0, 0, 1, 0
这个时候,你想知道id为7的用户今天是否登陆过系统,就只须要去看这个BitMap里面第7位是否是1就能够了。
能够看到,若是你使用一个list或者set来存的话,若是一个id是一个byte,占8位(真实状况多是long类型,占64位),那8个id就要占64位,而使用BitMap只须要占用8位。同理,若是咱们的id字段占了64位,那就能够节省64倍的空间。并且,能够利用位运算的特性,来快速实现统计、并集、交集等操做。
看过文章A的人有:1, 2, 4, 7:
1, 1, 0, 1, 0, 0, 1, 0
看过文章B的人有:1, 3, 4, 8:
1, 0, 1, 1, 0, 0, 0, 1
看过文章A且看过文章B的人有:
1, 1, 0, 1, 0, 0, 1, 0
&
1, 0, 1, 1, 0, 0, 0, 1
=
1, 0, 0, 1, 0, 0, 0, 0
获得看过文章A且看过文章B的人有:1, 4
上面的例子只能放8位,若是个人id是9怎么办?
BitMap是一个一个上面这样的数据来组成的。你可使用一个能够扩容的整数数组来作,好比long数组。因此能够无限扩展。
若是id比较稀疏怎么办?
好比我要存入的id多是1,101,1001这种比较稀疏的,若是用BitMap就会浪费一些空间。虽然如今开源的一些实现能够经过记录偏移量来解决这个问题,但也会由于频繁的分裂影响性能。这种场景下,其实不建议使用BitMap。
有兴趣的同窗能够了解一下谷歌开源的EWAHCompressedBitMap,它解决了输入稀疏的问题。
由于笔者主要熟悉Java语言,因此介绍一下Java语言对BitMap的实现。
JDK提供了一个BitMap的实现,叫BitSet
,位于java.util
包下。其底层使用的是一个long
类型的数组,一个long表明一个word。但BitSet没有解决上面提到的输入稀疏的问题。谷歌开源的EWAHCompressedBitMap解决了输入稀疏的问题。
Redis提供了bitmaps对象。实际上是使用的string对象,底层使用了咱们上篇文章提到的SDS。因此会有512M的最大限制,即最多能存2^32
个数据。若是你的id是long类型的,占64位,那可使用两个bitmaps来存。
由SDS的底层实现可知,它是能够扩容和缩容的,可是若是输入比较稀疏,仍然会浪费大量的内存。因此若是输入很稀疏,也不建议使用Redis的bitmaps。
Redis提供了bitmaps对象。它在某些场景下能够节省空间,并显著提高性能。但若是输入比较稀疏(好比网站注册用户有1亿,但天天只有10万用户登陆),那还不如使用set。
另一方面,输入只能是整形。若是是字符串类型的,就无法使用这个数据结构了。
关注公众号:xy的技术圈