目录
- 一、简介
- 二、常用方法
- 2.1、HSET
- 2.2、HSETNX
- 2.3、HGET
- 2.4、HINCRBY、HINCRBYFLOAT
- 2.5、HSTRLEN
- 2.6、HEXISTS
- 2.7、HDEL
- 2.8、HLEN
- 2.9、HMSET、HMGET
- 2.10、HKEYS、HVALS、HGETALL
- 2.11、HSCAN
一、简介
本文今天主要是讲哈希(Hash)的方法的使用,以及redis对应的Java实现该怎么用。因为篇幅问题,我这里写了一个测试类,引入 RedisTemplate对象,后面例子里就不一一引入了。大家理解就行,如果大家还不知道如何通过Spring Boot 整合redis则可以查看我之前的文章:SpringBoot整合redis(redis支持单节点和集群)
package com.alian.datastruct;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class RedisHashTest {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
}
二、常用方法
2.1、HSET
- HSET 将哈希表 key 中的字段 field 的值设为 value
语法
HSET KEY_NAME FIELD VALUE
命令操作
127.0.0.1:6379> hset hash1 author alian
(integer) 1
127.0.0.1:6379> hget hash1 author
"alian"
Java操作
@Test
public void hSet() {
String redisKey = "hash1";
redisTemplate.delete(redisKey);
// 设置作者为alian
redisTemplate.opsForHash().put(redisKey, "author","alian");
// 获取缓存中的作者
Object author = redisTemplate.opsForHash().get(redisKey, "author");
log.info("获取到的作者的信息:{}", author);
}
获取到的作者的信息:alian
2.2、HSETNX
- HSETNX 只有在字段 field 不存在时,设置哈希表字段的值
语法
HSETNX KEY_NAME FIELD VALUE
命令操作
127.0.0.1:6379> hsetnx hash2 title Redis
(integer) 1
127.0.0.1:6379> hsetnx hash2 title Java
(integer) 0
127.0.0.1:6379> hget hash2 title
"Redis"
- 哈希列表(hash2)中如果字段 title 不存在,设置 title 的值为 Redis
- 哈希列表(hash2)中如果字段 title 不存在,设置 title 的值为 Java
- 获取到哈希列表(hash2)中字段 title 的值为 Redis
Java操作
@Test
public void hSetNX() {
String redisKey = "hash2";
redisTemplate.delete(redisKey);
// 设置标题为Redis,如果title不存在
redisTemplate.opsForHash().putIfAbsent(redisKey, "title","Redis");
// 设置标题为Java,如果title不存在
// 此时title已存在了,设置失败
redisTemplate.opsForHash().putIfAbsent(redisKey, "title","Java");
// 获取缓存中的标题
Object author = redisTemplate.opsForHash().get(redisKey, "title");
log.info("获取到的标题的信息:{}", author);
}
获取到的标题的信息:Redis
2.3、HGET
- HGET 获取存储在哈希表中指定字段 field 的值
语法
HGET KEY_NAME FIELD_NAME
命令操作
127.0.0.1:6379> hset hash3 title Redis
(integer) 1
127.0.0.1:6379> hget hash3 title
"Redis"
Java操作
@Test
public void hGet() {
String redisKey = "hash3";
redisTemplate.delete(redisKey);
// 设置标题为Redis为Redis
redisTemplate.opsForHash().put(redisKey, "title","Redis");
// 获取缓存中的标题
Object title = redisTemplate.opsForHash().get(redisKey, "title");
log.info("获取到的标题的信息:{}", title);
}
获取到的标题的信息:Redis
2.4、HINCRBY、HINCRBYFLOAT
- HINCRBY 为哈希表 key 中的指定 整形字段 整形字段的加上增量 increment( 只能整数值 )
- HINCRBYFLOAT 为哈希表 key 中的指定的整形或者浮点型字段的加上增量 increment(整数或者小数都行)
语法
HINCRBY KEY_NAME FIELD_NAME INCR_BY_NUMBER
HINCRBYFLOAT KEY_NAME FIELD_NAME INCREMENT
命令操作
127.0.0.1:6379> hincrby hash4 num 1
(integer) 1
127.0.0.1:6379> hincrby hash4 num 3
(integer) 4
127.0.0.1:6379> hincrby hash4 num -2
(integer) 2
127.0.0.1:6379> hget hash4 num
"2"
127.0.0.1:6379> hincrbyfloat hash4 money 1.5
"1.5"
127.0.0.1:6379> hincrbyfloat hash4 money -0.5
"1"
127.0.0.1:6379> hgetall hash4
1) "num"
2) "2"
3) "money"
4) "1"
- 给哈希列表(hash4)中整形字段 num 的值 加1,得到 1
- 给哈希列表(hash4)中整形字段 num 的值 加3,得到 4
- 给哈希列表(hash4)中整形字段 num 的值 减2,得到 2
- 给哈希列表(hash4)中浮点型字段 money 的值 加1.5,得到 1.5
- 给哈希列表(hash4)中浮点型字段 money 的值 减0.5,得到 1
- 获取到哈希列表(hash4)的值,得到num=2,money=1
Java操作
@Test
public void hIncByAndHIncByFloat() {
String redisKey = "hash4";
redisTemplate.delete(redisKey);
// 存储在哈希列表中的num加1
Long num1 = redisTemplate.opsForHash().increment(redisKey, "num", 1);
log.info("存储在哈希列表中的num加1后的值:{}", num1);
// 存储在哈希列表中的num加3
Long num2 = redisTemplate.opsForHash().increment(redisKey, "num", 3);
log.info("存储在哈希列表中的num加3后的值:{}", num2);
// 存储在哈希列表中的num减2
Long num3 = redisTemplate.opsForHash().increment(redisKey, "num", -2);
log.info("存储在哈希列表中的num减2后的值:{}", num3);
// 存储在哈希列表中的money加1.5
Double double1 = redisTemplate.opsForHash().increment(redisKey, "money", 1.5);
log.info("存储在哈希列表中的money加1.5后的值:{}", double1);
// 存储在哈希列表中的money减0.5
Double double2 = redisTemplate.opsForHash().increment(redisKey, "money", -0.5);
log.info("存储在哈希列表中的money减0.5后的值:{}", double2);
Map<Object, Object> entries = redisTemplate.opsForHash().entries(redisKey);
log.info("获取哈希列表的值:{}", entries);
}
存储在哈希列表中的num加1后的值:1
存储在哈希列表中的num加3后的值:4
存储在哈希列表中的num减2后的值:2
存储在哈希列表中的money加1.5后的值:1.5
存储在哈希列表中的money减0.5后的值:1.0
获取哈希列表的值:{num=2, money=1}
2.5、HSTRLEN
- HINCRBY 为哈希表 key 中的指定 整形字段 整形字段的加上增量 increment( 只能整数值 )
语法
HSTRLEN KEY_NAME FIELD_NAME
命令操作
127.0.0.1:6379> hset hash5 content "Redis is an open source"
(integer) 1
127.0.0.1:6379> hstrlen hash5 content
(integer) 23
127.0.0.1:6379> hstrlen hash5 num
(integer) 0
- 哈希列表(hash5)中设置字段 content 的值为 Redis is an open source,得到长度 1
- 获取哈希列表(hash5)中字段 content 的长度,得到 23
- 获取哈希列表(hash5)中不存在的字段 num 的长度,得到 0
Java操作
@Test
public void hStrLen() {
String redisKey = "hash5";
redisTemplate.delete(redisKey);
// 设置内容content为:Redis is an open source
redisTemplate.opsForHash().put(redisKey, "content","Redis is an open source");
// 获取存储在哈希列表中的content的长度
Long length1 = redisTemplate.opsForHash().lengthOfValue(redisKey, "content");
log.info("获取存储在哈希列表中的content的长度:{}", length1);
Long length2 = redisTemplate.opsForHash().lengthOfValue(redisKey, "num");
log.info("获取存储在哈希列表中不存在的字段的长度:{}", length2);
}
获取存储在哈希列表中的content的长度:25
获取存储在哈希列表中不存在的字段的长度:0
2.6、HEXISTS
- HEXISTS 查看哈希表的指定字段是否存在
语法
HEXISTS KEY_NAME FIELD_NAME
命令操作
127.0.0.1:6379> hset hash6 author alian
(integer) 1
127.0.0.1:6379> hexists hash6 author
(integer) 1
127.0.0.1:6379> hexists hash6 num
(integer) 0
- 哈希列表(hash6)中设置字段 author 的值为 alian
- 判断哈希列表(hash6)中字段 author 是否存在,得到 1,表示存在
- 判断哈希列表(hash6)中字段 num 是否存在,得到 0,表示不存在
Java操作
@Test
public void hExists() {
String redisKey = "hash6";
redisTemplate.delete(redisKey);
// 设置作者为alian
redisTemplate.opsForHash().put(redisKey, "author","alian");
Boolean hasAuthorKey = redisTemplate.opsForHash().hasKey(redisKey, "author");
log.info("判断哈希列表中author字段是否存在:{}", hasAuthorKey);
Boolean hasNumKey = redisTemplate.opsForHash().hasKey(redisKey, "num");
log.info("判断哈希列表中num字段是否存在:{}", hasNumKey);
}
判断哈希列表中author字段是否存在:true
判断哈希列表中num字段是否存在:false
2.7、HDEL
- HEXISTS 删除一个或多个哈希表字段
语法
HDEL KEY_NAME FIELD1.. FIELDn
命令操作
127.0.0.1:6379> hset hash7 author alian
(integer) 1
127.0.0.1:6379> hset hash7 title Redis
(integer) 1
127.0.0.1:6379> hset hash7 content "Redis is an open source"
(integer) 1
127.0.0.1:6379> hgetall hash7
1) "author"
2) "alian"
3) "title"
4) "Redis"
5) "content"
6) "Redis is an open source"
127.0.0.1:6379> hdel hash7 content
(integer) 1
127.0.0.1:6379> hdel hash7 author title
(integer) 2
127.0.0.1:6379> hgetall hash7
(empty list or set)
- 先初始化一个哈希列表(hash7),设置字段 author 的值为 alian;字段 title 的值为 Redis;字段 content 的值为 Redis is an open source
- 删除哈希列表(hash7)中单个字段 content
- 删除哈希列表(hash7)中多个字段 author 和 title
Java操作
@Test
public void hDel() {
String redisKey = "hash7";
redisTemplate.delete(redisKey);
// 初始化数据
Map<String, Object> hashMap = new HashMap<>();
hashMap.put("author","alian");
hashMap.put("title","Redis");
hashMap.put("content","Redis is an open source");
redisTemplate.opsForHash().putAll(redisKey, hashMap);
// 获取哈希列表的信息
Map<Object, Object> result = redisTemplate.opsForHash().entries(redisKey);
log.info("获取哈希列表的信息:{}", result);
// 删除哈希列表中单个字段:content
redisTemplate.opsForHash().delete(redisKey, "content");
// 删除哈希列表中单个字段:author,title
redisTemplate.opsForHash().delete(redisKey, "author","title");
Map<Object, Object> entries = redisTemplate.opsForHash().entries(redisKey);
log.info("获取哈希列表的信息:{}", entries);
}
获取哈希列表的信息:{author=alian, title=Redis, content=Redis is an open source}
获取哈希列表的信息:{}
2.8、HLEN
- HLEN 获取哈希表中字段的数量
语法
HLEN KEY_NAME
命令操作
127.0.0.1:6379> hset hash8 author alian
(integer) 1
127.0.0.1:6379> hset hash8 title Redis
(integer) 1
127.0.0.1:6379> hset hash8 content "Redis is an open source"
(integer) 1
127.0.0.1:6379> hgetall hash8
1) "author"
2) "alian"
3) "title"
4) "Redis"
5) "content"
6) "Redis is an open source"
127.0.0.1:6379> hlen hash8
(integer) 3
- 先初始化一个哈希列表(hash8),设置字段 author 的值为 alian;字段 title 的值为 Redis;字段 content 的值为 Redis is an open source
- 获取列表哈希列表(hash8)中单个字段的个数,得到 3
Java操作
@Test
public void hLen() {
String redisKey = "hash8";
redisTemplate.delete(redisKey);
// 初始化数据
Map<String, Object> hashMap = new HashMap<>();
hashMap.put("author","alian");
hashMap.put("title","Redis");
hashMap.put("content","Redis is an open source");
redisTemplate.opsForHash().putAll(redisKey, hashMap);
// 获取哈希列表的信息
Map<Object, Object> result = redisTemplate.opsForHash().entries(redisKey);
log.info("获取哈希列表的信息:{}", result);
// 获取哈希列表中字段数量
Long size = redisTemplate.opsForHash().size(redisKey);
log.info("获取哈希列表中字段数量为:{}", size);
}
获取哈希列表的信息:{author=alian, title=Redis, content=Redis is an open source}
获取哈希列表中字段数量为:3
2.9、HMSET、HMGET
- HMSET 获取哈希表中字段的数量
语法
命令操作
127.0.0.1:6379> hmset hash9 author alian title Redis content "Redis is an open source"
OK
127.0.0.1:6379> hgetall hash9
1) "author"
2) "alian"
3) "title"
4) "Redis"
5) "content"
6) "Redis is an open source"
127.0.0.1:6379> hmget hash9 author title
1) "alian"
2) "Redis"
- 先初始化一个哈希列表(hash9),设置字段 author 的值为 alian;字段 title 的值为 Redis;字段 content 的值为 Redis is an open source
- 获取列表哈希列表(hash9)中字段 author 和字段 title 的值,得到 alian 和 Redis
Java操作
@Test
public void hmSetAndHmGet() {
String redisKey = "hash9";
redisTemplate.delete(redisKey);
// 初始化数据
Map<String, Object> hashMap = new HashMap<>();
hashMap.put("author","alian");
hashMap.put("title","Redis");
hashMap.put("content","Redis is an open source");
redisTemplate.opsForHash().putAll(redisKey, hashMap);
// 获取哈希列表的信息
Map<Object, Object> result = redisTemplate.opsForHash().entries(redisKey);
log.info("获取哈希列表的信息:{}", result);
List<Object> keys = Arrays.asList("author", "title");
// 获取哈希列表中多个字段的值
List<Object> list = redisTemplate.opsForHash().multiGet(redisKey, keys);
log.info("获取哈希列表中多个字段的值:{}", list);
}
获取哈希列表的信息:{author=alian, title=Redis, content=Redis is an open source}
获取哈希列表中多个字段的值:[alian, Redis]
2.10、HKEYS、HVALS、HGETALL
- HKEYS 获取所有哈希表中所有的字段
- HVALS 获取哈希表中所有值
语法
HKEYS KEY_NAME
命令操作
127.0.0.1:6379> hmset hash10 author alian title Redis content "Redis is an open source"
OK
127.0.0.1:6379> hkeys hash10
1) "author"
2) "title"
3) "content"
127.0.0.1:6379> hvals hash10
1) "alian"
2) "Redis"
3) "Redis is an open source"
127.0.0.1:6379> hgetall hash10
1) "author"
2) "alian"
3) "title"
4) "Redis"
5) "content"
6) "Redis is an open source"
- 先初始化一个哈希列表(hash10),设置字段 author 的值为 alian;字段 title 的值为 Redis;字段 content 的值为 Redis is an open source
- 获取列表哈希列表(hash10)中所有的字段得到: author 、 title 和 content
- 获取列表哈希列表(hash10)中所有的值得到: alian 、 Redis 和 Redis is an open source
- 获取列表哈希列表(hash10)的信息,得到: author=alian, title=Redis, content=Redis is an open source
Java操作
@Test
public void hKeysAndHValsAndHGetAll() {
String redisKey = "hash10";
redisTemplate.delete(redisKey);
// 初始化数据
Map<String, Object> hashMap = new HashMap<>();
hashMap.put("author","alian");
hashMap.put("title","Redis");
hashMap.put("content","Redis is an open source");
redisTemplate.opsForHash().putAll(redisKey, hashMap);
// 获取哈希列表中所有的字段
Set<Object> keySet = redisTemplate.opsForHash().keys(redisKey);
log.info("获取哈希列表中所有的字段:{}", keySet);
// 获取哈希列表中所有的值
List<Object> valueList = redisTemplate.opsForHash().values(redisKey);
log.info("获取哈希列表中所有的值:{}", valueList);
// 获取哈希列表的信息
Map<Object, Object> result = redisTemplate.opsForHash().entries(redisKey);
log.info("获取哈希列表的信息:{}", result);
}
获取哈希列表中所有的字段:[author, title, content]
获取哈希列表中所有的值:[alian, Redis, Redis is an open source]
获取哈希列表的信息:{author=alian, title=Redis, content=Redis is an open source}
2.11、HSCAN
- HSCAN 迭代哈希表中的键值对
语法
HSCAN key cursor [MATCH pattern] [COUNT count]
- cursor - 游标
- pattern - 匹配的模式(比如以Java开头的:Java*,)
- count - 指定从数据集里返回多少元素,默认值为 10
Java操作
我先演示Java的实现,给哈希列表(hash11)插入2000个值,字段为hash-i,值为value-i,这里的 i 的取值从0到2000。
@Test
public void hScan() {
String redisKey = "hash11";
redisTemplate.delete(redisKey);
Map<String, Object> hashMap = new HashMap<>();
for (int i = 0; i < 2000; i++) {
hashMap.put("Java" + i, "value" + i);
redisTemplate.opsForHash().put(redisKey, "Java-" + i, "value-" + i);
}
ScanOptions options = ScanOptions.scanOptions().match("Java*").count(100).build();
Cursor<Map.Entry<Object, Object>> cursor = redisTemplate.opsForHash().scan(redisKey, options);
while (cursor.hasNext()) {
Map.Entry<Object, Object> entry = cursor.next();
log.info("扫描到key:{},对应的value:{}", entry.getKey(), entry.getValue());
}
}
命令操作
127.0.0.1:6379> hscan hash11 0 match Java* count 3
1) "1024"
2) 1) "Java-1826"
2) "\"value-1826\""
3) "Java-722"
4) "\"value-722\""
5) "Java-933"
6) "\"value-933\""
127.0.0.1:6379> hscan hash11 1024 match Java* count 3
1) "256"
2) 1) "Java-1672"
2) "\"value-1672\""
3) "Java-601"
4) "\"value-601\""
5) "Java-1900"
6) "\"value-1900\""
7) "Java-1461"
8) "\"value-1461\""
9) "Java-934"
10) "\"value-934\""
127.0.0.1:6379> hscan hash11 256 match Java* count 3
1) "1792"
2) 1) "Java-270"
2) "\"value-270\""
3) "Java-118"
4) "\"value-118\""
5) "Java-1244"
6) "\"value-1244\""
127.0.0.1:6379>
先看官网的说明
虽然SCAN不能保证每次迭代返回的元素数量,但可以使用COUNT选项根据经验调整SCAN的行为。基本上,使用COUNT,用户指定了每次调用时要完成的工作量,以便从集合中检索元素。这只是对实现的一个提示,但一般来说,这是您在大多数情况下可以从实现中得到的。
- COUNT默认值为10
- 当遍历的目标Set、Hash、Sorted Set或者Key空间足够大可以使用一个哈希表表示并且不使用MATCH属性的前提下,Redis服务端会返回COUNT或者比COUNT大的遍历元素结果集合
- 当遍历只包含Integer值的Set集合(仅由整数组成的小集合)或者ziplists类型编码的Hash或者Sorted Set集合(小散列和由小单个值组成的集合)时,通常SCAN命令会返回集合中的所有元素,直接忽略COUNT属性
重要提示:不需要对每个迭代使用相同的COUNT值。只要在下一次调用中传递的游标是在上一次调用该命令时获得的游标,调用方就可以根据需要将计数从一次迭代更改为另一次迭代。
第三点就是在Hash集合中使用HSCAN命令COUNT属性失效的原因。Redis配置文件中有两个和Hash类型ziplist相关的配置
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
也就是说
- 当Hash集合中的数据项(即Field-Value对)的 数目超过512 的时候。
- 当Hash集合中插入的任意一个数据项(即Field-Value对)中的 Value长度超过64 的时候。
Hash集合的编码会由ziplist会转成dict,这个时候,HSCAN命令COUNT属性才会起效,当然此时Redis为Hash类型的数据会占用更多的内存空间。所以我这里插入了2000个数据演示这个游标,但是COUNT值不一定是那么多,有可能比这个多。