链表结构是Redis中的一个常用的结构,他可以存储多个字符串,而且它是有序的,能够存储40多亿个节点。Redis链表是双向的,因此既可以从左到右,也可以从右到左遍历它存储的节点,链表结构如下:
由于它是双向链表,所以它的读性能就会相对丧失,而插入和删除就会显得很便利。它的增删操作与双向链表同。
因为是双向链表结构,所以Redis链表命令分为左操作和右操作两种命令,左操作意味着是从左到右,右操作意味着是从右到左。Redis关于链表的命令如下:
- lpush key node1 [node2…]:把节点node1加入到链表的最左端,如果是node1,node2…noden这样的加入,那么链表的开头从左到右的顺序是noden…,node2,node1。
- rpush key node1 [node2…]:把节点node1加入到链表的最右端,如果是node1,node2…noden这样的加入,那么链表的开头从左到右的顺序是noden1,node2…noden。
- lindex key index:从左往右读取下标为index的节点,返回节点字符串,从0开始算。
- llen key:求链表的长度。返回链表节点数。
- lpop key:删除左边第一个节点,并将其返回。
- rpop key:删除右边第一个节点,并将其返回。
- linsert key before|after pivot node:插入一个节点node,并且可以指定在值为pivot node的结点的前面或者后面。如果list不存在,则报错;如果没有值为对应pivot node,也会插入失败。
- lpushx list node:如果存在key为list的链表,则插入节点node,并且作为从左到右的第一个节点,如果list不存在,则失败。
- rpushx list node:如果存在key为list的链表,则插入节点node,并且作为从右到左的第一个节点,如果list不存在,则失败。
- lrange list start end:获取链表从start下标到end下标的节点数,包含start和end下标的值。
- lrem list count value:如果count为0,则删除所有值为value的节点;如果count不为0,则先对count取绝对值,假设极为abs,然后从左到右删除不大于abs个等于value的节点。
- lset key index node:设置下标为index的节点的值为node。
- ltrim key start stop:修建链表,只保留从start到stop的区间的节点,其余的都删除掉,包含start和end的下标的节点会保留。
但是上述的所有方法都是进程不安全的,因为当我们操作这些命令的时候,其他的Redis的客户端也可能操作同一个链表,这样就会造成并发数据安全和一致性的问题。为了解决这些问题,Redis提供了链表的阻塞命令,它们在运行的时候,会给链表加锁,以保证操作链表的命令安全性。链表的阻塞命令如下:
- blpop key timeout:移出并获取列表的第一个元素,如果链表没有元素会阻塞链表直到等待超时或发现可弹出元素为止。相对于lpop命令,它是安全的。
- brpop key timeout:移出并获取列表的最后一个元素,如果链表没有元素会阻塞链表直到等待超时或发现可弹出元素为止。相对于rpop命令,它是安全的。
- rpoplpush key src dest:按从左到右的顺序,将一个链表的最后一个元素移除,并插入到目标链表的最左边。不能设置超时时间。
- brpoplpush key src dest timeout:按从左到右的顺序,将一个链表的最后一个元素移除,并插入到目标链表的最左边。并可以设置超时时间。
我们在用一个简单的demo来实现这些功能,代码如下:
redisSpring-cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxIdle" value="50" />
<property name="maxTotal" value="100" />
<property name="maxWaitMillis" value="20000" />
</bean>
<bean id="stringRedisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer" />
<bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="localhost" />
<property name="port" value="6379" />
<property name="password" value="123456" />
<property name="poolConfig" ref="poolConfig" />
</bean>
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="connectionFactory" />
<property name="defaultSerializer" ref="stringRedisSerializer" />
<property name="keySerializer" ref="stringRedisSerializer" />
<property name="valueSerializer" ref="stringRedisSerializer" />
</bean>
</beans>
testList.java
package com.ssm.redis1.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.data.redis.connection.RedisListCommands;
import org.springframework.data.redis.core.RedisTemplate;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import static com.sun.xml.internal.messaging.saaj.packaging.mime.util.ASCIIUtility.getBytes;
public class testList {
public static void main(String[] args){
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("redisSpring-cfg.xml");
RedisTemplate redisTemplate=applicationContext.getBean(RedisTemplate.class);
try {
//删除链表,以便我们反复测试
redisTemplate.delete("list");
//把node3插入链表list
redisTemplate.opsForList().leftPush("list","node3");
List<String> nodeList=new ArrayList<String>();
for(int i=2;i>=1;i--){
nodeList.add("node"+1);
}
//相当于lpush把多个节点从左插入链表
redisTemplate.opsForList().leftPushAll("list",nodeList);
//从右边插入一个节点
redisTemplate.opsForList().rightPush("list","node4");
//获取下标为0的节点
String node1=(String)redisTemplate.opsForList().index("list",0);
//获取链表的长度
long length=redisTemplate.opsForList().size("list");
//从左边弹出一个节点
String lpop=(String)redisTemplate.opsForList().leftPop("list");
//从右边弹出一个节点
String rpop=(String)redisTemplate.opsForList().rightPop("list");
//注意,需要使用更为底层的命令才能操作linsert命令
//使用linsert命令在node2前插入一个节点
redisTemplate.getConnectionFactory().getConnection().lInsert(getBytes("utf-8"),
RedisListCommands.Position.BEFORE,
"node2".getBytes("utf-8"),
"before_node".getBytes("utf-8"));
//使用linsert命令在node2后插入一个节点
redisTemplate.getConnectionFactory().getConnection().lInsert(getBytes("utf-8"),
RedisListCommands.Position.BEFORE,
"node2".getBytes("utf-8"),
"after_node".getBytes("utf-8"));
//判断list是否存在,如果存在则从左边插入head节点
redisTemplate.opsForList().leftPushIfPresent("list","head");
//判断list是否存在,如果存在则从右边边插入end节点
redisTemplate.opsForList().rightPushIfPresent("list","end");
//从左到右,或者下摆哦为0搭配10的节点元素
List valueList=redisTemplate.opsForList().range("list",0,10);
nodeList.clear();
for(int i=1;i<3;i++){
nodeList.add("node");
}
//在链表左边插入三个值为node的节点
redisTemplate.opsForList().leftPushAll("list",nodeList);
//从左到右删除至多三个node节点
redisTemplate.opsForList().remove("list",3,"node");
//给链表下标为0的节点设置新值
redisTemplate.opsForList().set("list",0,"new_head_node");
}catch (UnsupportedEncodingException ex){
ex.printStackTrace();
}
//打印链表数据
printList(redisTemplate,"list");
}
private static void printList(RedisTemplate redisTemplate, String list) {
//链表长度
Long size=redisTemplate.opsForList().size(list);
//获取整个链表的值
List valueList=redisTemplate.opsForList().range(list,0,size);
//打印
System.out.println(valueList);
}
}
运行结果: