前言:

Redis hash是一个String类型的field和value的映射表。添加、删除操作复杂度平均为O(1),为什么是平均呢?因为Hash的内部结构包含zipmap和hash两种。hash特别适合用于存储对象。相对于将对象序列化存储为String类型,将一个对象存储在hash类型中会占用更少的内存,并且可以方便的操作对象。为什么省内存,因为对象刚开始使用zipmap存储的。

  1. zipmap

        zipmap其实并不是hashtable,zip可以节省hash本身需要的一些元数据开销。zipmap的添加、删除、查找复杂度为O(n),但是filed数量都不多,所以可以说平均是O(1)。

默认配置:
            hash-max-ziplist-entries 512  //filed最多512个
            hash-max-ziplist-value 64     //value最大64字节

内存分配如下:

例:"foo" => "bar", "hello" => "world":<zmlen><len>"foo"<len><free>"bar"<len>"hello"<len><free>"world"

(1)zmlen:记录当前zipmap的key-value对的数量。一个字节,因此规定其表示的数量只能为0~254,当zmlen>254时,就需要遍历整个zipmap来得到key-value对的个数
          (2)len:记录key或value的长度,有两种情况,当len的第一个字节为0~254(注释是253,我们以代码为准)时,那么len就只占用这一个字节。若len的第一个字节为254时,那么len将用后面的4个字节来表示。因此len要么占用1字节,要么占用5字节。
          (3)free:记录value后面的空闲字节数,将”foo” => “world”变为”foo” => “me” ,那么会导致3个字节的空闲空间。当free的字节数过大用1个字节不足以表示时,zipmap就会重新分配内存,保证字符串尽量紧凑。
          (4)end: 记录zipmap的结束,0xFF

zipmap创建:

  2.hash

在Redis中,hash表被称为字典(dictionary),采用了典型的链式解决冲突方法,即:当有多个key/value的key的映射值(每对key/value保存之前,会先通过类似HASH(key) MOD N的方法计算一个值,
  以便确定其对应的hash table的位置)相同时,会将这些value以单链表的形式保存;同时为了控制哈希表所占内存大小,redis采用了双哈希表(ht[2])结构,并逐步扩大哈希表容量(桶的大小)的策略,
  即:刚开始,哈希表ht[0]的桶大小为4,哈希表ht[1]的桶大小为0,待冲突严重(redis有一定的判断条件)后,ht[1]中桶的大小增为ht[0]的两倍,并逐步(注意这个词:”逐步”)将哈希表ht[0]中元素迁移(称为“再次Hash”)到ht[1],
  待ht[0]中所有元素全部迁移到ht[1]后,再将ht[1]交给ht[0](这里仅仅是C语言地址交换),之后重复上面的过程。

Redis哈希表的实现位于文件dict.h和dict.c中,主要数据结构如下:

#define DICT_NOTUSED(V) ((void) V)

typedef struct dictEntry {
void *key;
union {
void *val;
uint64_t u64;
int64_t s64;
double d;
} v;
struct dictEntry *next;
} dictEntry; typedef struct dictType {
unsigned int (*hashFunction)(const void *key);
void *(*keyDup)(void *privdata, const void *key);
void *(*valDup)(void *privdata, const void *obj);
int (*keyCompare)(void *privdata, const void *key1, const void *key2);
void (*keyDestructor)(void *privdata, void *key);
void (*valDestructor)(void *privdata, void *obj);
} dictType; /* This is our hash table structure. Every dictionary has two of this as we
* implement incremental rehashing, for the old to the new table. */
typedef struct dictht {
dictEntry **table;
unsigned long size;
unsigned long sizemask;
unsigned long used;
} dictht; typedef struct dict {
dictType *type;
void *privdata;
dictht ht[];
long rehashidx; /* rehashing not in progress if rehashidx == -1 */
int iterators; /* number of iterators currently running */
} dict;

基本操作:
        Redis中hash table主要有以下几个对外提供的接口:dictCreate、dictAdd、dictReplace、dictDelete、dictFind、dictEmpty等,而这些接口调用了一些基础操作,包括:_dictRehashStep,_dictKeyIndex等

Hash Table在一定情况下会触发rehash操作,即:将第一个hash table中的数据逐步转移到第二个hash table中。
      (1)触发条件 当第一个表的元素数目大于桶数目且元素数目与桶数目比值大于5时,hash 表就会扩张,扩大后新表的大小为旧表的2倍。

/* Expand the hash table if needed */
static int _dictExpandIfNeeded(dict *d)
{
/* Incremental rehashing already in progress. Return. */
if (dictIsRehashing(d)) return DICT_OK; /* If the hash table is empty expand it to the initial size. */
if (d->ht[].size == ) return dictExpand(d, DICT_HT_INITIAL_SIZE); /* If we reached the 1:1 ratio, and we are allowed to resize the hash
* table (global setting) or we should avoid it but the ratio between
* elements/buckets is over the "safe" threshold, we resize doubling
* the number of buckets. */
if (d->ht[].used >= d->ht[].size &&
(dict_can_resize ||
d->ht[].used/d->ht[].size > dict_force_resize_ratio))
{
return dictExpand(d, d->ht[].used*);
}
return DICT_OK;
}

(2)转移策略 为了避免一次性转移带来的开销,Redis采用了平摊开销的策略,即:将转移代价平摊到每个基本操作中,如:dictAdd、dictReplace、dictFind中,每执行一次这些基本操作会触发一个桶中元素的迁移操作。在此,有读者可能会问,如果这样的话,如果旧hash table非常大,什么时候才能迁移完。为了提高前移速度,Redis有一个周期性任务serverCron,每隔一段时间会迁移100个桶。

相关操作:

         1.hset,hmset,hsetnx
            hset命令用来将某个hash指定键的值,如果键不存在,则创建并设置对应的值,返回一个整数1,如果键已经存在,则对应的值将被覆盖并返回整数0.
            hset hash_name field value

127.0.0.1:> hset userid: age
(integer)
127.0.0.1:> hset userid: age
(integer)

hmset命令和hset命令的作用相似,可以用来设置hash的键和值。不同的是hmset可以同时设置多个键值对。操作成功后hmset命令返回一个简单的字符串“OK”。
            hset hash_name field value

127.0.0.1:> hmset userid: name zhangsan age
OK

hsetnx命令也用来在指定键不存在的情况下设置键值信息。如果键不存在,则Redis会先创建键,然后设置对应的值,操作成功后返回整数1。如果该键已经存在,则该命令不进行任何操作,返回值为0
            hsetnx hash_name field value

127.0.0.1:> HSETNX userid: age
(integer)
127.0.0.1:> HSETNX userid: weight
(integer)

  2.hget,hmget,hgetall
            hget命令用来获取某个hash指定key的值。如果该键存在,直接返回对应的值,否则返回nil。
            hget hash_name field

127.0.0.1:> hget user: name
(nil)
127.0.0.1:> hget userid: name
"zhangsan"

hmget命令和hget命令类似,用来返回某个hash多个键的值的列表,对于不存在的键,返回nil值。
          hmget hash_name field1 field2...

127.0.0.1:> hmget userid: name age
) "zhangsan"
) ""

hgetall命令返回一个列表,该列表包含了某个hash的所有键和值。在返回值中,先是键,接下来的一个元素是对应的值,所以hgetall命令返回的列表长度是hash大小的两倍。
          hgetall hash_name

127.0.0.1:> HGETALL userid:
) "age"
) ""
) "name"
) "zhangsan"
) "weight"
) ""

        3.hexists
           hexists命令用来判断某个hash指定键是否存在,若存在返回整数1,否则返回0。
           hexists hash_name field

127.0.0.1:> HEXISTS userid: name
integer)
127.0.0.1:> HEXISTS userid: sex
(integer)

4.hlen
           hlen命令用来返回某个hash中所有键的数量。
           hlen hash_name

127.0.0.1:> hlen userid:
(integer)

5.hdel
            hdel命令用来删除某个hash指定的键。如果该键不存在,则不进行任何操作。hdel命令的返回值是成功删除的键的数量(不包括不存在的键)。
            hdel hash_name field

127.0.0.1:> hlen userid:
(integer)
127.0.0.1:> hdel userid: age
(integer)
127.0.0.1:> hlen userid:
(integer)

6.Hkeys,hvals
            hkeys命令返回某个hash的所有键,如果该hash不存在任何键则返回一个空列表。
            hkeys hash_name
            hvals命令返回某个hash的所有值的列表。
            hvals hash_name

127.0.0.1:> hkeys userid:
) "name"
) "weight"
127.0.0.1:> hvals userid:
) "zhangsan"
) ""

   7.hincrby,hincrbyfloat
            这两个命令都用来对指定键进行增量操作,不同的是hincrby命令每次加上一个整数值,而hincrbyfloat命令每次加上一个浮点值。操作成功后返回增量操作后的最终值
            hincrby hash_name field i
            hincrbyfloat hash_name field f

127.0.0.1:> HINCRBY userid: weight
(integer)
127.0.0.1:> HINCRBYFLOAT userid: weight 10.0
""

Redis数据类型之Hash(二)的更多相关文章

  1. redis数据类型之—Hash

    (1)hash 简单介绍 hash类型适合存储对象,字段值只能是字符串,不支持其他数据类型. (2)hash 常用命令 // 增加hash属性值 > hset user: name zm (in ...

  2. redis详解(二)-- 数据类型详解

    Redis常用数据类型详解 1,Redis最为常用的数据类型主要有以下: String Hash List Set Sorted set pub/sub Transactions 在具体描述这几种数据 ...

  3. 二:Redis数据类型

    一.nosql(非关系性数据库): mongoDB hbase redis nulch hive pig mahout zookeeper 二:redis 数据类型 1.存储string: 常用命令 ...

  4. 《闲扯Redis六》Redis五种数据类型之Hash型

    一.前言 Redis 提供了5种数据类型:String(字符串).Hash(哈希).List(列表).Set(集合).Zset(有序集合),理解每种数据类型的特点对于redis的开发和运维非常重要. ...

  5. Linux+Redis实战教程_day02_3、redis数据类型_4、String命令_5、hash命令_6、java操作redis数据库技术

    3. redis数据类型[重点] redis 使用的是键值对保存数据.(map) key:全部都是字符串 value:有五种数据类型 Key名:自定义,key名不要过长,否则影响使用效率 Key名不要 ...

  6. Redis笔记(二):Redis数据类型

    Redis 数据类型 Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合). String(字符串) st ...

  7. 缓存数据库-redis数据类型和操作(hash)

    一:Redis 哈希(Hash) Redis hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象. Redis 中每个 hash 可以存储 232 - 1 ...

  8. redis 数据类型 Hash

    Redis 数据类型Hash:hash数据类型存储的数据和mysql数据库中存储的一条记录很类似. hash的一些操作: 比如数据库是user表,有id,name,age ,sex,可以建立与之对应的 ...

  9. Redis数据类型之散列类型hash

    在redis中用的最多的就是hash和string类型. 问题 假设有User对象以JSON序列化的形式存储到redis中, User对象有id.username.password.age.name等 ...

随机推荐

  1. lua 变量

    lua 变量 类型 全局变量 lua 变量默认均为全局变量 打印一个未定义的变量输出为 nil 示例代码 a = 1 print(a, b) 局部变量 lua 变量默认均为全局变量, 除非变量前显式声 ...

  2. Instrument详解

    Instruments用户指南介绍Instruments是应用程序用来动态跟踪和分析Mac OS X和iOS代码的实用工具.这是一个灵活而强大的工具,它让你可以跟踪一个或多个进程,并检查收集的数据.这 ...

  3. 【小瑕疵】在div里插入img后在底部留有缝隙怎么解决

    [本文转载自http://blog.sina.com.cn/s/blog_9fd5b6df01013mld.html] 图片IMG与容器下边界之间有空隙怎么办?这里介绍3中简单的解决方法. 第一,给图 ...

  4. Andriod中自定义Dialog样式的Activity点击空白处隐藏软件盘(Dialog不消失)

    一.需求触发场景: 项目中需要出发带有EditText的Dialog显示,要求在编辑完EditText时,点击Dilog的空白处隐藏软键盘.但是Dialog不会消失.示例如下: 二.实现方法: 发布需 ...

  5. 工资不高也要给自己放假 这几款APP估计你用得上

    我是这样的一个人,我宁愿工资不高,只要给我足够的假期,那我就满足了.都说上班就是为了赚钱,但如果身体不好,赚再多的钱也是无福享受,所以建议各位,有机会的话,一定要抽出时间去旅游,去放松. 现在我们外出 ...

  6. Springmvc的工作流程

    1.向服务器发送http请求,请求被前端控制器DispatcherServlet捕获. 2.DispatcherServlet根据servlet.xml中的配置进行URL解析后,得到(URL),然后根 ...

  7. PL/SQL 自动补全[转]

    1.新建 shortcuts.txt 内容如下: s = SELECT t.* FROM t w = WHERE b = BETWEEN AND l = LIKE '%%' o = ORDER BY ...

  8. iOS开发 - CocoaPods安装和使用教程

    一.CocoaPods简介 1.什么是CocoaPods CocoaPods是iOS的包管理工具. 2.为什么要使用CocoaPods 在开发iOS项目时,经常会使用第三方开源库,手动引入流程复杂,并 ...

  9. [原创]普通的MySQL多表连接查询

  10. Git总结笔记3-把本地仓库推送到github

    说明:此笔记在centos 7 上完成 1.配置公钥 [root@kangvcar ~]# ssh-keygen -t rsa -C "kangvcar@126.com" [roo ...