mirror of
https://github.com/zhwei820/learn.lianglianglee.com.git
synced 2025-09-17 16:56:40 +08:00
1413 lines
30 KiB
HTML
1413 lines
30 KiB
HTML
<!DOCTYPE html>
|
||
|
||
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
|
||
|
||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||
|
||
<head>
|
||
|
||
<head>
|
||
|
||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
|
||
|
||
<link rel="icon" href="/static/favicon.png">
|
||
|
||
<title>10 列表使用与内部实现原理.md.html</title>
|
||
|
||
<!-- Spectre.css framework -->
|
||
|
||
<link rel="stylesheet" href="/static/index.css">
|
||
|
||
<!-- theme css & js -->
|
||
|
||
<meta name="generator" content="Hexo 4.2.0">
|
||
|
||
</head>
|
||
|
||
|
||
|
||
<body>
|
||
|
||
|
||
|
||
<div class="book-container">
|
||
|
||
<div class="book-sidebar">
|
||
|
||
<div class="book-brand">
|
||
|
||
<a href="/">
|
||
|
||
<img src="/static/favicon.png">
|
||
|
||
<span>技术文章摘抄</span>
|
||
|
||
</a>
|
||
|
||
</div>
|
||
|
||
<div class="book-menu uncollapsible">
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li><a href="/" class="current-tab">首页</a></li>
|
||
|
||
</ul>
|
||
|
||
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li><a href="../">上一级</a></li>
|
||
|
||
</ul>
|
||
|
||
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/01 Redis 是如何执行的.md.html">01 Redis 是如何执行的.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/02 Redis 快速搭建与使用.md.html">02 Redis 快速搭建与使用.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/03 Redis 持久化——RDB.md.html">03 Redis 持久化——RDB.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/04 Redis 持久化——AOF.md.html">04 Redis 持久化——AOF.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/05 Redis 持久化——混合持久化.md.html">05 Redis 持久化——混合持久化.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/06 字符串使用与内部实现原理.md.html">06 字符串使用与内部实现原理.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/07 附录:更多字符串操作命令.md.html">07 附录:更多字符串操作命令.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/08 字典使用与内部实现原理.md.html">08 字典使用与内部实现原理.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/09 附录:更多字典操作命令.md.html">09 附录:更多字典操作命令.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
<a class="current-tab" href="/专栏/Redis 核心原理与实战/10 列表使用与内部实现原理.md.html">10 列表使用与内部实现原理.md.html</a>
|
||
|
||
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/11 附录:更多列表操作命令.md.html">11 附录:更多列表操作命令.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/12 集合使用与内部实现原理.md.html">12 集合使用与内部实现原理.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/13 附录:更多集合操作命令.md.html">13 附录:更多集合操作命令.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/14 有序集合使用与内部实现原理.md.html">14 有序集合使用与内部实现原理.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/15 附录:更多有序集合操作命令.md.html">15 附录:更多有序集合操作命令.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/16 Redis 事务深入解析.md.html">16 Redis 事务深入解析.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/17 Redis 键值过期操作.md.html">17 Redis 键值过期操作.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/18 Redis 过期策略与源码分析.md.html">18 Redis 过期策略与源码分析.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/19 Redis 管道技术——Pipeline.md.html">19 Redis 管道技术——Pipeline.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/20 查询附近的人——GEO.md.html">20 查询附近的人——GEO.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/21 游标迭代器(过滤器)——Scan.md.html">21 游标迭代器(过滤器)——Scan.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/22 优秀的基数统计算法——HyperLogLog.md.html">22 优秀的基数统计算法——HyperLogLog.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/23 内存淘汰机制与算法.md.html">23 内存淘汰机制与算法.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/24 消息队列——发布订阅模式.md.html">24 消息队列——发布订阅模式.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/25 消息队列的其他实现方式.md.html">25 消息队列的其他实现方式.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/26 消息队列终极解决方案——Stream(上).md.html">26 消息队列终极解决方案——Stream(上).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/27 消息队列终极解决方案——Stream(下).md.html">27 消息队列终极解决方案——Stream(下).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/28 实战:分布式锁详解与代码.md.html">28 实战:分布式锁详解与代码.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/29 实战:布隆过滤器安装与使用及原理分析.md.html">29 实战:布隆过滤器安装与使用及原理分析.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/30 完整案例:实现延迟队列的两种方法.md.html">30 完整案例:实现延迟队列的两种方法.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/31 实战:定时任务案例.md.html">31 实战:定时任务案例.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/32 实战:RediSearch 高性能的全文搜索引擎.md.html">32 实战:RediSearch 高性能的全文搜索引擎.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/33 实战:Redis 性能测试.md.html">33 实战:Redis 性能测试.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/34 实战:Redis 慢查询.md.html">34 实战:Redis 慢查询.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/35 实战:Redis 性能优化方案.md.html">35 实战:Redis 性能优化方案.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/36 实战:Redis 主从同步.md.html">36 实战:Redis 主从同步.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/37 实战:Redis哨兵模式(上).md.html">37 实战:Redis哨兵模式(上).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/38 实战:Redis 哨兵模式(下).md.html">38 实战:Redis 哨兵模式(下).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/39 实战:Redis 集群模式(上).md.html">39 实战:Redis 集群模式(上).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/40 实战:Redis 集群模式(下).md.html">40 实战:Redis 集群模式(下).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/41 案例:Redis 问题汇总和相关解决方案.md.html">41 案例:Redis 问题汇总和相关解决方案.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/42 技能学习指南.md.html">42 技能学习指南.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/43 加餐:Redis 的可视化管理工具.md.html">43 加餐:Redis 的可视化管理工具.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
</ul>
|
||
|
||
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
|
||
|
||
<div class="sidebar-toggle-inner"></div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<script>
|
||
|
||
function add_inner() {
|
||
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
|
||
inner.classList.add('show')
|
||
|
||
}
|
||
|
||
|
||
|
||
function remove_inner() {
|
||
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
|
||
inner.classList.remove('show')
|
||
|
||
}
|
||
|
||
|
||
|
||
function sidebar_toggle() {
|
||
|
||
let sidebar_toggle = document.querySelector('.sidebar-toggle')
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let content = document.querySelector('.off-canvas-content')
|
||
|
||
if (sidebar_toggle.classList.contains('extend')) { // show
|
||
|
||
sidebar_toggle.classList.remove('extend')
|
||
|
||
sidebar.classList.remove('hide')
|
||
|
||
content.classList.remove('extend')
|
||
|
||
} else { // hide
|
||
|
||
sidebar_toggle.classList.add('extend')
|
||
|
||
sidebar.classList.add('hide')
|
||
|
||
content.classList.add('extend')
|
||
|
||
}
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
function open_sidebar() {
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
|
||
sidebar.classList.add('show')
|
||
|
||
overlay.classList.add('show')
|
||
|
||
}
|
||
|
||
function hide_canvas() {
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
|
||
sidebar.classList.remove('show')
|
||
|
||
overlay.classList.remove('show')
|
||
|
||
}
|
||
|
||
|
||
|
||
</script>
|
||
|
||
|
||
|
||
<div class="off-canvas-content">
|
||
|
||
<div class="columns">
|
||
|
||
<div class="column col-12 col-lg-12">
|
||
|
||
<div class="book-navbar">
|
||
|
||
<!-- For Responsive Layout -->
|
||
|
||
<header class="navbar">
|
||
|
||
<section class="navbar-section">
|
||
|
||
<a onclick="open_sidebar()">
|
||
|
||
<i class="icon icon-menu"></i>
|
||
|
||
</a>
|
||
|
||
</section>
|
||
|
||
</header>
|
||
|
||
</div>
|
||
|
||
<div class="book-content" style="max-width: 960px; margin: 0 auto;
|
||
|
||
overflow-x: auto;
|
||
|
||
overflow-y: hidden;">
|
||
|
||
<div class="book-post">
|
||
|
||
<p id="tip" align="center"></p>
|
||
|
||
<div><h1>10 列表使用与内部实现原理</h1>
|
||
|
||
<p>列表类型 (List) 是一个使用链表结构存储的有序结构,它的元素插入会按照先后顺序存储到链表结构中,因此它的元素操作 (插入\删除) 时间复杂度为 O(1),所以相对来说速度还是比较快的,但它的查询时间复杂度为 O(n),因此查询可能会比较慢。</p>
|
||
|
||
<h3>1 基础使用</h3>
|
||
|
||
<p>列表类型的使用相对来说比较简单,对它的操作就相当操作一个没有任何 key 值的 value 集合,如下图所示: <img src="assets/2020-02-28-031229.png" alt="列表类型使用-列表结构图.png" /></p>
|
||
|
||
<h4>1)给列表添加一个或多个元素</h4>
|
||
|
||
<p>语法:lpush key value [value …] 示例:</p>
|
||
|
||
<pre><code class="language-shell">127.0.0.1:6379> lpush list 1 2 3
|
||
|
||
(integer) 3
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<h4>2)给列表尾部添加一个或多个元素</h4>
|
||
|
||
<p>语法:rpush key value [value …] 示例:</p>
|
||
|
||
<pre><code class="language-shell">127.0.0.1:6379> rpush list2 1 2 3
|
||
|
||
(integer) 3
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<h4>3)返回列表指定区间内的元素</h4>
|
||
|
||
<p>语法:lrange key start stop 示例:</p>
|
||
|
||
<pre><code class="language-shell">127.0.0.1:6379> lrange list 0 -1
|
||
|
||
"3"
|
||
|
||
"2"
|
||
|
||
"1"
|
||
|
||
127.0.0.1:6379> lrange list2 0 -1
|
||
|
||
"1"
|
||
|
||
"2"
|
||
|
||
"3"
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>其中 -1 代表列表中的最后一个元素。</p>
|
||
|
||
<h4>4)获取并删除列表的第一个元素</h4>
|
||
|
||
<p>语法:lpop key 示例:</p>
|
||
|
||
<pre><code class="language-shell">127.0.0.1:6379> lrange list 0 -1
|
||
|
||
1) "d"
|
||
|
||
2) "c"
|
||
|
||
3) "b"
|
||
|
||
4) "a"
|
||
|
||
127.0.0.1:6379> lpop list
|
||
|
||
"d"
|
||
|
||
127.0.0.1:6379> lrange list 0 -1
|
||
|
||
1) "c"
|
||
|
||
2) "b"
|
||
|
||
3) "a"
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<h4>5)获取并删除列表的最后一个元素</h4>
|
||
|
||
<p>语法:rpop key 示例:</p>
|
||
|
||
<pre><code class="language-shell">127.0.0.1:6379> lrange list 0 -1
|
||
|
||
1) "c"
|
||
|
||
2) "b"
|
||
|
||
3) "a"
|
||
|
||
127.0.0.1:6379> rpop list
|
||
|
||
"a"
|
||
|
||
127.0.0.1:6379> lrange list 0 -1
|
||
|
||
1) "c"
|
||
|
||
2) "b"
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<h4>6)根据下标获取对应的元素</h4>
|
||
|
||
<p>语法:lindex key index 示例:</p>
|
||
|
||
<pre><code class="language-shell">127.0.0.1:6379> rpush list3 a b c
|
||
|
||
(integer) 3
|
||
|
||
127.0.0.1:6379> lindex list3 0
|
||
|
||
"a"
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>更多操作命令,详见附录部分。</p>
|
||
|
||
<h3>2 代码实战</h3>
|
||
|
||
<p>下面来看列表类型在 Java 中的使用,同样先添加 Jedis 框架,使用代码如下:</p>
|
||
|
||
<pre><code class="language-java">public class ListExample {
|
||
|
||
public static void main(String[] args) {
|
||
|
||
Jedis jedis = new Jedis("127.0.0.1", 6379);
|
||
|
||
// 声明 Redis key
|
||
|
||
final String REDISKEY = "list";
|
||
|
||
// 在头部插入一个或多个元素
|
||
|
||
Long lpushResult = jedis.lpush(REDISKEY, "Java", "Sql");
|
||
|
||
System.out.println(lpushResult); // 输出:2
|
||
|
||
// 获取第 0 个元素的值
|
||
|
||
String idValue = jedis.lindex(REDISKEY, 0);
|
||
|
||
System.out.println(idValue); // 输出:Sql
|
||
|
||
// 查询指定区间的元素
|
||
|
||
List<String> list = jedis.lrange(REDISKEY, 0, -1);
|
||
|
||
System.out.println(list); // 输出:[Sql, Java]
|
||
|
||
// 在元素 Java 前面添加 MySQL 元素
|
||
|
||
jedis.linsert(REDISKEY, ListPosition.BEFORE, "Java", "MySQL");
|
||
|
||
System.out.println(jedis.lrange(REDISKEY, 0, -1)); // 输出:[Sql, MySQL, Java]
|
||
|
||
jedis.close();
|
||
|
||
}
|
||
|
||
}
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>程序运行结果如下:</p>
|
||
|
||
<blockquote>
|
||
|
||
<p>2 Sql [Sql, Java] [Sql, MySQL, Java]</p>
|
||
|
||
</blockquote>
|
||
|
||
<h3>3 内部实现</h3>
|
||
|
||
<p>我们先用 <code>debug encoding key</code> 来查看列表类型的内部存储类型,如下所示:</p>
|
||
|
||
<pre><code class="language-shell">127.0.0.1:6379> object encoding list
|
||
|
||
"quicklist"
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>从结果可以看出,列表类型的底层数据类型是 quicklist。</p>
|
||
|
||
<p>quicklist (快速列表) 是 Redis 3.2 引入的数据类型,早期的列表类型使用的是ziplist (压缩列表) 和双向链表组成的,Redis 3.2 改为用 quicklist 来存储列表元素。</p>
|
||
|
||
<p>我们来看下 quicklist 的实现源码:</p>
|
||
|
||
<pre><code class="language-c">typedef struct quicklist { // src/quicklist.h
|
||
|
||
quicklistNode *head;
|
||
|
||
quicklistNode *tail;
|
||
|
||
unsigned long count; /* ziplist 的个数 */
|
||
|
||
unsigned long len; /* quicklist 的节点数 */
|
||
|
||
unsigned int compress : 16; /* LZF 压缩算法深度 */
|
||
|
||
//...
|
||
|
||
} quicklist;
|
||
|
||
typedef struct quicklistNode {
|
||
|
||
struct quicklistNode *prev;
|
||
|
||
struct quicklistNode *next;
|
||
|
||
unsigned char *zl; /* 对应的 ziplist */
|
||
|
||
unsigned int sz; /* ziplist 字节数 */
|
||
|
||
unsigned int count : 16; /* ziplist 个数 */
|
||
|
||
unsigned int encoding : 2; /* RAW==1 or LZF==2 */
|
||
|
||
unsigned int container : 2; /* NONE==1 or ZIPLIST==2 */
|
||
|
||
unsigned int recompress : 1; /* 该节点先前是否被压缩 */
|
||
|
||
unsigned int attempted_compress : 1; /* 节点太小无法压缩 */
|
||
|
||
//...
|
||
|
||
} quicklistNode;
|
||
|
||
typedef struct quicklistLZF {
|
||
|
||
unsigned int sz;
|
||
|
||
char compressed[];
|
||
|
||
} quicklistLZF;
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>从以上源码可以看出 quicklist 是一个双向链表,链表中的每个节点实际上是一个 ziplist,它们的结构如下图所示: <img src="assets/2020-02-28-31230.png" alt="列表类型使用-quicklist结构图.png" /></p>
|
||
|
||
<p>ziplist 作为 quicklist 的实际存储结构,它本质是一个字节数组,ziplist 数据结构如下图所示:</p>
|
||
|
||
<p><img src="assets/2020-02-28-031231.png" alt="列表类型使用-压缩列表结构图.png" /></p>
|
||
|
||
<p>其中的字段含义如下:</p>
|
||
|
||
<ul>
|
||
|
||
<li>zlbytes:压缩列表字节长度,占 4 字节;</li>
|
||
|
||
<li>zltail:压缩列表尾元素相对于起始元素地址的偏移量,占 4 字节;</li>
|
||
|
||
<li>zllen:压缩列表的元素个数;</li>
|
||
|
||
<li>entryX:压缩列表存储的所有元素,可以是字节数组或者是整数;</li>
|
||
|
||
<li>zlend:压缩列表的结尾,占 1 字节。</li>
|
||
|
||
</ul>
|
||
|
||
<h3>4 源码解析</h3>
|
||
|
||
<p>下面我们来看一下更多关于列表类型的源码实现。</p>
|
||
|
||
<h4>1)添加功能源码分析</h4>
|
||
|
||
<p>quicklist 添加操作对应函数是 quicklistPush,源码如下:</p>
|
||
|
||
<pre><code class="language-c">void quicklistPush(quicklist *quicklist, void *value, const size_t sz,
|
||
|
||
int where) {
|
||
|
||
if (where == QUICKLIST_HEAD) {
|
||
|
||
// 在列表头部添加元素
|
||
|
||
quicklistPushHead(quicklist, value, sz);
|
||
|
||
} else if (where == QUICKLIST_TAIL) {
|
||
|
||
// 在列表尾部添加元素
|
||
|
||
quicklistPushTail(quicklist, value, sz);
|
||
|
||
}
|
||
|
||
}
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>以 quicklistPushHead 为例,源码如下:</p>
|
||
|
||
<pre><code class="language-c">int quicklistPushHead(quicklist *quicklist, void *value, size_t sz) {
|
||
|
||
quicklistNode *orig_head = quicklist->head;
|
||
|
||
if (likely(
|
||
|
||
_quicklistNodeAllowInsert(quicklist->head, quicklist->fill, sz))) {
|
||
|
||
// 在头部节点插入元素
|
||
|
||
quicklist->head->zl =
|
||
|
||
ziplistPush(quicklist->head->zl, value, sz, ZIPLIST_HEAD);
|
||
|
||
quicklistNodeUpdateSz(quicklist->head);
|
||
|
||
} else {
|
||
|
||
// 头部节点不能继续插入,需要新建 quicklistNode、ziplist 进行插入
|
||
|
||
quicklistNode *node = quicklistCreateNode();
|
||
|
||
node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_HEAD);
|
||
|
||
quicklistNodeUpdateSz(node);
|
||
|
||
// 将新建的 quicklistNode 插入到 quicklist 结构中
|
||
|
||
_quicklistInsertNodeBefore(quicklist, quicklist->head, node);
|
||
|
||
}
|
||
|
||
quicklist->count++;
|
||
|
||
quicklist->head->count++;
|
||
|
||
return (orig_head != quicklist->head);
|
||
|
||
}
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>quicklistPushHead 函数的执行流程,先判断 quicklist 的 head 节点是否可以插入数据,如果可以插入则使用 ziplist 的接口进行插入,否则就新建 quicklistNode 节点进行插入。</p>
|
||
|
||
<p>函数的入参是待插入的 quicklist,还有需要插入的值 value 以及他的大小 sz。</p>
|
||
|
||
<p>函数的返回值为 int,0 表示没有新建 head,1 表示新建了 head。 quicklistPushHead 执行流程,如下图所示:</p>
|
||
|
||
<p><img src="assets/2020-02-28-031232.png" alt="列表类型使用-插入流程图.png" /></p>
|
||
|
||
<h4>2)删除功能源码分析</h4>
|
||
|
||
<p>quicklist 元素删除分为两种情况:单一元素删除和区间元素删除,它们都位于 src/quicklist.c 文件中。</p>
|
||
|
||
<h5>① 单一元素删除</h5>
|
||
|
||
<p>单一元素的删除函数是 quicklistDelEntry,源码如下:</p>
|
||
|
||
<pre><code class="language-c">void quicklistDelEntry(quicklistIter *iter, quicklistEntry *entry) {
|
||
|
||
quicklistNode *prev = entry->node->prev;
|
||
|
||
quicklistNode *next = entry->node->next;
|
||
|
||
// 删除指定位置的元素
|
||
|
||
int deleted_node = quicklistDelIndex((quicklist *)entry->quicklist,
|
||
|
||
entry->node, &entry->zi);
|
||
|
||
//...
|
||
|
||
}
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>可以看出 quicklistDelEntry 函数的底层,依赖 quicklistDelIndex 函数进行元素删除。</p>
|
||
|
||
<h5>② 区间元素删除</h5>
|
||
|
||
<p>区间元素删除的函数是 quicklistDelRange,源码如下:</p>
|
||
|
||
<pre><code class="language-c">// start 表示开始删除的下标,count 表示要删除的个数
|
||
|
||
int quicklistDelRange(quicklist *quicklist, const long start,
|
||
|
||
const long count) {
|
||
|
||
if (count <= 0)
|
||
|
||
return 0;
|
||
|
||
unsigned long extent = count;
|
||
|
||
if (start >= 0 && extent > (quicklist->count - start)) {
|
||
|
||
// 删除的元素个数大于已有元素
|
||
|
||
extent = quicklist->count - start;
|
||
|
||
} else if (start < 0 && extent > (unsigned long)(-start)) {
|
||
|
||
// 删除指定的元素个数
|
||
|
||
extent = -start; /* c.f. LREM -29 29; just delete until end. */
|
||
|
||
}
|
||
|
||
//...
|
||
|
||
// extent 为剩余需要删除的元素个数,
|
||
|
||
while (extent) {
|
||
|
||
// 保存下个 quicklistNode,因为本节点可能会被删除
|
||
|
||
quicklistNode *next = node->next;
|
||
|
||
unsigned long del;
|
||
|
||
int delete_entire_node = 0;
|
||
|
||
if (entry.offset == 0 && extent >= node->count) {
|
||
|
||
// 删除整个 quicklistNode
|
||
|
||
delete_entire_node = 1;
|
||
|
||
del = node->count;
|
||
|
||
} else if (entry.offset >= 0 && extent >= node->count) {
|
||
|
||
// 删除本节点的所有元素
|
||
|
||
del = node->count - entry.offset;
|
||
|
||
} else if (entry.offset < 0) {
|
||
|
||
// entry.offset<0 表示从后向前,相反则表示从前向后剩余的元素个数
|
||
|
||
del = -entry.offset;
|
||
|
||
if (del > extent)
|
||
|
||
del = extent;
|
||
|
||
} else {
|
||
|
||
// 删除本节点部分元素
|
||
|
||
del = extent;
|
||
|
||
}
|
||
|
||
D("[%ld]: asking to del: %ld because offset: %d; (ENTIRE NODE: %d), "
|
||
|
||
"node count: %u",
|
||
|
||
extent, del, entry.offset, delete_entire_node, node->count);
|
||
|
||
if (delete_entire_node) {
|
||
|
||
__quicklistDelNode(quicklist, node);
|
||
|
||
} else {
|
||
|
||
quicklistDecompressNodeForUse(node);
|
||
|
||
node->zl = ziplistDeleteRange(node->zl, entry.offset, del);
|
||
|
||
quicklistNodeUpdateSz(node);
|
||
|
||
node->count -= del;
|
||
|
||
quicklist->count -= del;
|
||
|
||
quicklistDeleteIfEmpty(quicklist, node);
|
||
|
||
if (node)
|
||
|
||
quicklistRecompressOnly(quicklist, node);
|
||
|
||
}
|
||
|
||
// 剩余待删除元素的个数
|
||
|
||
extent -= del;
|
||
|
||
// 下个 quicklistNode
|
||
|
||
node = next;
|
||
|
||
// 从下个 quicklistNode 起始位置开始删除
|
||
|
||
entry.offset = 0;
|
||
|
||
}
|
||
|
||
return 1;
|
||
|
||
}
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>从上面代码可以看出,quicklist 在区间删除时,会先找到 start 所在的 quicklistNode,计算删除的元素是否小于要删除的 count,如果不满足删除的个数,则会移动至下一个 quicklistNode 继续删除,依次循环直到删除完成为止。</p>
|
||
|
||
<p>quicklistDelRange 函数的返回值为 int 类型,当返回 1 时表示成功的删除了指定区间的元素,返回 0 时表示没有删除任何元素。</p>
|
||
|
||
<h4>3)更多源码</h4>
|
||
|
||
<p>除了上面介绍的几个常用函数之外,还有一些更多的函数,例如:</p>
|
||
|
||
<ul>
|
||
|
||
<li>quicklistCreate:创建 quicklist;</li>
|
||
|
||
<li>quicklistInsertAfter:在某个元素的后面添加数据;</li>
|
||
|
||
<li>quicklistInsertBefore:在某个元素的前面添加数据;</li>
|
||
|
||
<li>quicklistPop:取出并删除列表的第一个或最后一个元素;</li>
|
||
|
||
<li>quicklistReplaceAtIndex:替换某个元素。</li>
|
||
|
||
</ul>
|
||
|
||
<h3>5 使用场景</h3>
|
||
|
||
<p>列表的典型使用场景有以下两个:</p>
|
||
|
||
<ul>
|
||
|
||
<li>消息队列:列表类型可以使用 rpush 实现先进先出的功能,同时又可以使用 lpop 轻松的弹出(查询并删除)第一个元素,所以列表类型可以用来实现消息队列;</li>
|
||
|
||
<li>文章列表:对于博客站点来说,当用户和文章都越来越多时,为了加快程序的响应速度,我们可以把用户自己的文章存入到 List 中,因为 List 是有序的结构,所以这样又可以完美的实现分页功能,从而加速了程序的响应速度。</li>
|
||
|
||
</ul>
|
||
|
||
<h3>6 小结</h3>
|
||
|
||
<p>通过本文我们可以知道列表类型并不是简单的双向链表,而是采用了 quicklist 的数据结构对数据进行存取,quicklist 是 Redis 3.2 新增的数据类型,它的底层采取的是压缩列表加双向链表的存储结构,quicklist 为了存储更多的数据,会对每个 quicklistNode 节点进行压缩,这样就可以有效的存储更多的消息队列或者文章的数据了。</p>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div>
|
||
|
||
<div style="float: left">
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/09 附录:更多字典操作命令.md.html">上一页</a>
|
||
|
||
</div>
|
||
|
||
<div style="float: right">
|
||
|
||
<a href="/专栏/Redis 核心原理与实战/11 附录:更多列表操作命令.md.html">下一页</a>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
|
||
|
||
</div>
|
||
|
||
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"709973c3bddf3d60","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
|
||
|
||
</body>
|
||
|
||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||
|
||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
|
||
|
||
<script>
|
||
|
||
window.dataLayer = window.dataLayer || [];
|
||
|
||
|
||
|
||
function gtag() {
|
||
|
||
dataLayer.push(arguments);
|
||
|
||
}
|
||
|
||
|
||
|
||
gtag('js', new Date());
|
||
|
||
gtag('config', 'G-NPSEEVD756');
|
||
|
||
var path = window.location.pathname
|
||
|
||
var cookie = getCookie("lastPath");
|
||
|
||
console.log(path)
|
||
|
||
if (path.replace("/", "") === "") {
|
||
|
||
if (cookie.replace("/", "") !== "") {
|
||
|
||
console.log(cookie)
|
||
|
||
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
|
||
|
||
}
|
||
|
||
} else {
|
||
|
||
setCookie("lastPath", path)
|
||
|
||
}
|
||
|
||
|
||
|
||
function setCookie(cname, cvalue) {
|
||
|
||
var d = new Date();
|
||
|
||
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
|
||
|
||
var expires = "expires=" + d.toGMTString();
|
||
|
||
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
|
||
|
||
}
|
||
|
||
|
||
|
||
function getCookie(cname) {
|
||
|
||
var name = cname + "=";
|
||
|
||
var ca = document.cookie.split(';');
|
||
|
||
for (var i = 0; i < ca.length; i++) {
|
||
|
||
var c = ca[i].trim();
|
||
|
||
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
|
||
|
||
}
|
||
|
||
return "";
|
||
|
||
}
|
||
|
||
|
||
|
||
</script>
|
||
|
||
|
||
|
||
</html>
|
||
|