Redis的散列类型可以看做Java中的Map结构,后文简称Map,同时Redis中操纵Map的指令均已H开头。一个散列类型可以存储2的32次方-1个字段,即内部Key-Value的对数。
可以将Map当做Java中的HashMap,这样便于快速理解。既然将其看做Map,那Redis的这个Map肯定与Java中的Map有相似之处,如下图:
可以看到Key对应的Value是一个Map,而Map内部又有Key-Value键值对。内部的Key也是不能重复的,Value的值也是会被覆盖的。
1、设值/取值(put/get)
使用HSET与HGET获取Map的值,当然,与String类型一样,Map也能一次性获取多个或者设置多个值(HMSET/HMGET)。但设置多个值时,Key-Value一定要成对出现。否则当前指令会执行失败,原Map不会有任何改变。
可以看到取出来的值是String类型的。同时Key的值没有重复,且Value的值会被覆盖,如果获取的Key对应Map的key不存在,则会返回nil。
还有一点值得注意的是HSET,如果是插入新的Key-Value返回的是1,如果是修改旧的Value,返回是0.
2、获取Map内部的Key-Value对数(Map的size())。
使用HLEN指令可以获取指定的Map的Key-Value对数。同时,这个指令与STRLEN和LLEN指令一致,会在Key不存在时返回0.
3、获取Map的所有Key或者所有Value
指令HKEYS获取指定Map的所有Key,与Java中Map的KeySet一致。
指令KVALS获取指定Map的所有Value,在Java的Map中,对应values(),但有所不同。
示例如下:
4、获取Map的所有Key-Value。
可以使用指令HGETALL获取Map的所有Key-Value,与之对应的方法是Java中Map的entrySet方法。演示如下
单行为Key,双行为Value,获取不存在的Redis的Key,返回empty list,可以从提示信息看到,这个指令是将Map遍历然后转换成List类型呈现。
5、判断Map中的Key是否存在(containsKey)
指令用于HEXISTS用于判断Map中对应的Key是否存在。演示如下
我的Redis中是没有Key1这个键存在的,可以看到使用HEXISTS返回0表示Map中没有这个键,但也有可能是这个Map根本不存在。
6、删除Map中的Key(remove)
使用HDEL指令删除Map中的Key,格式HDEL map field。演示如下:
这个指令是可以跟多个field的,可以从图看到,一次性删除了name和color属性。
7、当不存在时设置
指令HSETNX用于在Map的Key不存在时执行HSET操作,如果存在,则不作任何操作。这个指令可以看做HSET NOT EXISTS的缩写。
演示如下:
可以看到当Map的Key存在时,这条指令并没有对Value其进行覆盖。在Java中与之相对应的方法是putIfAbsent。与之对应的Java代码如下:
default V putIfAbsent(K key, V value) {
V v = get(key);
if (v == null) {
v = put(key, value);
}
return v;
}
但有区别,上述代码是分成了两步操作 1) 判断Key是否存在 2) 不存在进行put操作。而HSETNX是一个原子操作指令。
8、HINCRBY指令
看到这个指令会想起String类型的INCR、DECR等指令。的确,这个指令与INCRBY指令操作完全一致,并且都会在对应的Key不存在时执行SET操作。演示如下:
上图中的Key1并不存在,可以看到HINCRBY指令在Map以及Map的Key都不存在的情况下,会先创建Map,然后HSET操作。这个指令只对Value为字符串类型的整数有效,如下图:
Redis的Java操作
测试代码如下,略长。
package org.yamikaze.redis.test;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import redis.clients.jedis.Jedis;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.junit.Assert.*;
/**
* 使用Java代码连接测试Redis的Map类型
* @author yamikaze
*/
public class MapTest {
private Jedis client;
private static final String KEY = "mapKey";
private String key1 = "key1";
private String value1 = "value1";
private String key2 = "key2";
private String value2 = "value2";
private Map map;
@Before
public void setUp() {
client = MyJedisFactory.defaultJedis();
map = new HashMap(4);
map.put(key1, value1);
map.put(key2, value2);
}
/**
* 测试普通的HSET与HGET
*/
@Test
public void testMapPutAndGet() {
Long result = client.hset(KEY, key1, value1);
assertTrue(result.equals(1L));
String value = client.hget(KEY, key1);
assertEquals(value1, value);
}
/**
* 测试HMSET与HMSET
*/
@Test
public void testMapMPutAndMGet() {
String result = client.hmset(KEY, map);
assertTrue("OK".equals(result));
List list = client.hmget(KEY, key1, key2);
assertNotNull(list);
assertTrue(list.size() == 2);
}
/**
* 测试HLEN
*/
@Test
public void testMapHLen() {
String result = client.hmset(KEY, map);
assertTrue("OK".equals(result));
Long size = client.hlen(KEY);
assertEquals(size.intValue(), map.size());
}
/**
* 测试HKEYS
*/
@Test
public void testMapHKeys() {
String result = client.hmset(KEY, map);
assertTrue("OK".equals(result));
Set redisKeys = client.hkeys(KEY);
Set keys = map.keySet();
assertEquals(redisKeys.size(), keys.size());
/**
* 两个Set的元素都应该一致.
* 不能使用将下面的代码中Keys与RedisKeys互换位置。
* 因为HashMap返回的Set是内部KeySet,不支持add操作。
* 你可以认为HashMap返回的Set是一个可读副本
*/
for(String key : keys ) {
assertFalse(redisKeys.add(key));
}
}
/**
* 测试HEXISTS
*/
@Test
public void testMapHExists() {
String result = client.hmset(KEY, map);
assertTrue("OK".equals(result));
assertTrue(client.hexists(KEY, key1));
assertTrue(client.hexists(KEY, key2));
assertFalse(client.hexists(KEY, "abc"));
}
/**
* 测试HDEL
*/
@Test
public void testMapHDel() {
String result = client.hmset(KEY, map);
assertTrue("OK".equals(result));
Long delResult = client.hdel(KEY, key1, key2);
assertTrue(delResult.intValue() == map.size());
assertTrue(client.hlen(KEY).intValue() == 0);
}
/**
* 测试HSETNX
*/
@Test
public void testMapHSetNX() {
String result = client.hmset(KEY, map);
assertTrue("OK".equals(result));
Long result1 = client.hsetnx(KEY, key1, "123");
String value = client.hget(KEY, key1);
assertEquals(value1, value);
assertTrue(result1 == 0);
Long result2 = client.hsetnx(KEY, "abc", value2);
assertTrue(result2 == 1);
String value3 = client.hget(KEY, "abc");
assertEquals(value3, value2);
}
/**
* 测试HGETALL
*/
@Test
public void testMapGetAll() {
String result = client.hmset(KEY, map);
assertTrue("OK".equals(result));
Map redisMap = client.hgetAll(KEY);
assertNotNull(redisMap);
assertEquals(redisMap.size(), map.size());
Set> entrySet = map.entrySet();
for(Map.Entry entry : entrySet) {
String key = entry.getKey();
String val = entry.getValue();
String redisValue = redisMap.get(key);
assertTrue(redisMap.containsKey(key));
assertEquals(val, redisValue);
}
}
/**
* 测试HINCRBY
*/
@Test
public void testMapHIncrBy() {
String result = client.hmset(KEY, map);
assertTrue("OK".equals(result));
Long result1 = client.hincrBy(KEY, "abc", 2);
assertEquals(result1.intValue(), 2);
}
/**
* HVALS指令略
*/
@After
public void tearDown() {
if(client != null) {
client.del(KEY);
}
}
}
Redis的Map类型可以将其当做是Java中的Map,但又有所不同,可以将对象序列化成字符串进行存储。既然转换为字符串,就与对象原本的类型无关了。所以与Java中的Map第一个区别是Java的Map的Value只能为一种类型,而Redis的Map的Value可以多种类型(字符串形式)。第二个区别是遍历Redis中Map的Key,Value或者Key-Value会被转换为List或Set进行操作,HKEYS是转为Set,HVAL是转为List,HGETALL是转为List。为什么HKEYS是Set,而HVAL是List,是因为Map的特性使然,这一点与Java的Map保持了一致,分别对应keySet(),values(),entrySet()方法。但这三个方法又与Redis的指令有所区别,这儿不在详细讲解区别。第三个区别Redis提供了一些Java中Map没有的操作,例如HINCRBY指令。同时Java Map也有一些操作Redis没有,例如clear(), getOrDefault().
参考资料
《Redis入门指南》