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

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: Redis-04Redis数据结构--哈希hash

哈希概述


Redis 中哈希结构就如同 Java 的 map 一样 , 一个对象里面有许多键值对,它是特别适合存储对象的.


如果内存足够大 ,那么一个 Redis 的 hash 结构可以存储2的32次方-1个键值对 ( 40多亿)。


在 Redis 中, hash 是一个 String 类型的 field 和 value 的映射表,因此我们存储的数据实际在 Redis 内存中都是一个个字符串而己。


假设artisan有 3 个字段 : 编号( id)、名称 (name )、性别( sex),这样就可以使用一个 hash 结构保存它。

20180920155337458.png


在 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



20190109232644620.png

image.png


在 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


20180920191739683.png


如果不指定的话就是


20180920191834623.png

否则抛出如下异常

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

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
相关文章
|
2月前
|
存储 缓存 NoSQL
Redis核心数据结构与分布式锁实现详解
Redis 是高性能键值数据库,支持多种数据结构,如字符串、列表、集合、哈希、有序集合等,广泛用于缓存、消息队列和实时数据处理。本文详解其核心数据结构及分布式锁实现,帮助开发者提升系统性能与并发控制能力。
|
4月前
|
存储 NoSQL 算法
Redis设计与实现——数据结构与对象
Redis 是一个高性能的键值存储系统,其数据结构设计精妙且高效。主要包括以下几种核心数据结构:SDS、链表、字典、跳跃表、整数集合、压缩列表。此外,Redis 对象通过类型和编码方式动态转换,优化内存使用,并支持引用计数、共享对象和淘汰策略(如 LRU/LFU)。这些特性共同确保 Redis 在性能与灵活性之间的平衡。
|
7月前
|
NoSQL 算法 安全
Redis原理—1.Redis数据结构
本文介绍了Redis 的主要数据结构及应用。
Redis原理—1.Redis数据结构
|
7月前
|
存储 缓存 NoSQL
Redis哈希结构在提升数据检索速度中的实践应用
本文详细介绍了 Redis 哈希结构的特点、常见使用场景以及如何在实际应用中利用哈希结构提升数据检索速度。通过合理使用 Redis 哈希结构,可以显著提高系统的性能和响应速度。在实际开发中,结合具体业务需求,灵活运用 Redis 提供的多种数据结构,构建高效的缓存和数据存储解决方案。希望本文能帮助您更好地理解和应用 Redis 哈希结构,提升数据检索速度。
183 18
|
9月前
|
存储 消息中间件 缓存
Redis 5 种基础数据结构?
Redis的五种基础数据结构——字符串、哈希、列表、集合和有序集合——提供了丰富的功能来满足各种应用需求。理解并灵活运用这些数据结构,可以极大地提高应用程序的性能和可扩展性。
163 2
|
存储 NoSQL Redis
Redis命令——哈希(Hash)
Redis hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象。 Redis 中每个 hash 可以存储 232 - 1 键值对(40多亿)。
1533 0
|
存储 NoSQL Redis
redis必杀命令:哈希(Hash)
题记: Redis hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象。 Redis 中每个 hash 可以存储 232 - 1 键值对(40多亿)。
1070 0
|
4月前
|
缓存 NoSQL 关系型数据库
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
|
4月前
|
缓存 NoSQL Java
Redis+Caffeine构建高性能二级缓存
大家好,我是摘星。今天为大家带来的是Redis+Caffeine构建高性能二级缓存,废话不多说直接开始~
637 0