基于Redis的六大数据类型-----在Spring中如何使用
在实际场合,我们更多的是在spring种使用Redis,这更加符合实际的学习和工作的需求。
Spring提供了RedisTemplate来操纵命令。而实际工作种并不是那么用的,因为每一个操作会尝试从连接池种取一个新的Redis连接,多个命令应该使用SeesionCallback接口进出操作。简单来说,单个命令用RestTemplate,多个命令就用SessionCallback。
我们以Redis支持存储的数据类型来入手。
①对于String字符串类型
是最基本的数据结构,它将以一个键和一个值存储在Redis内部。
这是关于Redis的命令
命令 | 说明 | 备注 |
set key value | 设置键值对 | |
get key | 通过键获取值 | |
del key | 通过key,删除键值对 | 返回删除数 |
strlen key | 求key指向字符串的长度 | 返回长度 |
getset key value | 修改原来key的对应值,并将旧值返回 | 如果原值为空,则返回空,并设置新值 |
getrange key start end | 获取子串(包头包尾) | start和end的取值是0到lenth-1,从0开始的索引 |
append key value | 将新的字符串value,加入到原来key指向的字符串末 | 返回key指向的新字符串的长度 |
以上都是Redis的命令,那用java代码如何实现呢?
上面我就说了,对于单个命令使用RedisTemplate,对于多个命令使用SeesionCallback,因为每一个命令都会取一个新的Redis连接对象
- 对于spring来说,得这样得到RedisTemplate对象
- ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml") ----------------applicationContext.xml 是关于redis配置的xml文件
- RedisTemplate redisTemplate = applicationContext .getBean(RedisTemplate.class); ----------------得到了redisTemplate 对象
- 对于springcloud,可以直接注入得到RedisTemplate对象
- @Resource
- private RedisTemplate redisTemplate; ----------------得到了redisTemplate 对象
命令 | 说明 | 备注 | java代码 |
set key value | 设置键值对 | redisTemplate.opsForValue().set("key1", "value1") | |
get key | 通过键获取值 | String value1 = (String) redisTemplate.opsForValue().get("key1") | |
del key | 通过key,删除键值对 | 返回删除数 | redisTemplate.delete("key1"); |
strlen key | 求key指向字符串的长度 | 返回长度 | Long length = redisTemplate.opsForValue().size("key1"); |
getset key value | 修改原来key的对应值,并将旧值返回 | 如果原值为空,则返回空,并设置新值 | (String)redisTemplate.opsForValue().getAndSet("key2", "value2"); |
getrange key start end | 获取子串(包头包尾) | start和end的取值是0到lenth-1,从0开始的索引 | String sonStr = redisTemplate.opsForValue().get("key2",0,2); |
append key value | 将新的字符串value,加入到原来key指向的字符串末 | 返回key指向的新字符串的长度 | int newLen = redisTemplate.opsForValue().append("key2", "_appen"); |
注意:redisTemplate.opsForValue()所返回的对象可以操作简单的键值对,可以是字符串,也可以是对象。
最开始六大数据类型的表格种,我们可以知道String数据类型,不仅仅存储字符串类型,还可以存储整数和浮点数。
如果字符串是数字(整数或者浮点数)时,Redis也支持简单的运算,但是目前只支持加减法的运算,但是不支持浮点数的减法。
但是spring不支持redisTemplate的减法,只支持加法;但是减法(整数的),可以利用连接工厂获得连接从而得到底层的Redis连接对象,在用keySerializer属性对键进行序列化等操作。
命令 | 说明 | 备注 | java代码 |
incr key | 在原字段上加1 | 只对整数 |
redisTemplate.opsForValue().set("i", "9") redisTemplate.opsForValue().increment("i", 1); |
incrby key increment | 在原字段上加上整数(increment) | 只对整数 | redisTemplate.opsForValue().increment("i", increment); |
decr key | 在原字段上减1 | 只对整数 |
redisTemplate.getConnectionFactory().getConection().decrBy( redisTemplate.getKeySerializer().serialize("i"), 1); |
decrby key decrement | 在原字段上减去整数(decrement) | 只对整数 |
redisTemplate.getConnectionFactory().getConection().decrBy( redisTemplate.getKeySerializer().serialize("i"), decrement); |
incrbyfloat keyincrement | 在原字段上加上浮点数(keyincrement) | 整数,浮点数 | redisTemplate.opsForValue().increment("i", keyincrement); |
注意:即使这儿表示的还是整数和浮点数,实际存储在Redis种还是字符串,所以一定要使用字符串序列化器,而不能使用JDK序列化器,否则出异常
②Hash哈希结构
redis中的哈希接口就如同java中的map一样,一个对象可以有很多键值对,它时特别适合存储对象的。
在Redis中hash其实时一个String类型的field和value的映射表,因此,我们存储的数据实际上在Redis内存中时一个个字符串而已。
可能在这儿有人就会问了,什么时哈希(hash)结构呢? 请看下表:
这是一个hash结构的数据
role_key 这是key,下面的roleid,name,note都是kv接口的值 |
|
field 字段名 |
value 值 |
roleId |
001 |
roleName | name11 |
roleNote |
note22 |
role_key代表的就是这和hash结构在Redis内存的key,可以通过它找到这个hash结构,而hash结构是由一系列的field和value组成的。
上面简单结构了以下hash结构,那在redis'中hash数据结构和String数据结构有何区别呢?
在redis的内存中 | redis的命令 | |
String | 只是以k-V的形式存在 | 直接使用 |
Hash |
是以K-V(field-value)的形式存在; 先通过key索引找到对应的hash结构,在通过field来确定是使用hash结构的哪个键值对 |
命令都是以小h开头; 且大多数的命令都多了一个层级那就是field |
说了这么多,那到底在Redis中hash结构的命令时如何的呢?
命令 | 说明 | 备注 | java代码 |
hset key field value | 在hash结构中设置键值对 | 单个设值 | redisTemplate.opsForHash().put(key, "f3","6") |
hmset key field value [valeu2...] | hash结构设置多个键值 |
map.put("f1","value1"); map.put("f2","value2"); redisTemplate.opsForHash().putAll(key,map) |
|
hgetall key | 获取所有hash结构中的键值 |
返回健和值; 对于javaAPI,数据是保存在MAP中的,如果hash数据过大,得考虑JVM性能问题 |
Map m = redisTemplate.opsForHash().entries(key) |
hdel key field [field2....] | 删除hash结构中某个(多个)键值对 | redisTemplate.opsForHash().delete(key,"f1", "f2") | |
hlen key | 返回hash结构中键值对的数量 | 返回数量 | |
hexists key field | 判断hash结构中是否存在field字段 | 存在返回1,否则0 | boolean ex = redisTemplate.opsForHash().haskey(key,"f3") |
hkeys key | 返回hash中所有的键 | 对于javaAPI,Set保存 | Set ketList= redisTemplate.opsForHash(),keys(key) |
hvals key | 获取hash结构中所有的值 | 对于javaAPI,List保存 | List list = redisTemplate.opsForHash().values(key) |
hmget key field [field2....] |
返回hash中指定的键和值,可以是多个 | 依次返回 |
keyList.add("f1"); keyList.add("f2"); Lisy list = redisTemplate.opsForHash().multiGet(key, keyList); |
hsetnx key field value |
在hash结构中不存在对应的键,才设置值 | boolean success = redisTemplate.opsForHash().putIfAbsent(key, "f4", "value4") |
----------------------
- 黄色字体标出的是与String类型的区别
- m表示多个操作
③链表List结构
在学习这个结构之前,我们得知道这么几点信息
- 它是可以存储多个字符串,而且是有序的
- Redis的链表是双向的,因此既可以从左到右,也可以从右到左遍历它的存储节点---------------基于这个提醒,我们可以有很多玩法的。
- 链表结构表示查询慢,增删快
- 由于双链表结构,所以Redis的链表命令分为左操作(从左往右) 和 右操作(从右到左)
- 删除链表redisTemplate.delete("list") ---------list指的是key名
- 打印列表数据printList(redisTemplate, "list"); ---------list指的是key名
命令 | 说明 | 备注 | java代码 |
lpush key nodel [node2....] | 把节点node1加入到链表的最左边,返回加入的个数 |
如果是node1,node2,node3这样假如, 那链表开头从左到右的顺序是node3,nodel2,node1 |
redisTemplate.opsForList().leftPush("list", "node3"); nodeList.add("node4"); nodeList.add("node5"); redisTemplate.opsForList().leftPushAll("list", nodeList) |
rpush key nodel [node2....] | 把节点node1加入到链表的最右边,返回加入的个数 |
如果是node1,node2,node3这样假如, 那链表结尾从左到右的顺序是node1,nodel2,node3 |
同理rightPush和rightPushAll |
lindex key index | 读取下标为index的节点 | 返回节点字符串,从0开始 | String node1 = (String)redisTemplate.opsForList().index("index", 0) |
llen key | 求链表的长度 | 返回链表的节点数 | long size = redisTemplate.opsForList().size("list") |
lpop key | 删除左边第一个节点,并将其返回 | String leftNode = (String)redisTemplate.opsForList().leftPop("list") | |
rpop key | 删除右边第一个节点,并将其返回 | String rightNode = (String)redisTemplate.opsForList().rightPop("list") | |
linsert key before|after pivot node |
插入一个节点node,并且可以指定在值为pivot的 节点的前面(before)或者后面(after) |
pivot节点不存在,插入失败返回-1 | |
lpushx list node |
如果存在key为list的节点,则插入节点node, 作为从左到右的第 一个节点 |
redisTemplate.opsForList().leftPushIfPresent("list", "head") | |
rpushx list node |
如果存在key为list的节点,则插入节点node, 作为从左到右的最后一个节点 |
redisTemplate.opsForList().rightPushIfPresent("list", "head") | |
lrange list start end | 获取链表list从start到end的节点值 | 包头包尾 | List l = redisTemplate.opsForList().range("list", 0, 10); |
lrem list count value |
如果count为0,则删除所有值等于value的节点; 如果count不为0,则先对count取绝对值,假设记为abs, 然后从左到右删除不大于ads个等于value的节点 |
从左到右删除至多三个值为node节点 redisTemplate.opsForList().remove("list", 3, "node") |
|
lset key index node | 设置列表下标为index的节点的值为node | redisTemplate.opsForList().set("list", index, "node_new") | |
ltrim key start stop | 修建链表,只保留start到stop的区间的系欸但,其余都删除 | 包头包尾 |
----- 其中来表示left左操作,r表示right右操作
注意:上面的链表操作都是进程不安全的,因为当我们操作这些命令的时候,Redis'的其它用户也可以操作同一链表,从而导致并发数据安全和一致性的问题了。
那上述的问题如何解决呢? 不用担心,Redis已经为我们解决好了,redis提供了链表的阻塞命令 ------------------这儿可以拓展到线程的阻塞那边,有兴趣的可以通过链接查看
阻塞命令 ,他在运行的时候,回个链表加上锁,以保证链表的唯一操作 --------------------------对锁感兴趣的可以看这个链接
命令 | 说明 | 备注 | java代码 |
blpop key timeout | 移出并获取列表的第一个元素,如果列表没有元素会阻塞列表知道等待超时或者发现可弹出元素为止 | 相对lpop命令,它的操作是线程安全的 |
使用参数超时时间作为阻塞命令区分; 等价于blpop命令,并且可以设置时间参数。 redisTemplate.opsForList().leftPop("list", 1, TimeUnit.SECONDS) |
brpop key timeout | 移出并获取列表的最后一个元素,如果列表没有元素会阻塞列表知道等待超时或者发现可弹出元素为止 | 相对rpop命令,它的操作是线程安全的 | redisTemplate.opsForList().rightPop("list", 1, TimeUnit.SECONDS) |
rpoplpush key src dest | 按从左到右的顺序,将第一个链表的最后一个元素移除,并插入到目标链表的最左边 | 不可以设置超时时间 |
相当于rpoplpush命令,弹出list1最右边的节点,插入到list2左左边 redisTemplate.opsForList().rightPopAndLeftPush("list1", "list2") |
brpoplpush key src dest timeout | 按从左到右的顺序,将第一个链表的最后一个元素移除,并插入到目标链表的最左边,并且可以设置超时时间 | 可以设置超时时间 | redisTemplate.opsForList().rightPopAndLeftPush("list1", "list2",1,TimeUnit.SECONDS ) |
当使用上诉4条命令的时候,Redis就会对对应的链表加锁,来保证其它进程不能在读取或者写入该链表,只能等待命令结束,从而保证数据的一致性。
但实际上,我们不想要阻塞,阻塞就是cpu的挂起和恢复等操作,在工作中用的少(保证了一致性但是性能却不佳),更多的是高并发锁的使用较多-----------------------看链接
④数据结构----set集合
- Redis的集合不是一个线性结构,而是一个哈希表结构。
- 对于集合而言,它的每一个元素都是不能重复的,当插入相同的记录时都会失败
- 集合时无序的
- 集合的每一个元素都是String数据结构类型
- 所有命令都包含一个前缀s,来表示这是集合命令;
- 集合是无序,并且支持交集,并集差集的运算的
- 求集合长度:redisTemplate.opsForSet().size("set1")
命令 | 说明 | 备注 | java代码 |
sadd key member1 [member2...] | 给键为key的集合添加成员 | 可以同时增加多个,返回添加的个数 | redisTemplate.boundSetOps("set1").add("v1","v2","v3"); |
scard key | 统计键为key的集合成员数 | ||
sdiff key1 [key2] | 找出两个集合的差集,key2中不包含key1的那些元素 |
参数如果时单key,那么redis就 返回这个key的所有元素 |
Set set = redisTemplate.opsForSet().difference("set1", "set2") |
sdiffstore key1 [key2] | 先按sdiff命令的规则,找出key1和key2的差集,然后将其保存到des集合中 | redisTemplate.opsForSet().differenceAndStroe("set1", "set2", "des") | |
sinter des key1 key2 | 求key1和key2两集合的交集,显式共有的元素 |
参数如果时单key,那么redis就 返回这个key的所有元素 |
Set set = redisTemplate.opsForSet().intersect("set1", "set2") |
sinterstore des key1 key2 | 先按sdiff命令的规则,找出key1和key2的交集,然后将其保存到des集合中 | redisTemplate.opsForSet().intersectAndStroe("set1", "set2", "des") | |
sismember key member | 判断member是否键为key的集合的成员 | 如果是返回1,不是0 | boolean bl = redisTemplate.opsForSet().isMember("set1", "v1") |
smembers key | 返回集合所有成员,(与sdiff key和sdiffstore key的单个可以的作用一样) | 数据量大,需要考虑迭代遍历的问题 | Set set = redisTemplate.opsForSet().members("set1") |
smove src des member | 将成员member从集合src迁移到集合des中 | ||
spop key | 随机弹出集合的一个元素 | 注意随机性,因为集合是无序的 |
String s = (String)redisTemplate.opsForSet().pop("set1")
|
srandmember key [count] |
随机返回集合中一个或者多个元素,count为限制返回总数,如果count为负数, 则先求其绝对值 |
count为整数,如果不填默认为1, 如果count大于等于集合总数,则返回整个集合 |
String s = (String) redisTemplate.opsForSet().randomMember("set1"); List list = redisTemplate.opsForSet().randomMember("set1", 2L); |
srem key member1 [member2...] | 移除集合中的元素,可以是多个元素 |
对于很大的集合可以通过它删除部分元素, 避免删除大量数据引发Redis卡顿 |
redisTemplate.opsForSet().remove("set1","v1") |
sunion key1 [key2] | 求两个集合的并集 | 参数是单key,那么Redis就返回这个key的所有元素 | redisTemplate.opsForSet().union("set1", "set2") |
sunionstore des key1 key2 | 先执行sunion命令求出并集,然后保存到键为des的集合中 | redisTemplate.opsForSet().unionAndStore("set1", "set2", "des") |
⑤数据结构----zset有序集合
- 和无序集合的主要区别在于每一个元素除了值外,它还会多一个分数,这个分数是一个浮点数Double,根据分数Redis可以从小到大 或者 从大到小 排序。
- 分数可以一样,但是每一个元素是唯一的,意识就是去重的。
- 元素也是String类型,也是基于hahs的存储结构
- 有序集合是根据hash表来实现的
总结:有序集合是通过key来标记它是属于哪个集合(和无序集合set一样),但是是通过分数来排序的(无序集合set没有)
但是实际上,在一定条件下还可以对值来排序;
由于命令太多了,而且使用频率也不高,就偷个懒。。。
⑥数据结构----基数 HyperLogLog
基数是一种算法-----简单来说,就是不重复的元素个数
基数的作用是,评估大约需要准备多是个存储单元来存储数据。
这个在工作中用的不多,简单说一下就好了
命令 | 说明 | 备注 | java代码 |
pfadd key element | 添加指定元素到HyperLogLog中 | 如果已经存储元素,则返回0,添加失败 | redisTemplate.opsForHyperLogLog().add("HyperLogLog2", "a","b","c") |
pfcount key | 返回HyperLogLog的基数值 | Long size = redisTemplate.opsForHyperLogLog().size("HyperLogLog2") | |
pfmerge desKey key1 [key2...] | 合并多个HyperLogLog,并保留在desKey中 | redisTemplate.opsForHyperLogLog().union("desKey", "HyperLogLog1" , "HyperLogLog2") |
在提供一些Redis的命令:
keys * 查看当前redis数据库的所有的键
set age 1 往当前数据库中添加键age值1
exists name 返回1表示存在这个键,返回0表示不存在这个键
move age 1 移除这个键,1表示的是当前的数据库
get name 通过键获得值
expire name 10 设置10S后这个件键name过期
ttl name 查看当前key的剩余时间
type name 查看name键的类型