Redis-04Redis数据结构--哈希hash

版权声明:【show me the code ,change the world】 https://blog.csdn.net/yangshangwei/article/details/82788441

哈希概述

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

猜你喜欢

转载自blog.csdn.net/yangshangwei/article/details/82788441