哈希概述
Redis 中哈希结构就如同 Java 的 map 一样 , 一个对象里面有许多键值对,它是特别适合存储对象的.
如果内存足够大 ,那么一个 Redis 的 hash 结构可以存储2的32次方-1个键值对 ( 40多亿)。
在 Redis 中, hash 是一个 String 类型的 field 和 value 的映射表,因此我们存储的数据实际在 Redis 内存中都是一个个字符串而己。
假设artisan有 3 个字段 : 编号( id)、名称 (name )、性别( sex),这样就可以使用一个 hash 结构保存它。
在 Redis 中它就是一个这样的结构,其中 artisan代表的是这个 hash 结构在 Redis 内存的 key,通过它就可以找到这个 hash 结构,而 hash 结构由一系列的 field 和 value 组成
客户端操作hash
127.0.0.1:6379> HMSET artisan id 123 name littleArtisan sex female
OK
127.0.0.1:6379> HGETALL artisan
1) "id"
2) "123"
3) "name"
4) "littleArtisan"
5) "sex"
6) "female"
127.0.0.1:6379>
Redis hash 结构命令
官网:https://redis.io/commands#hash
命令 | 说明 | 备注 |
---|---|---|
hdel key field 1 [ field2 …] | 删除 hash 结构中的某个(些)字段 | 可以进行多个字段的删除 |
hexists key field | 判断 hash 结构中是否存在 field 字段 | 存在返回 1 ,否则返回0 |
hgetall key | 获取所有 hash 结构中的键值 | 返回键和值 |
hincrby key field increment | 指定给 hash 结构中的某一字段加上一个整数 | 要求该字段也是整数字符串 |
hincrbyfloat key field increment | 指定给 hash 结构中的某一字段加上一个浮点数 | 要求该字段是数字型字符串 |
hkeys key | 返回 hash 中所有的键 | |
hlen key | 返问 hash 中键值对的数量 | |
hmget key field1 [field2…] | 返回 hash 中指定的键的值,可以是多个 | 依次返回值 |
hmset key field1 value1 [field2 value2…] | hash 结构设置多个键值对 | |
hset key filed value | 在 hash 结构中设置键值对 | 单个设值 |
hsetnx key field value | 当 hash 结构中不存在对应的键,才设置值 | |
hvals key | 获取 hash 结构中所有的值 |
在 Redis 中的哈希结构和字符串有着比较明显的不同。
-
首先,命令都是以 h 开头,代表操作的是 hash 结构
-
其次,大多数命令多了一个层级 field,这是hash 结构的一个内部键,也就是说Redis 需要通过 key 索引到对应的 hash 结构,再通过 field来确定使用 hash 结构的哪个键值对
注意事项:
- 哈希结构的大小,如果哈希结构是个很大的键值对,那么使用它要十分注意。 尤其是关于 hkeys 、 hgetall 、 hvals 等返回所有哈希结构数据的命令,会造成大量数据的读取。这需要考虑性能和读取数据大小对 JVM 内存的影响 。
- 对于数字的操作命令 hincrby 而言,要求存储的也是整数型的字符串
- 对于hincrbyfloat 而言,则要求使用浮点数或者整数,否则命令会失败。
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379>
127.0.0.1:6379>
127.0.0.1:6379> HMSET obj k1 value1 k2 value2 k3 value3
OK
127.0.0.1:6379> HSET obj k4 6
(integer) 1
127.0.0.1:6379> HEXISTS obj k2
(integer) 1
127.0.0.1:6379> HGETALL obj
1) "k1"
2) "value1"
3) "k2"
4) "value2"
5) "k3"
6) "value3"
7) "k4"
8) "6"
127.0.0.1:6379> HINCRBY obj k4 8
(integer) 14
127.0.0.1:6379> HINCRBYFLOAT obj k4 6.2
"20.2"
127.0.0.1:6379> HKEYS obj
1) "k1"
2) "k2"
3) "k3"
4) "k4"
127.0.0.1:6379> HMGET obj k1 k2 k4
1) "value1"
2) "value2"
3) "20.2"
127.0.0.1:6379> HLEN obj
(integer) 4
127.0.0.1:6379> HSETNX obj k2 test
(integer) 0
127.0.0.1:6379> HSETNX obj k5 test
(integer) 1
127.0.0.1:6379> HGETALL obj
1) "k1"
2) "value1"
3) "k2"
4) "value2"
5) "k3"
6) "value3"
7) "k4"
8) "20.2"
9) "k5"
10) "test"
127.0.0.1:6379> HVALS obj
1) "value1"
2) "value2"
3) "value3"
4) "20.2"
5) "test"
127.0.0.1:6379> HDEL obj k5
(integer) 1
127.0.0.1:6379> HGETALL obj
1) "k1"
2) "value1"
3) "k2"
4) "value2"
5) "k3"
6) "value3"
7) "k4"
8) "20.2"
127.0.0.1:6379> HGET obj k4
"20.2"
127.0.0.1:6379>
Spring操作reids的hash
Step1 修改defaultSerializer
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="classpath:redis/redis.properties" />
<!--2,注意新版本2.3以后,JedisPoolConfig的property name,不是maxActive而是maxTotal,而且没有maxWait属性,建议看一下Jedis源码或百度。 -->
<!-- redis连接池配置 -->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<!--最大空闲数 -->
<property name="maxIdle" value="${redis.maxIdle}" />
<!--连接池的最大数据库连接数 -->
<property name="maxTotal" value="${redis.maxTotal}" />
<!--最大建立连接等待时间 -->
<property name="maxWaitMillis" value="${redis.maxWaitMillis}" />
<!--逐出连接的最小空闲时间 默认1800000毫秒(30分钟) -->
<property name="minEvictableIdleTimeMillis" value="${redis.minEvictableIdleTimeMillis}" />
<!--每次逐出检查时 逐出的最大数目 如果为负数就是 : 1/abs(n), 默认3 -->
<property name="numTestsPerEvictionRun" value="${redis.numTestsPerEvictionRun}" />
<!--逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1 -->
<property name="timeBetweenEvictionRunsMillis" value="${redis.timeBetweenEvictionRunsMillis}" />
<property name="testOnBorrow" value="true"></property>
<property name="testOnReturn" value="true"></property>
<property name="testWhileIdle" value="true"></property>
</bean>
<!--redis连接工厂 -->
<bean id="jedisConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
destroy-method="destroy">
<property name="poolConfig" ref="jedisPoolConfig"></property>
<!--IP地址 -->
<property name="hostName" value="${redis.host.ip}"></property>
<!--端口号 -->
<property name="port" value="${redis.port}"></property>
<!--如果Redis设置有密码 -->
<property name="password" value="${redis.password}" />
<!--客户端超时时间单位是毫秒 -->
<property name="timeout" value="${redis.timeout}"></property>
<property name="usePool" value="true" />
<!--<property name="database" value="0" /> -->
</bean>
<!-- 键值序列化器设置为String 类型 -->
<bean id="stringRedisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
<!-- redis template definition -->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
p:connection-factory-ref="jedisConnectionFactory"
p:keySerializer-ref="stringRedisSerializer"
p:defaultSerializer-ref="stringRedisSerializer"
p:valueSerializer-ref="stringRedisSerializer">
</bean>
</beans>
在 Redis 中, hash 是一个 String 类型的 field 和 value 的映射表。 Spring 对 Redis 进行了封装,所以有必要对 RedisTemplate 的配置项进行修改。修改defaultSerializer-ref
如果不指定的话就是
否则抛出如下异常
Cannot deserialize; nested exception is org.springframework.core.serializer.support.SerializationFailedException: Failed to deserialize payload. Is the byte array a result of corresponding serialization for DefaultDeserializer?; nested exception is java.io.EOFException
Step2 操作hash
package com.artisan.redis.baseStructure.hash;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.data.redis.core.RedisTemplate;
public class SpringRedisHashDemo {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:spring/spring-redis-hash.xml");
RedisTemplate redisTemplate = (RedisTemplate) ctx.getBean("redisTemplate");
// 127.0.0.1:6379> HMSET obj k1 value1 k2 value2 k3 value3
// OK
String key = "obj";
Map<String, String> map = new HashMap<String, String>();
map.put("k1", "value1");
map.put("k2", "value2");
map.put("k3", "value3");
redisTemplate.opsForHash().putAll(key, map);
// 127.0.0.1:6379> HSET obj k4 6
// (integer) 1
redisTemplate.opsForHash().put(key, "k4", String.valueOf(6));
// 127.0.0.1:6379> HEXISTS obj k2
// (integer) 1
boolean exist = redisTemplate.opsForHash().hasKey(key, "k2");
System.out.println(key + " 这个键中是否存在 k2这个field:" + exist);
// 127.0.0.1:6379> HGETALL obj
// 1) "k1"
// 2) "value1"
// 3) "k2"
// 4) "value2"
// 5) "k3"
// 6) "value3"
// 7) "k4"
// 8) "6"
Map<String,String> map2 = redisTemplate.opsForHash().entries(key);
if (map2 != null) {
scanMap(map2);
}
// 127.0.0.1:6379> HINCRBY obj k4 8
// (integer) 14
System.out.println(redisTemplate.opsForHash().increment(key, "k4", 8));
// 127.0.0.1:6379> HINCRBYFLOAT obj k4 6.2
// "20.2"
System.out.println(redisTemplate.opsForHash().increment(key, "k4", 6.2));
// 127.0.0.1:6379> HKEYS obj
// 1) "k1"
// 2) "k2"
// 3) "k3"
// 4) "k4"
Set<String> set = redisTemplate.opsForHash().keys(key);
for (String str : set) {
System.out.println(str);
}
// 127.0.0.1:6379> HMGET obj k1 k2 k4
// 1) "value1"
// 2) "value2"
// 3) "20.2"
List<String> list = new ArrayList<String>();
list.add("k1");
list.add("k2");
list.add("k4");
List<String> list2 = redisTemplate.opsForHash().multiGet(key, list);
scanList(list2);
// 127.0.0.1:6379> HLEN obj
// (integer) 4
System.out.println(redisTemplate.opsForHash().size(key));
// 127.0.0.1:6379> HSETNX obj k2 test
// (integer) 0
System.out.println(redisTemplate.opsForHash().putIfAbsent(key, "k2", "test"));
// 127.0.0.1:6379> HSETNX obj k5 test
// (integer) 1
System.out.println(redisTemplate.opsForHash().putIfAbsent(key, "k5", "test"));
// 127.0.0.1:6379> HGETALL obj
// 1) "k1"
// 2) "value1"
// 3) "k2"
// 4) "value2"
// 5) "k3"
// 6) "value3"
// 7) "k4"
// 8) "20.2"
// 9) "k5"
// 10) "test"
Map<String, String> map3 = redisTemplate.opsForHash().entries(key);
if (map3 != null) {
scanMap(map3);
}
// 127.0.0.1:6379> HVALS obj
// 1) "value1"
// 2) "value2"
// 3) "value3"
// 4) "20.2"
// 5) "test"
List<String> list3 = redisTemplate.opsForHash().values(key);
scanList(list3);
// 127.0.0.1:6379> HDEL obj k5
// (integer) 1
redisTemplate.opsForHash().delete(key, "k5");
// 127.0.0.1:6379> HGETALL obj
// 1) "k1"
// 2) "value1"
// 3) "k2"
// 4) "value2"
// 5) "k3"
// 6) "value3"
// 7) "k4"
// 8) "20.2"
Map<String, String> map4 = redisTemplate.opsForHash().entries(key);
if (map4 != null) {
scanMap(map4);
}
// 127.0.0.1:6379> HGET obj k4
// "20.2"
System.out.println(redisTemplate.opsForHash().get(key, "k4"));
}
private static void scanList(List<String> list2) {
for (String string : list2) {
System.out.println(string);
}
}
private static void scanMap(Map<String, String> map4) {
for (Map.Entry<String, String> entry : map4.entrySet()) {
System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
}
}
}
输出
INFO : org.springframework.context.support.ClassPathXmlApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@73a8dfcc: startup date [Thu Sep 20 19:13:10 CST 2018]; root of context hierarchy
INFO : org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [spring/spring-redis-hash.xml]
obj 这个键中是否存在 k2这个field:true
Key = k2, Value = value2
Key = k3, Value = value3
Key = k4, Value = 6
Key = k1, Value = value1
14
20.2
k1
k2
k3
k4
value1
value2
20.2
4
false
true
Key = k2, Value = value2
Key = k5, Value = test
Key = k3, Value = value3
Key = k1, Value = value1
Key = k4, Value = 20.2
value1
value2
value3
20.2
test
Key = k3, Value = value3
Key = k2, Value = value2
Key = k4, Value = 20.2
Key = k1, Value = value1
20.2
- hmset 命令,在 Java 的 API 中,是使用 map 保存多个键值对。
- hgetall 命令会返回所有的键值对,并保存到一个 map 对象中,如果 hash 结构很大,那么要考虑它对 JVM 的内存影响。
- hincrby 和 hincrbyFloat 命令都采用 increment 方法, Spring 会识别它具体使用何种方法。
- redisTemplate.opsForHash().values(key)方法相当于 hvals 命令,它会返回所有的值,并保存到一个 List 对象中;
- redisTemplate.opsForHash().keys(key)方法相当于 hkeys命令,它会获取所有的键,保存到一个 Set 对象中 。
- 在 Spring 中使用 redisTemplate.opsForHash().putAll(key, map )方法相当于执行了hmset 命令,使用了 map ,由于配置了默认的序列化器为字符串,所以它也只会用字符串进行转化,这样才能执行对应的数值加法,如果使用其他序列化器,则后面的命令可能会抛出异常。
- 在使用大的 hash 结构时,需要考虑返回数据的大小,以避免返回太多的数据,引发JVM内存溢出或者 Redis 的性能问题。
注意
使用 Spring 提供的 RedisTemplate 去展示多个命令可以学习到如何使用 RedisTemplate 操作 Redis 。 实际工作中并不是那么用的,因为每一 个操作会尝试从连接池里获取 一 个新的 Redis 连接,多个命令应该使用SessionCallback 接口进行操作 。
代码
代码托管到了 https://github.com/yangshangwei/redis_learn