2.3、哈希

哈希

  • 几乎所有的编程语言都提供了哈希(hash)类型,它们的叫法可能是哈希、字
    典、关联数组。在Redis中,哈希类型是指键值本身又是一个键值对结构,形如
    value={{field,value},...{fieldN,valueN}}。
  1. 命令

    (1)设置值

    hset key field value

    下面为user:1添加一堆field-value:

    127.0.0.1:6379> hset user:1 name tom
    (integer) 1
    

    如果设置成功会返回1,反之会返回0。此外Redis提供了hsetnx命令,它们的
    关系就像set和setnx命令一样,只不过作用域由键变为field。

    (2)获取值

    hget key field

    例如,下面操作获取user:1的那么域(属性)对应的值:

    127.0.0.1:6379> hget user:1 name
    "tom"
    

    如果键或field不存在,会返回null

    127.0.0.1:6379> hget user:2 name
    (nil)
    127.0.0.1:6379> hget user:1 age
    (nil)
    

    (3)删除field

    hdel key field [field...]

    hdel会删除一个或多个field,返回结果为成功删除field的个数,例如:

    127.0.0.1:6379> hdel user:1 name 
    (integer) 1
    127.0.0.1:6379> hdel user:1 age
    (integer) 0
    

    (4)计算field个数

    hlen key

    例如user:1有3个field

    127.0.0.1:6379> hset user:1 name tom
    (integer) 1
    127.0.0.1:6379> hset user:1 age 23
    (integer) 1
    127.0.0.1:6379> hset user:1 city tianjin
    (integer) 1
    127.0.0.1:6379> hlen user:1
    (integer) 3
    

    (5)批量设置或获取field-value

    hmget key field [field ...]
    hmset key field value [field value ...]
    

    hmset和hmget分别是批量设置和获取field-value,hmset需要的参数是key和
    多对field-value,hmget需要的参数是key和多个field。例如:

    127.0.0.1:6379> hmset user:1 name mike age 12 city tianjin
    OK
    127.0.0.1:6379> hmget user:1 name city
    1) "mike"
    2) "tianjin"
    

    (6)判断field是否存在

    hexists key filed

    例如,user:1包含name域,所以返回结果为1,不包含时返回0:

    127.0.0.1:6379> hexists user:1 name
    (integer) 1
    

    (7)获取所有filed

    hkeys key

    hkeys命令应该叫hfields更为恰当,他返回制定哈希键所有的field,例如:

    127.0.0.1:6379> hkeys user:1
    1) "name"
    2) "age"
    3) "city"
    

    (8)获取所有value

    hvals key

    下面操作获取user:1全部value:

    127.0.0.1:6379> hvals user:1
    1) "mike"
    2) "12"
    3) "tianjin"
    

    (9)获取所有的field-value

    hgetall key

    下面操作获取user:1所有的field-value:

    127.0.0.1:6379> hgetall user:1
    1) "name"
    2) "mike"
    3) "age"
    4) "12"
    5) "city"
    6) "tianjin"
    

    (10)hinerby hincybyfloat

    hincrby key field
    hincrbyfloat key field

    hincrby和hincrbyfloat,就像incrbyhy和incrbyfloat命令一样,但是它们
    的作用域是field。

    (11)计算value的字符串长度

    hstrlen key field

    例如hget user:1 name的value是tom,那么hstrlen的返回结果是3:

    127.0.0.1:6379> hstrlen user:1 name
    (integer) 3
    

    下表是哈希类型命令的时间复杂度

    命令 时间复杂度
    hset key field value O(1)
    hget key field O(1)
    hdel key field [field ...] O(k),k是field的个数
    hlen key O(1)
    hgetall key O(n),n是field的总数
    hmget field [field ...] O(k),k是field的个数
    hmset field value [field value ...] O(k),k是field的个数
    hexists key field O(1)
    hkeys key O(n),n是field的总数
    hvals key O(n),n是field的总数
    hstenx key field value O(1)
    hincrby key field increment O(1)
    hincrbyfloat key filed increment O(1)
    hstrlen key field O(1)
  1. 内部编码

    哈希类型的编码有两种:

    • ziplist(压缩列表):当哈希类型元素个数小于
      hash-max-ziplist-entries配置(默认512个)、同时所有值都小于
      hash-max-ziplist-value配置(默认64字节)是,Redis会使用ziplist作为
      哈希的内部实现,ziplist使用更加紧凑的结构实现多个元素的连续存储,所以
      在节省内存方面比hashtable更加优秀。

    • hashtable(哈希表):当哈希类型无法满足ziplist的条件时,Redis会使
      用hashtable作为哈希的内部实现,因为此时ziplist的读写效率会下降,而
      hashtable的读写时间复杂度是O(1)。

    下面的实例演示了哈希类型的内部编码,以及相应的变化。

    1)当field个数比较少且没有大的value时,内部编码为ziplist:

    127.0.0.1:6379> hmset hashkey f1 v1 f2 v2
    OK
    127.0.0.1:6379> objet encoding hashkey
    "ziplist"
    

    2.1)当有value大于64字节,内部编码会由ziplist变为hashtable:

    127.0.0.1:6379> hset hashkey f3 "one string is bigger than 64 byte ...忽略..."
    OK
    127.0.0.1:6379> object encoding hashkey
    "hashtable"
    

    2.2)当field个数超过512,内部编码也会有ziplist变为hashtable:

    127.0.0.1:6379> hmset hashkey f1 v1 f2 v2 f3 v3 ...忽略... f513 v513
    OK
    127.0.0.1:6379> object encoding hashkey
    "hashtable"
    
  2. 使用场景

    下表为关系型数据表记录的两条用户信息,用户的属性作为表的列,每条用户信
    息作为行

    id name age city
    1 tom 23 beijing
    2 mike 30 tianjin

    如果将其用哈希类型存储,如下表

    user:1

    field value
    id 1
    name tom
    age 23
    city beijing

    user:2

    field value
    id 2
    name mike
    age 30
    city tianjing

    相比于使用字符串序列化缓存用户信息,哈希类型变得更加直观,并且在更新操
    作上会更加便捷。可以将每个用户的id定义为键后缀,多对field-value对应每
    个用户的属性,类似如下伪代码:

    UserInfo getUserInfo(long id){
        //用户id作为key后缀
        userRedisKey = "user:info" + id;
        //使用hgetall获取所有用户信息映射关系
        userInfoMap = redis.hgetAll(userRedisKey);
        UserInfo userInfo;
        if(userInfoMap != null){
            //将映射关系转换为UserInfo
            userInfo = transferMapToUserInfo(userInfoMap);
        }else{
            //从MySQL中获取用户信息
            userInfo = mysql.get(id);
            //将userInfo变为映射关系使用hmset保存到Redis中
            redis.hmset(userRedisKey, transferUserInfoToMap(userInfo));
            //添加过期时间
            redis.expire(userRedisKey, 3600);
        }
        return usreInfo;
    }
    

    但是需要注意的是哈希类型和关系型数据库有两点不同之处:

    • 哈希类型是稀疏的,而关系型数据库是完全结构化的,例如哈希类型每个键可
      以有不同的field,而关系型数据库一旦添加新的列,所有行都要为其设置值
      (即使为NULL),如下表:
    id name favor city age gender
    1 tom sprot beijing NULL NULL
    2 mike NULL NULL 30 male

    user:1

    field value
    id 1
    name tom
    favor sport
    city beijing

    user:2

    field value
    id 2
    name mike
    age 30
    gender male

    开发人员需要将两者的特点搞清楚,才能在适合的场景使用适合的技术。到目前
    为止,我们已经能够用三种方法缓存用户信息,下面给出三种方案的实现方法和
    优缺点分析。

    1)原声字符串类型:每个属性一个键。

    set user:1:name tom
    set user:1:age 23
    set user:1:city beijing 
    

    优点:简单直观,每个属性都支持更新操作。
    缺点:占用过多的键,内存占用量较大,同时用户信息内聚性比较差,所以此种
    方案一般不会在生产环境使用。

    2)序列化字符串类型:将用户信息序列化后用一个键保存。

    set user:1 serialize(usreInfo)

    优点:简化编程,如果合理的使用序列化可以提高内存的使用效率。
    缺点:序列化和反序列化有一定的开销,同时每次更新属性都需要把全部数据取
    出进行反序列化,更新后在序列化到Redis中。

    3)哈希类型:每个用户属性使用一堆field-value,但是只用一个键保存。

    hmset user:1 name tom age 23 city beijing

    优点:简单直观,如果使用合理可以减少内存空间的使用。
    缺点:要控制哈希在ziplist和hashtable两种内部编码的转换,hashtable会消耗更多内存。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 223,726评论 6 521
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 95,697评论 3 402
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 170,734评论 0 366
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 60,508评论 1 300
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 69,522评论 6 399
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,051评论 1 314
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,429评论 3 427
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,403评论 0 278
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,930评论 1 323
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,977评论 3 343
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,122评论 1 354
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,763评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,454评论 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,931评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,047评论 1 275
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 49,613评论 3 380
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,150评论 2 363

推荐阅读更多精彩内容