在任何一种编程语言里,字符串String
都是最基础的数据结构, 那你有想过Redis
中存储一个字符串都进行了哪些操做嘛?java
在Redis
中String
是能够修改的,称为动态字符串
(Simple Dynamic String
简称SDS
)(快拿小本本记名词,要考的),说是字符串但它的内部结构更像是一个ArrayList
,内部维护着一个字节数组,而且在其内部预分配了必定的空间,以减小内存的频繁分配。golang
Redis
的内存分配机制是这样:redis
这样既保证了内存空间够用,还不至于形成内存的浪费,字符串最大长度为512MB
.。 编程
上图就是字符串的基本结构,其中content
里面保存的是字符串内容,0x\0
做为结束字符不会被计算len
中。数组
分析一下字符串的数据结构bash
struct SDS{
T capacity; //数组容量
T len; //实际长度
byte flages; //标志位,低三位表示类型
byte[] content; //数组内容
}
复制代码
capacity
和len
两个属性都是泛型,为何不直接用int类型
?由于Redis
内部有不少优化方案,为更合理的使用内存,不一样长度的字符串采用不一样的数据类型表示,且在建立字符串的时候len
会和capacity
同样大,不产生冗余的空间,因此String
值能够是字符串、数字(整数、浮点数) 或者 二进制。网络
一、应用场景:数据结构
存储key-value键值对,这个比较简单不细说了编程语言
二、字符串(String)经常使用的命令:优化
set [key] [value] 给指定key设置值(set 可覆盖老的值)
get [key] 获取指定key 的值
del [key] 删除指定key
exists [key] 判断是否存在指定key
mset [key1] [value1] [key2] [value2] ...... 批量存键值对
mget [key1] [key2] ...... 批量取key
expire [key] [time] 给指定key 设置过时时间 单位秒
setex [key] [time] [value] 等价于 set + expire 命令组合
setnx [key] [value] 若是key不存在则set 建立,不然返回0
incr [key] 若是value为整数 可用 incr命令每次自增1
incrby [key] [number] 使用incrby命令对整数值 进行增长 number
复制代码
Redis
中的list
和Java
中的LinkedList
很像,底层都是一种链表结构,list
的插入和删除操做很是快,时间复杂度为 0(1),不像数组结构插入、删除操做须要移动数据。
像归像,可是redis
中的list
底层可不是一个双向链表那么简单。
当数据量较少的时候它的底层存储结构为一块连续内存,称之为ziplist(压缩列表)
,它将全部的元素紧挨着一块儿存储,分配的是一块连续的内存;当数据量较多的时候将会变成quicklist(快速链表)
结构。
可单纯的链表也是有缺陷的,链表的先后指针prev
和next
会占用较多的内存,会比较浪费空间,并且会加剧内存的碎片化。在redis 3.2以后就都改用ziplist+链表
的混合结构,称之为quicklist(快速链表)
。
下面具体介绍下两种链表
先看一下ziplist
的数据结构,
struct ziplist<T>{
int32 zlbytes; //压缩列表占用字节数
int32 zltail_offset; //最后一个元素距离起始位置的偏移量,用于快速定位到最后一个节点
int16 zllength; //元素个数
T[] entries; //元素内容
int8 zlend; //结束位 0xFF
}
复制代码
int32 zlbytes
: 压缩列表占用字节数 int32 zltail_offset
: 最后一个元素距离起始位置的偏移量,用于快速定位到最后一个节点
`int16 zllength`:元素个数
`T[] entries`:元素内容
`int8 zlend`:结束位 0xFF
复制代码
压缩列表为了支持双向遍历,因此才会有ztail_offset
这个字段,用来快速定位到最后一 个元素,而后倒着遍历
entry
的数据结构:
struct entry{
int<var> prevlen; //前一个 entry 的长度
int<var> encoding; //元素类型编码
optional byte[] content; //元素内容
}
复制代码
entry
它的prevlen
字段表示前一个entry
的字节长度,当压缩列表倒着遍历时,须要经过这 个字段来快速定位到下一个元素的位置。
一、应用场景:
因为list它是一个按照插入顺序排序的列表,因此应用场景相对还较多的,例如:
lpop
和rpush
(或者反过来,lpush
和rpop
)能实现队列的功能lpush
命令和lrange
命令能实现最新列表的功能,每次经过lpush
命令往列表里插入新的元素,而后经过lrange
命令读取最新的元素列表。二、list操做的经常使用命名:
rpush [key] [value1] [value2] ...... 链表右侧插入
rpop [key] 移除右侧列表头元素,并返回该元素
lpop [key] 移除左侧列表头元素,并返回该元素
llen [key] 返回该列表的元素个数
lrem [key] [count] [value] 删除列表中与value相等的元素,count是删除的个数。 count>0 表示从左侧开始查找,删除count个元素,count<0 表示从右侧开始查找,删除count个相同元素,count=0 表示删除所有相同的元素
(PS: index 表明元素下标,index 能够为负数, index= 表示倒数第一个元素,同理 index=-2 表示倒数第二 个元素。)
lindex [key] [index] 获取list指定下标的元素 (须要遍历,时间复杂度为O(n))
lrange [key] [start_index] [end_index] 获取list 区间内的全部元素 (时间复杂度为 O(n))
ltrim [key] [start_index] [end_index] 保留区间内的元素,其余元素删除(时间复杂度为 O(n))
复制代码
Redis
中的Hash
和 Java的HashMap
更加类似,都是数组+链表
的结构,当发生 hash 碰撞时将会把元素追加到链表上,值得注意的是在Redis
的Hash
中value
只能是字符串.
hset books java "Effective java" (integer) 1
hset books golang "concurrency in go" (integer) 1
hget books java "Effective java"
hset user age 17 (integer) 1
hincrby user age 1 #单个 key 能够进行计数 和 incr 命令基本一致 (integer) 18
复制代码
Hash
和String
均可以用来存储用户信息 ,但不一样的是Hash
能够对用户信息的每一个字段单独存储;String
存的是用户所有信息通过序列化后的字符串,若是想要修改某个用户字段必须将用户信息字符串所有查询出来,解析成相应的用户信息对象,修改完后在序列化成字符串存入。而 hash能够只对某个字段修改,从而节约网络流量,不过hash内存占用要大于String
,这是hash
的缺点。
一、应用场景:
hset [key] [field] [value]
命令, 能够实现以用户Id
,商品Id
为field
,商品数量为value
,刚好构成了购物车的3个要素。hash
类型的(key, field, value)
的结构与对象的(对象id, 属性, 值)
的结构类似,也能够用来存储对象。二、hash经常使用的操做命令:
hset [key] [field] [value] 新建字段信息
hget [key] [field] 获取字段信息
hdel [key] [field] 删除字段
hlen [key] 保存的字段个数
hgetall [key] 获取指定key 字典里的全部字段和值 (字段信息过多,会致使慢查询 慎用:亲身经历 曾经用过这个这个指令致使线上服务故障)
hmset [key] [field1] [value1] [field2] [value2] ...... 批量建立
hincr [key] [field] 对字段值自增
hincrby [key] [field] [number] 对字段值增长number
复制代码
Redis
中的set
和Java
中的HashSet
有些相似,它内部的键值对是无序的、惟一 的。它的内部实现至关于一个特殊的字典,字典中全部的value都是一个值 NULL。当集合中最后一个元素被移除以后,数据结构被自动删除,内存被回收。
一、应用场景:
sinter
命令能够得到A和B两个用户的共同好友; 2)sismember
命令能够判断A是不是B的好友; 3)scard
命令能够获取好友数量; 4) 关注时,smove
命令能够将B从A的粉丝集合转移到A的好友集合srandmember
命令则能够从中随机获取几个。二、set的经常使用命令:
sadd [key] [value] 向指定key的set中添加元素
smembers [key] 获取指定key 集合中的全部元素
sismember [key] [value] 判断集合中是否存在某个value
scard [key] 获取集合的长度
spop [key] 弹出一个元素
srem [key] [value] 删除指定元素
复制代码
zset
也叫SortedSet
一方面它是个set
,保证了内部 value 的惟一性,另方面它能够给每一个 value 赋予一个score
,表明这个value的排序权重。它的内部实现用的是一种叫做“跳跃列表
”的数据结构。
一、应用场景:
zset
能够用作排行榜,可是和list
不一样的是zset
它可以实现动态的排序,例如: 能够用来存储粉丝列表,value 值是粉丝的用户 ID,score 是关注时间,咱们能够对粉丝列表按关注时间进行排序。
zset
还能够用来存储学生的成绩,value
值是学生的 ID,score
是他的考试成绩。 咱们对成绩按分数进行排序就能够获得他的名次。
二、zset有序集合的经常使用操做命令:
zadd [key] [score] [value] 向指定key的集合中增长元素
zrange [key] [start_index] [end_index] 获取下标范围内的元素列表,按score 排序输出
zrevrange [key] [start_index] [end_index] 获取范围内的元素列表 ,按score排序 逆序输出
zcard [key] 获取集合列表的元素个数
zrank [key] [value] 获取元素再集合中的排名
zrangebyscore [key] [score1] [score2] 输出score范围内的元素列表
zrem [key] [value] 删除元素
zscore [key] [value] 获取元素的score
复制代码