Redis的bitMap命令

2018-11-08 22:46:58
1272次阅读
0个评论

bitmaps的基本命令都在string的的命令当中。


因为redis的key和value本身就支持二进制的存储方式,所以bitmaps只是一个独特的扩展。因为是面向字节操作,所以他的最大长度就是512M,最适合设置成2^32个不同字节。

一般的使用场景都是单一的统计,效率较高。比如签到啊,比如点击啊。有人可能会说用set也可以做出类似操作,的确,set就有类似功能,但是不同的实现会有不同的侧重点,

此就是相对于map效率高了很多。本身的存储方式就决定了他的效率。

2.Bitmap相关命令

(1)

命令:SETBIT key offset value

时间复杂度:O(1)

命令描述:对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。位的设置或清除取决于 value 参数,可以是 0 也可以是 1 。当 key 不存在时,自动生成一个新的字符串值。字符串会进行伸展(grown)以确保它可以将 value 保存在指定的偏移量上。当字符串值进行伸展时,空白位置以 0 填充。offset 参数必须大于或等于 0 ,小于 2^32 (bit 映射被限制在 512 MB 之内)。

返回值:指定偏移量原来储存的位。

(2)

命令:GETBIT key offset

时间复杂度:O(1)

命令描述:对 key 所储存的字符串值,获取指定偏移量上的位(bit)。当 offset 比字符串值的长度大,或者 key 不存在时,返回 0 。

返回值:字符串值指定偏移量上的位(bit)。

(3)

命令:BITCOUNT key [start] [end]

时间复杂度:O(N)

命令描述:计算给定字符串中,被设置为 1 的比特位的数量。一般情况下,给定的整个字符串都会被进行计数,通过指定额外的 start 或 end 参数,可以让计数只在特定的位上进行。start 和 end 参数的设置和 GETRANGE 命令类似,都可以使用负数值:比如 -1 表示最后一个位,而 -2 表示倒数第二个位,以此类推。不存在的 key 被当成是空字符串来处理,因此对一个不存在的 key 进行 BITCOUNT 操作,结果为 0 。

返回值:被设置为 1 的位的数量。

(4)

命令:BITOP operation destkey key [key ...]

时间复杂度:O(N)

命令描述:对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。

operation 可以是 AND 、 OR 、 NOT 、 XOR 这四种操作中的任意一种:

BITOP AND destkey key [key ...] ,对一个或多个 key 求逻辑并,并将结果保存到 destkey 。
BITOP OR destkey key [key ...] ,对一个或多个 key 求逻辑或,并将结果保存到 destkey 。
BITOP XOR destkey key [key ...] ,对一个或多个 key 求逻辑异或,并将结果保存到 destkey 。
BITOP NOT destkey key ,对给定 key 求逻辑非,并将结果保存到 destkey 。
除了 NOT 操作之外,其他操作都可以接受一个或多个 key 作为输入。

处理不同长度的字符串当 BITOP 处理不同长度的字符串时,较短的那个字符串所缺少的部分会被看作 0 。空的 key 也被看作是包含 0 的字符串序列。

当处理大型矩阵(matrix)或者进行大数据量的统计时,最好将任务指派到附属节点(slave)进行,避免阻塞主节点。

返回值:保存到 destkey 的字符串的长度,和输入 key 中最长的字符串长度相等。

ps:

第一个,就是bitcount命令,在使用start,end的时候一定要注意,setbit和getbit命令操作的是bit,但是bitcount用的是byte来计算位数,两者差了8倍,因此这点很容易采坑,也不建议用。

setbit的offset是用大小限制的,在0到 232(最大使用512M内存)之间,即0~4294967296之前,超过这个数会自动将offset转化为0,因此使用的时候一定要注意。




import com.alibaba.fastjson.JSONObject;
import org.junit.Test;
import redis.clients.jedis.BitOP;
import redis.clients.jedis.BitPosParams;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
/** BitMapTes
*/
public class BitMapTest {    
	//记录签到的开始时间:20180101    
	private static final long dateStart = 1514736000000L;    
	public BitMapTest() throws ParseException {}    
	/**     
	 * 假设用户ID是从1开始递增的LONG类型,那么可以用1个bitMap表示网站用户的当天的签到情况     
	 */    
	@Test    
	public void test1() {        
		Redis redis = RedisFactory.get();        
		int[] userId = new int[]{1, 5, 9, 111, 232, 434, 566, 121, 12123};        
		for (int i = 0; i < userId.length; i++) {            
			redis.setbit("user_sign_20180101", userId[i], true);        
		}        
		redis.setbit("user_sign", 8839, "1");        
		System.out.println("20180101 签到用户数:" + redis.bitcount("user_sign"));        
		for (int i = 0; i < userId.length; i++) {            
			redis.setbit("user_sign_20180102", userId[i], true);        
		}        
		for (int i = 0; i < userId.length; i++) {            
			redis.setbit("user_sign_20180103", userId[i], true);       
		}        
		for (int i = 0; i < userId.length; i++) {           
			redis.setbit("user_sign_20180105", userId[i], true);        
		}        
		for (int i = 0; i < userId.length; i++) {            
			redis.setbit("user_sign_20180106", userId[i], true);        
		}        
		for (int i = 0; i < userId.length; i++) {           
			redis.setbit("user_sign_20180107", userId[i], true);        
		}       
		for (int i = 0; i < userId.length; i++) {            
			redis.setbit("user_sign_20180126", userId[i], true);        
		}       
		for (int i = 0; i < userId.length; i++) {           
			redis.setbit("user_sign_20180206", userId[i], true);        
		}        
		//求20180206的总签到数        
		System.out.println(redis.bitcount("user_sign_20180206"));        
		//求用户111在20180206的签到情况        
		System.out.println("user_111_sign_20180206:" + redis.getbit("user_sign_20180206", 111));       
		//求用户111在201801的签到列表        
		LinkedHashMap user_111_201801 = new LinkedHashMap<String, Integer>();        
		for (int i = 20180101; i <= 20180131; i++) {            
			user_111_201801.put("" + i, redis.getbit("user_sign_" + i, 111) ? 1 : 0);       
		}        
		System.out.println(JSONObject.toJSONString(user_111_201801));        
		//求用户111在201801月份的连续签到天数        
		List<String> list = new ArrayList<>();        
		TreeMap<Integer, List<String>> map = new TreeMap<>();       
		for (int i = 20180101; i <= 20180131; i++) {           
			if (redis.getbit("user_sign_" + i, 111)) {               
				list.add("" + i);            
			}            
			if (!redis.getbit("user_sign_" + i, 111)) {            
				if (list.size() > 0) {                  
					map.put(list.size(), list);                   
					list = new ArrayList<>();               
				}           
			}        
		}        
		map.put(list.size(), list);       
		Map.Entry<Integer, List<String>> maxEntry = map.lastEntry();       
		System.out.println("userid:111,最大签到天数:" + maxEntry.getKey() + ",签到列表:" + maxEntry.getValue().toString());  
	}   
	/**     
	 * 用户ID不是从1递增的情况,用1个位图表示用户每天的签到情况     
	 */    
	@Test    
	public void test2() {       
		Redis redis = RedisFactory.get();       
		String[] userIds = new String[]{"ds0001a", "ds0003b", "ds0004c", "ds0005d"};    
		for (int i = 0; i < userIds.length; i++) {          
			redis.setbit("user_sign_" + userIds[i], getOffset("20180101"), true);        
			redis.setbit("user_sign_" + userIds[i], getOffset("20180102"), true);         
			redis.setbit("user_sign_" + userIds[i], getOffset("20180103"), true);        
			redis.setbit("user_sign_" + userIds[i], getOffset("20180105"), true);        
			redis.setbit("user_sign_" + userIds[i], getOffset("20180126"), true);        
			redis.setbit("user_sign_" + userIds[i], getOffset("20180206"), true);     
		}       
		//求20180206的总签到数,实现比较困难,用hyperloglog做粗略统计,或者用其他数据结构        
		//求用户ds0003b在20180206的签到情况        
		System.out.println("user_ds0003b_sign_20180206:" + redis.getbit("user_sign_ds0003b", getOffset("20180206")));      
		//求用户user_sign_ds0003b在201801的签到列表        
		LinkedHashMap user_sign_ds0003b_201801 = new LinkedHashMap<String, Integer>();      
		long soffset = getOffset("20180101");       
		for (int i = 0; i < 31; i++) {           
			user_sign_ds0003b_201801.put("201801" + (1 + i), redis.getbit("user_sign_ds0003b", soffset + i) ? 1 : 0);    
		}        
		System.out.println(JSONObject.toJSONString(user_sign_ds0003b_201801));       
		//求用户111在201801月份的连续签到天数    
	//  List<String> list = new ArrayList<>();   
	//    for (int i = 20180101; i <= 20180131; i++) {
			//            for (int j = i; j <= 20180131; j++) {
			//                if (i == j) {
			//                    if (redis.getbit("user_sign_ds0003b", getOffset("" + i))) {
			//                        System.out.println(1);
			//                    } else {
			//                        break;
			//                    }
			//                    continue;
			//                }
			//                
			//第一个,就是bitcount命令,在使用start,end的时候一定要注意,setbit和getbit命令操作的是bit,但是bitcount用的是byte来计算位数,两者差了8倍,因此这点很容易采坑,也不建议用。
			//                Long user_sign_ds0003b = redis.bitcount("user_sign_ds0003b", getOffset("" + i) + 1, getOffset("" + j) + 1);
			//                if (user_sign_ds0003b == j - i + 1) {
			//                    if (j == 20180131) {
			//                        list.add((j - i + 1) + "_" + i + ":" + j);
			//                    }
			//                } else {
			//                    if (j > i && user_sign_ds0003b > 0) {
			//                        list.add((j - i) + "_" + i + ":" + (j - 1));
			//                    }
			//                    break;
			//                }
			//            }
			//        }
			//        System.out.println(list.toString());        
		System.out.println(redis.bitpos("user_sign_ds0003b", true));       
		System.out.println(redis.bitpos("user_sign_ds0003b", true, new BitPosParams(1)));
		//又是坑,表示1*8位后第一个为true的位置        
		System.out.println(redis.bitfield("user_sign_ds0003b"));        
		System.out.println(redis.bitop(BitOP.AND, "new_key", "user_sign_ds0003b", "user_sign_ds0001a"));//位运算   
	} 
	
	private long getOffset(String ymd) {        
		try {            
			return (new SimpleDateFormat("yyyyMMdd").parse(ymd).getTime() - dateStart) / (1000 * 60 * 60 * 24);   
		} catch (ParseException e) {           
			e.printStackTrace();            
			return -1;        
		}    
	}    
	@Test    
	public void test3() throws ParseException {       
		System.out.println(new SimpleDateFormat("yyyyMMdd").parse("20180101").getTime());      
		System.out.println(new SimpleDateFormat("yyyyMMdd").parse("20180102").getTime());      
		System.out.println((new SimpleDateFormat("yyyyMMdd").parse("20180102").getTime() - new SimpleDateFormat("yyyyMMdd").parse("20180101").getTime()) / 1000 / 60 / 60 / 24);
		//        System.out.println(getOffset("20180102"));    
	}
	
		//5    GETBIT key offset 对 key 所储存的字符串值,获取指定偏移量上的位(bit)。
		//7    SETBIT key offset value 对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。
		
}
收藏00

登录 后评论。没有帐号? 注册 一个。