redis bitmap数据结构之java对等操作
在之前的文章中,我们有说过bitmap,bitmap在很多场景可以应用,比如黑白名单,快速判定,登录情况等等。总之,bitmap是以其高性能出名。其基本原理是一位存储一个标识,其他衍生知道咱就不说了,而redis就是以这种原生格式存储的。
实际上,redis是基于string的数据结构实现了bitmap的功能。
1. redis基本的bitmap操作命令
最基本的,redis的bitmap有设置和读取两个值,即 setbit/getbit, 非常容易理解,即设置某个标识为1,那么取值判定的时候,就可以得到true.
127.0.0.1:6379> setbit bm1 222 1
(integer) 0
127.0.0.1:6379> getbit bm1 222
(integer) 1
这很容易理解,也是最基本的。当然,它还提供其他的一些操作:BITCOUNT 做数据量统计, BITOP 做bitmap的交并差运算... 我们也不必过多讨论它。
2. java中的原生bitmap
可以说redis的bitmap实现相当之简单,所以java也就顺便实现了一个bitmap的版本:BitSet .
@Test
public void testJavaBitmap() {
BitSet bitmap = new BitSet();
bitmap.set(88);
// exist = true
boolean exist = bitmap.get(88);
BitSet bitmap2 = new BitSet();
bitmap2.set(99);
// bitmap中将包含 [88, 99]
bitmap.or(bitmap2);
}
java中的bitmap实现,也是按位存储,但是是基于long的存储。
/*
* BitSets are packed into arrays of "words." Currently a word is
* a long, which consists of 64 bits, requiring 6 address bits.
* The choice of word size is determined purely by performance concerns.
*/
private final static int ADDRESS_BITS_PER_WORD = 6; /**
* Sets the bit at the specified index to {@code true}.
*
* @param bitIndex a bit index
* @throws IndexOutOfBoundsException if the specified index is negative
* @since JDK1.0
*/
public void set(int bitIndex) {
if (bitIndex < 0)
throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex); int wordIndex = wordIndex(bitIndex);
expandTo(wordIndex); words[wordIndex] |= (1L << bitIndex); // Restores invariants checkInvariants();
}
/**
* Given a bit index, return word index containing it.
*/
private static int wordIndex(int bitIndex) {
return bitIndex >> ADDRESS_BITS_PER_WORD;
}
所以,我们可以得出一个浅显的结论,bitmap很简单,一点都不神秘。但是,大道至简,它高性能,它自然还是有好处的,咱们该用还得用。显然,java版本的bitmap虽然很很好用,但是它只是应用级别的,只能在进程内使用,有太多的其他问题没考虑,所以咱们还得要依赖于redis的bitmap.
问题:如果我有很多的数字标识想要写入redis中,然后再进行读取判定,该怎么办呢?
很简单的,我们可以一个个地调用 setbit 命令,依次写入redis中。这自然能解决问题,但是明显会带来很多的网络io。
其次,我们可以使用pipeline调用setbit进行批量写入。这当然是一种优化方案,只是仍然不是最优。
那有没有什么更好的办法呢?
3. java和redis的bitmap互操作
对于批量的操作,redis是基于string实现,而java是基于bitset实现。其功能都基本差不多,判定、写入、交并差运算。那么,除了一个个按照各自语法进行添加外,有没有可能进行数据结构上的对等呢?
这个思路是很自然的,因为我们已经完全理解了各自的实现原理,为什么不呢?直接将BitSet转换为byte[]写入redis,直接将redis的bitmap当作string读出来不就可以了吗?
事实真是如此吗?实际上有点差别,原因是一个是大端存储,一个是小端存储。
比如:比如对于存储byte值: 00000010 , redis中会解释为偏移为6的值为1, 而在java中则会解析为数字2存在于bitmap中。也就是说两个的判定结果是不一样的,一个是6,一个是2。如果把java中的值给调换一下,变成 01000000,那么就和redis是一样的了。
而从redis中转变到java中,则需要将每个byte位做一逆向操作判定,具体实现如下:
@Test
public void testSetBitmapData2Redis() {
//创建一个连接
Jedis jedis = new Jedis("localhost", 6379);
// 正向设置redis bitmap
String testBitmapKey = "mybitmap1";
jedis.set(testBitmapKey.getBytes(),
genRedisBitmap(2, 55, 133, 65537, 10_0000));
Assert.assertEquals("bitmap取值不正确", true,
jedis.getbit(testBitmapKey, 2L));
Assert.assertEquals("bitmap取值不正确", true,
jedis.getbit(testBitmapKey, 133L));
Assert.assertEquals("bitmap取值不正确", true,
jedis.getbit(testBitmapKey, 65537L));
Assert.assertEquals("bitmap取值不正确", true,
jedis.getbit(testBitmapKey, 10_0000L));
Assert.assertEquals("bitmap取值不正确", false,
jedis.getbit(testBitmapKey, 3L));
//在redis中获取name值
byte[] redisBitmapData = jedis.get("mybitmap1".getBytes());
BitSet bitSet = convertRedisBitmapToJava(redisBitmapData);
Assert.assertTrue("redisBitmap反解不正确", bitSet.get(2));
Assert.assertTrue("redisBitmap反解不正确", bitSet.get(133));
Assert.assertTrue("redisBitmap反解不正确", bitSet.get(65537));
Assert.assertTrue("redisBitmap反解不正确", bitSet.get(10_0000));
Assert.assertFalse("redisBitmap反解不正确", bitSet.get(332));
jedis.close();
} // 将redis的bitmap转换为java 的bitset
private BitSet convertRedisBitmapToJava(byte[] redisBitmapData) {
int len = redisBitmapData.length;
BitSet bitSet = new BitSet();
// 每个 byte 8位, 所以整个bitmap 的长度为 len * 8
for (int i = 0; i < len * 8; i++) {
byte currentSegment = redisBitmapData[i / 8];
if(currentSegment == 0) {
continue;
}
if((currentSegment & (1 << (7 - (i % 8) ) ) ) != 0 ) {
bitSet.set(i);
}
}
return bitSet;
} // 生成redis的bitmap数据
private byte[] genRedisBitmap(int... items) {
BitSet bitSet = new BitSet();
// 2 55 133
for (int k : items) {
bitSet.set(k);
}
byte[] targetBitmap = bitSet.toByteArray();
convertJavaToRedisBitmap(targetBitmap);
return targetBitmap;
} // 将java中的字节数组转换为redis的bitmap数据形式
private void convertJavaToRedisBitmap(byte[] bytes) {
int len = bytes.length;
for (int i = 0; i < len; i++) {
byte b1 = bytes[i];
if(b1 == 0) {
continue;
}
byte transByte = 0;
for (byte j = 0; j < 8; j++) {
transByte |= (b1 & (1 << j)) >> j << (7 -j);
}
bytes[i] = transByte;
}
}
经验证,将8位的byte进行位置反转,能够完美匹配两种数据结构。
如此一来,就可以轻松将整个bitmap进行初始化设置到redis中,从而在redis的bitmap中,使用 getbit 进行高效判定了。
redis bitmap数据结构之java对等操作的更多相关文章
- Redis【4】Java Jedis 操作 Redis~
package redis.redis; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; /** * 描 ...
- 20190928-02使用Redis客户端Jedis连接Redis,以及用Java代码操作Redis 000 030
启动redis package com.yujie.jedis; import java.util.HashMap; import java.util.Map; import java.util.Se ...
- Redis的数据结构、通用操作及其特性
Redis的数据结构 五种数据类型: 字符串(String).字符串列表(list).字符串集合(set).有序字符串集合(sorted set).哈希(hash) key定义的注意点: 不要过长,不 ...
- windows下Redis安装及利用java操作Redis
一.windows下Redis安装 1.Redis下载 下载地址:https://github.com/MicrosoftArchive/redis 打开下载地址后,选择版本 然后选择压缩包 下载 R ...
- Java中操作Redis
一.server端安装 1.下载 https://github.com/MSOpenTech/redis 可看到当前可下载版本:redis2.6 下载后的文件为: 解压后,选择当前64位win7系统对 ...
- Redis入门教程(三)— Java中操作Redis
在Redis的官网上,我们可以看到Redis的Java客户端众多 其中,Jedis是Redis官方推荐,也是使用用户最多的Java客户端. 开始前的准备 使用jedis使用到的jedis-2.1.0. ...
- 使用java代码操作Redis
1导入pom.xml依赖 <dependency> <groupId>redis.clients</groupId> <artifactId>jedis ...
- java代码操作Redis
1.导入需要的pom依赖 <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEn ...
- Redis数据库 : python与java操作redis
redis 包 from redis import * 连接: r = StrictRedis(host='localhost', port='6379') 读写:r.set('key','value ...
随机推荐
- 前端-关于CORS跨域的解决方案,面向服务端
最近自己在写后台管理系统的时候,并没有采用jsp.freemaker.叶子等模板技术,而是由后端提供数据api,前端通过AJAX和JQuery来动态操作页面上的一些div.table元素,从而实现报表 ...
- python面向对象的特征及反射
目录 派生类实操 面向对象特征之封装 property伪装属性(python内置装饰器) 面向对象特征之多态 面向对象之反射 派生类实操 1.将时间字典序列化成json格式,由于序列化数据类型的要求, ...
- Python图像处理丨基于OpenCV和像素处理的图像灰度化处理
摘要:本篇文章讲解图像灰度化处理的知识,结合OpenCV调用cv2.cvtColor()函数实现图像灰度操作,使用像素处理方法对图像进行灰度化处理. 本文分享自华为云社区<[Python图像处理 ...
- Taurus.MVC 微服务框架 入门开发教程:项目集成:2、客户端:ASP.NET Core(C#)项目集成:应用中心。
系列目录: 本系列分为项目集成.项目部署.架构演进三个方向,后续会根据情况调整文章目录. 本系列第一篇:Taurus.MVC V3.0.3 微服务开源框架发布:让.NET 架构在大并发的演进过程更简单 ...
- linux centos7开启防火墙端口
firewall-cmd --zone=public --add-port=3306/tcp --permanent firewall-cmd --reload
- [网鼎杯 2020 朱雀组]phpweb-1|反序列化
1.打开界面之后界面一直在刷新,检查源代码也未发现提示信息,但是在检查中发现了两个隐藏的属性:func和p,抓包进行查看一下,结果如下: 2.对两个参数与返回值进行分析,我们使用dat时一般是这种格式 ...
- KingbaseES 全局临时表
Postgresql 支持会话级别的临时表,表的存续期只在创建临时表的会话存活期间,会话退出后,临时表自动删除,表结构及数据也无法跨会话共享.KingbaseES 除了支持PG原生的临时表机制外,还支 ...
- KingbaseFlySync 版本升级
关键字: KingbaseFlySync.Linux.x86_64.mips64el.aarch64.Java 拓扑图: 客户现场源端和目标端写在一个flysync.ini中,所以不单独把目标端拿出来 ...
- 【Vue项目笔记】—— 父子组件之间传递参数和子组件执行父组件中的方法
父组件(MyBlog.vue) <template> <!-- Delete Modal --> <!-- 注意:这里的@deleteBlog中的deleteBlog要和 ...
- 动态调整日志级别思路&实现
引言 上篇文章 性能调优--小小的 log 大大的坑 已将详细的介绍了高并发下,不正确的使用日志姿势,可能会导致服务性能急剧下降问题.文末也给各位留下了解决方案--日志级别动态调整. 本文将详细介绍& ...