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

哈希概述

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小小工匠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值