鏈表結構是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);
}
}
運行結果: