日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

Redis链表结构深入

發布時間:2025/3/12 数据库 18 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Redis链表结构深入 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

鏈表結構是 Redis 中一個常用的結構,它可以存儲多個字符串,而且它是有序的,能夠存儲 2 的 32 次方減 1 個節點(超過 40 億個節點)。

Redis 鏈表是雙向的,因此即可以從左到右,也可以從右到左遍歷它存儲的節點,鏈表結構如下圖所示。

由于是雙向鏈表,所以只能夠從左到右,或者從右到左地訪問和操作鏈表里面的數據節點。但是使用鏈表結構就意味著讀性能的喪失,所以要在大量數據中找到一個節點的操作性能是不佳的,因為鏈表只能從一個方向中去遍歷所要節點。

比如從查找節點 10 000 開始查詢,它需要按照節點 1、節點 2、節點 3……直至節點 10 000,這樣的順序查找,然后把一個個節點和你給出的值比對,才能確定節點所在。如果這個鏈表很大,如有上百萬個節點,可能需要遍歷幾十萬次才能找到所需要的節點,顯然查找性能是不佳的。

而鏈表結構的優勢在于插入和刪除的便利,因為鏈表的數據節點是分配在不同的內存區域的,并不連續,只是根據上一個節點保存下一個節點的順序來索引而已,無需移動元素。其新增和刪除的操作如下圖所示。

上述的的阿拉伯數字代表新增的步驟,而漢字數字代表刪除步驟。

1 新增節點
對插入圖中的節點 4 而言,先看從左到右的指向,先讓節點 4 指向節點 1 原來的下一個節點,也就是節點 2,然后讓節點 1 指向節點 4,這樣就完成了從右到左的指向修改。再看從右到左,先讓節點 4 指向節點 1,然后節點 2 指向節點 4,這個時候就完成了從右到左的指向,那么節點 1 和節點 2 之間的原有關聯關系都已經失效,這樣就完成了在鏈表中新增節點4的功能。

2 刪除節點
對刪除圖中的節點 3 而言,首先讓節點 2 從左到右指向后續節點,然后讓后續節點指向節點 2,這樣節點 3 就脫離了鏈表,也就是斷絕了與節點 2 和后繼節點的關聯關系,然后對節點 3 進行內存回收,無須移動任何節點,就完成了刪除。

由此可見,鏈表結構的使用是需要注意場景的,對于那些經常需要對數據進行插入和刪除的列表數據使用它是十分方便的,因為它可以在不移動其他節點的情況下完成插入和刪除。而對于需要經常查找的,使用它性能并不佳,它只能從左到右或者從右到左的查找和比對。

因為是雙向鏈表結構,所以 Redis 鏈表命令分為左操作和右操作兩種命令,左操作就意味著是從左到右,右操作就意味著是從右到左。

Redis關于鏈表的命令

上表所示的鏈表命令,其中以“l”開頭的代表左操作,以“r”開頭的代表右操作。對于很多個節點同時操作的,需要考慮其花費的時間,鏈表數據結構對于查找而言并不適合于大數據,而 Redis 也給了比較靈活的命令對其進行操作。

Redis關于鏈表的操作命令

這里展示了關于 Redis 鏈表的常用命令,只是對于大量數據操作的時候,我們需要考慮插入和刪除內容的大小,因為這將是十分消耗性能的命令,會導致 Redis 服務器的卡頓。對于不允許卡頓的一些服務器,可以進行分批次操作,以避免出現卡頓。

需要指出的是,之前這些操作鏈表的命令都是進程不安全的,因為當我們操作這些命令的時候,其他 Redis 的客戶端也可能操作同一個鏈表,這樣就會造成并發數據安全和一致性的問題,尤其是當你操作一個數據量不小的鏈表結構時,常常會遇到這樣的問題。

為了克服這些問題,Redis 提供了鏈表的阻塞命令,它們在運行的時候,會給鏈表加鎖,以保證操作鏈表的命令安全性,如下表所示。

鏈表的阻塞命令


當使用這些命令時,Redis 就會對對應的鏈表加鎖,加鎖的結果就是其他的進程不能再讀取或者寫入該鏈表,只能等待命令結束。加鎖的好處可以保證在多線程并發環境中數據的一致性,保證一些重要數據的一致性,比如賬戶的金額、商品的數量。

不過在保證這些的同時也要付出其他線程等待、線程環境切換等代價,這將使得系統的并發能力下降。

Redis 鏈表阻塞操作命令

在項目中,雖然阻塞可以有效保證了數據的一致性,但是阻塞就意味著其他進程的等待,CPU 需要給其他線程掛起、恢復等操作,更多的時候我們希望的并不是阻塞的處理請求,所以這些命令在實際中使用得并不多。

使用 Spring 去操作 Redis 鏈表的命令,這里繼續保持代碼關于 RedisTemplate 的配置,在此基礎上獲取 RedisTemplate 對象,然后輸入以下代碼。

public static void testList() {ApplicationContext applicationcontext = new ClassPathXmlApplicationContext("applicationContext.xml");RedisTemplate redisTemplate = applicationcontext.getBean(RedisTemplate.class);try {//刪除鏈表,以便我們可以反復測試redisTemplate.delete("list");//把node3插入鏈表listredisTemplate. opsForList ().leftPush ("list", "node3");List<String> nodeList = new ArrayList<String>();for (int i = 2; i >= 1; i--){nodeList.add("nnode" + i);}//相當于lpush把多個價值從左插入鏈表redisTemplate.opsForList().leftPushAll("list", nodeList);//從右邊插入一個節點redisTemplate.opsForList().rightPush("list", "node4");//獲取下標為0的節點String nodel = (String) redisTemplate.opsForList() .index("list", 0);//獲取鏈表長度long size = redisTemplate.opsForList ().size ("listn");//從左邊彈出一個節點String lpop = (String) redisTemplate.opsForList().leftPop("list");//從右邊彈出一個節點String rpop = (String) redisTemplate.opsForList().rightPop("list");//注意,需要使用更為底層的命令才能操作linsert命令//使用linsert命令在node2前插入一個節點redisTemplate.getConnectionFactory().getConnection().lInsert("list".getBytes("utf-8"),RedisListCommands.Position.BEFORE,"node2".getBytes("utf-8"),"before_node".getBytes("utf-8"));//使用linsert命令在node2后插入一個節點redisTemplate.getConnectionFactory().getConnection().linsert("list".getBytes("utf-8"),RedisListCommands.Position.AFTER,"node2".getBytes("utf-8"), "after_node".getBytes("utf-8"));//判斷list是否存在,如果存在則從左邊插入head節點redisTemplate.opsForList().leftPushlfPresent("list", "head");//判斷list是否存在,如果存在則從右邊插入end節點redisTemplate.opsForList().rightPushlfPresent("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_value");} catch (UnsupportedEncodingException ex) {ex.printStackTrace();}//打印鏈表數據printList(redisTemplate, "list"); } public static void printList(RedisTemplate redisTemplate, String key) { //鏈表長度Long size = redisTemplate.opsForList().size(key); }

這里所展示的是 RedisTemplate 對于 Redis 鏈表的操作,其中 left 代表左操作,right 代表右操作。有些命令 Spring 所提供的 RedisTemplate 并不能支持,比如 linsert 命令,這個時候可以使用更為底層的方法去操作,正如代碼中的這段:

// 使用linsert命令在node2前插入一個節點 redisTemplate.getConnectionFactory().getConnection().lInsert("list".getBytes("utf-8"), RedisListCommands.Position.BEFORE,"node2".getBytes("utf-8"), "before_node".getBytes("utf-8"));

在多值操作的時候,往往會使用 list 進行封裝,比如 leftPushAll 方法,對于很大的 list 的操作需要注意性能,比如 remove 這樣的操作,在大的鏈表中會消耗 Redis 系統很多的性能。

Redis 還有對鏈表進行阻塞操作的命令,這里 Spring 也給出了支持,代碼如下所示。

public static void testBList() {ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");RedisTemplate redisTemplate = applicationContext.getBean(RedisTemplate.class);// 清空數據,可以重復測試redisTemplate.delete ("list1");redisTemplate.delete ("list2");//初始化鏈表 list1List<String> nodeList = new ArrayList<String>();for (int i=1; i<=5; i++) {nodeList.add("node" + i);}redisTemplate.opsForList().leftPushAll("list1", nodeList);// Spring 使用參數超時時間作為阻塞命令區分,等價于 blpop 命令,并且可以設置時間參數 redisTemplate.opsForList().leftPop ("list1", 1, TimeUnit.SECONDS);// Spring 使用參數超時時間作為阻塞命令區分,等價于 brpop 命令,并且可以設置時間參數redisTemplate.opsForList().rightPop("list1", 1, TimeUnit.SECONDS);nodeList.clear();// 初始化鏈表 list2for (int i=1; i<=3; i++) {nodeList.add("dato" + i);}redisTemplate.opsForList().leftPushAll("list2", nodeList);// 相當于 rpoplpush 命令,彈出 list1 最右邊的節點,插入到 list2 最左邊redisTemplate.opsForList().rightPopAndLeftPush("list1","list2");// 相當于brpoplpush命令,注意在 Spring 中使用超時參數區分 redisTemplate.opsForList().rightPopAndLeftPush("list1", "list2",1,TimeUnit.SECONDS);// 打印鏈表數據printList(redisTemplate, "list1");printList(redisTemplate, "list2"); }

這里展示了 Redis 關于鏈表的阻塞命令,在 Spring 中它和非阻塞命令的方法是一致的,只是它會通過超時參數進行區分,而且我們還可以通過方法設置時間的單位,使用還是相當簡單的。注意,它是阻塞的命令,在多線程的環境中,它能在一定程度上保證數據的一致而性能卻不佳。

總結

以上是生活随笔為你收集整理的Redis链表结构深入的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。