禅与计算机 禅与计算机
首页
  • Java基础

    • 聊一聊java一些核心知识点
    • 聊聊java面向对象核心知识点
    • 聊聊Java中的异常
    • 聊聊Java中的常用类String
    • 万字长文带你细聊Java注解本质
    • 来聊聊Java的反射机制
    • 深入解析Java泛型的魅力与机制
    • Java集合框架深度解析与面试指南
    • Java常用集合类HashMap深度解析
    • LinkedHashMap源码到面试题的全解析
    • 深入解析CopyOnWriteArrayList的工作机制
    • Java基础IO总结
    • Java三大IO模型小结
    • Java BIO NIO AIO详解
    • Java进阶NIO之IO多路复用详解
    • Java8流式编程入门
    • 一文速通lambda与函数式编程
    • Java8函数式方法引用最佳实践
  • Java并发编程

    • Java并发编程基础小结
    • 深入理解Java中的final关键字
    • 浅谈Java并发安全发布技术
    • 浅谈Java并发编程中断的哲学
    • Java线程池知识点小结
    • 浅谈Java线程池中拒绝策略与流控的艺术
    • synchronized关键字使用指南
    • 深入源码解析synchronized关键字
    • 详解JUC包下的锁
    • 详解并发编程中的CAS原子类
    • LongAdder源码分析
    • AQS源码解析
    • 深入剖析Java并发编程中的死锁问题
    • Java并发容器总结
    • 详解Java并发编程volatile关键字
    • 并发编程ThreadLocal必知必会
    • CompletableFuture基础实践小结
    • CompletableFuture异步多任务最佳实践
    • 硬核详解FutureTask设计与实现
    • 线程池大小设置的底层逻辑与场景化方案
    • 来聊一个有趣的限流器RateLimiter
  • JVM相关

    • 从零开始掌握 JVM
    • JVM核心知识点小结
    • JVM指令集概览:基础与应用
    • JVM类加载器深度解析
    • JVM方法区深度解析
    • Java内存模型JMM详解
    • Java对象大小的精确计算方法
    • 逃逸分析在Java中的应用与优化
    • 从零开始理解JVM的JIT编译机制
    • G1垃圾回收器:原理详解与调优指南
    • JVM故障排查实战指南
    • JVM内存问题排错最佳实践
    • JVM内存溢出排查指南
    • 简明的Arthas使用教程
    • 简明的Arthas配置及基础运维教程
    • 基于Arthas Idea的JVM故障排查与指令生成
    • 基于arthas量化监控诊断java应用方法论与实践
    • 深入剖析arthas技术原理
  • 深入理解Spring框架

    • Spring 核心知识点全面解析
    • Spring核心功能IOC详解
    • Spring AOP 深度剖析与实践
    • Spring 三级缓存机制深度解析
    • 深入 Spring 源码,剖析设计模式的落地实践
    • 探索 Spring 事务的奥秘
    • 深入解析Spring Bean的生命周期管理
    • 解读 Spring Boot 核心知识点
    • Spring Boot 启动优化实战:1分钟到13秒的排查与优化之路
    • Spring Boot自动装配原理及实践
    • 一文快速上手Sharding-JDBC
    • sharding-jdbc如何实现分页查询
    • 基于DynamicDataSource整合分库分表框架Shardingsphere
  • 计算机组成原理

    • 计算机硬件知识小结
    • CPU核心知识点小结
    • 浅谈CPU流水线的艺术
    • 从Java程序员视角聊聊CPU缓存
    • CPU任务调度和伪共享问题小结
    • CPU MESI缓存一致性协议
    • CPU内存管理机制
    • 内存深度解析
    • 磁盘存储原理
    • 详解计算机启动步骤
    • CPU南北桥架构与发展史
    • CPU中断机制与硬件交互详解
  • 操作系统

    • 如何实现一个高性能服务器
    • Linux文件结构与文件权限
    • Linux常见压缩指令小结
    • Linux核心系统调用详解
    • Linux进程管理
    • Linux线程管理
    • 进程与线程深度解析
    • Linux进程间通信机制
    • 零拷贝技术原理与实践
    • CPU缓存一致性问题深度解析
    • IO任务与CPU调度艺术
  • 计算机网络

    • 网卡通信原理详解
    • 网卡数据包处理指南
    • 基于抓包详解TCP协议
  • 编码最佳实践

    • 浅谈现代软件工程TDD最佳实践
    • 浅谈TDD模式下并发程序设计与实现
    • 面向AI编程新范式Trae后端开发环境搭建与实践
    • 基于提示词工程的Redis签到功能开发实践
    • 基于Vibe Coding的Redis分页查询实现
    • 告别AI无效对话:资深工程师的提示词设计最佳实践
  • 实用技巧与配置

    • Mac常用快捷键与效率插件指南
    • Keynote技术科普短视频制作全攻略
  • 写作

    • 写好技术博客的5大核心原则:从认知科学到AI工具的全流程指南
  • 开发工具

    • IDEA配置详解与高效使用指南
  • Nodejs
  • 博客搭建
  • Redis

    • Redis核心知识小结
    • 解锁Redis发布订阅模式
    • 掌握Redis事务
    • Redis主从复制技术
    • Redis的哨兵模式详解
    • 深度剖析Redisson分布式锁
    • 详解redis单线程设计思路
    • 来聊聊Redis所实现的Reactor模型
    • Redis RDB持久化源码深度解析
    • 来聊聊redis的AOF写入
    • 来聊聊Redis持久化AOF管道通信的设计
    • 来聊聊redis集群数据迁移
    • Redis SDS动态字符串深度解析
    • 高效索引的秘密:redis跳表设计与实现
    • 聊聊redis中的字典设计与实现
  • MySQL

    • MySQL基础知识点小结
    • 解读MySQL 索引基础
    • MySQL 索引进阶指南
    • 解读MySQL Explain关键字
    • 探秘 MySQL 锁:原理与实践
    • 详解MySQL重做日志redolog
    • 详解undoLog在MySQL MVCC中的运用
    • MySQL二进制日志binlog核心知识点
    • MySQL高效插入数据的最佳实践
    • MySQL分页查询优化指南
    • MySQL流式查询的奥秘与应用解析
    • 来聊聊分库分表
    • 来聊聊大厂常用的分布式ID生成方案
  • ElasticSearch

    • 从Lucene到Elasticsearch:进化之路
    • ES 基础使用指南
    • ElasticSearch如何写入一篇文档
    • 深入剖析Elasticsearch文档读取原理
    • 聊聊ElasticSearch性能调优
    • Spring借助Easy-Es操作ES
  • Netty

    • 一文快速了解高性能网络通信框架Netty
    • Netty网络传输简记
    • 来聊聊Netty的ByteBuf
    • 来聊聊Netty消息发送的那些事
    • 解密Netty高性能之谜:NioEventLoop线程池阻塞分析
    • 详解Netty中的责任链Pipeline如何管理ChannelHandler
    • Netty Reactor模型常见知识点小结
    • Netty如何驾驭TCP流式传输?粘包拆包问题全解
    • Netty解码器源码解析
  • 消息队列

    • 一文快速入门消息队列
    • 消息队列RocketMQ入门指南
    • 基于RocketMQ实现分布式事务
    • RocketMQ容器化最佳实践
    • RocketMQ常见问题与深度解析
    • Kafka快速安装与使用指南
  • Nginx

    • Linux下的nginx安装
    • Nginx基础入门总结
    • Nginx核心指令小结
    • Nginx进程结构与核心模块初探
    • Nginx应用进阶HTTP核心模块配置
    • Nginx缓存及HTTPS配置小记
    • nginx高可用实践简记
    • Nginx性能优化
  • 微服务基础

    • 微服务基础知识小结
    • 分布式事务核心概念小结
    • OpenFeign核心知识小结
    • 微服务组件Gateway核心使用小结
    • 分布式事务Seata实践
    • 用 Docker Compose 完成 Seata 的整合部署
  • Nacos

    • Nacos服务注册原理全解析
    • Nacos服务订阅流程全解析
    • Nacos服务变更推送流程全解析
    • 深入解析SpringCloud负载均衡器Loadbalancer
    • Nacos源码环境搭建与调试指南
  • Seata

    • 深度剖析Seata源码
  • Docker部署

    • 一文快速掌握docker的理念和基本使用
    • 使用docker编排容器
    • 基于docker-compose部署微服务基本环境
    • 基于docker容器化部署微服务
    • Gateway全局异常处理及请求响应监控
    • Docker图形化界面工具Portainer最佳实践
  • Go基础

    • 一文带你速通Go语言基础语法
    • 一文快速掌握Go语言切片
    • 来聊聊go语言的hashMap
    • 一文速通go语言类型系统
    • 浅谈Go语言中的面向对象
    • go语言是如何实现协程的
    • 聊聊go语言中的GMP模型
    • 极简的go语言channel入门
    • 聊聊go语言基于epoll的网络并发实现
    • 写给Java开发的Go语言协程实践
  • mini-redis实战

    • 来聊聊我用go手写redis这件事
    • mini-redis如何解析处理客户端请求
    • 实现mini-redis字符串操作
    • 硬核复刻redis底层双向链表核心实现
    • 动手复刻redis之go语言下的字典的设计与落地
    • Go 语言下的 Redis 跳表设计与实现
    • Go 语言版 Redis 有序集合指令复刻探索
  • 项目编排

    • Spring脚手架创建简记
    • Spring脚手架集成分页插件
    • Spring脚手架集成校验框架
    • maven父子模块两种搭建方式简记
    • SpringBoot+Vue3前后端快速整合入门
    • 来聊聊Java项目分层规范
  • 场景设计

    • Java实现文件分片上传
    • 基于时间缓存优化浏览器轮询阻塞问题
    • 基于EasyExcel实现高效导出
    • 10亿数据高效插入MySQL最佳方案
    • 从开源框架中学习那些实用的位运算技巧
  • CI/CD

    • 基于NETAPP实现内网穿透
    • 基于Gitee实现Jenkins自动化部署SpringBoot项目
    • Jenkins离线安装部署教程简记
    • 基于Nexus搭建Maven私服基础入门
    • 基于内网的Jenkins整合gitlab综合方案简记
  • 监控方法论

    • SpringBoot集成Prometheus与Grafana监控
    • Java监控度量Micrometer全解析
    • 从 micrometer计量器角度快速上手promQL
    • 硬核安利一个监控告警开源项目Nightingale
  • Spring AI

    • Spring AI Alibaba深度实战:一文掌握智能体开发全流程
    • Spring AI Alibaba实战:JVM监控诊断Arthas Agent的工程化构建与最佳实践
  • 大模型评测

    • M2.7 真能打!我用两个真实场景测了测,结果有点意外
    • Qoder JetBrains插件评测:祖传代码重构与接口优化实战
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

sharkchili

计算机禅修者
首页
  • Java基础

    • 聊一聊java一些核心知识点
    • 聊聊java面向对象核心知识点
    • 聊聊Java中的异常
    • 聊聊Java中的常用类String
    • 万字长文带你细聊Java注解本质
    • 来聊聊Java的反射机制
    • 深入解析Java泛型的魅力与机制
    • Java集合框架深度解析与面试指南
    • Java常用集合类HashMap深度解析
    • LinkedHashMap源码到面试题的全解析
    • 深入解析CopyOnWriteArrayList的工作机制
    • Java基础IO总结
    • Java三大IO模型小结
    • Java BIO NIO AIO详解
    • Java进阶NIO之IO多路复用详解
    • Java8流式编程入门
    • 一文速通lambda与函数式编程
    • Java8函数式方法引用最佳实践
  • Java并发编程

    • Java并发编程基础小结
    • 深入理解Java中的final关键字
    • 浅谈Java并发安全发布技术
    • 浅谈Java并发编程中断的哲学
    • Java线程池知识点小结
    • 浅谈Java线程池中拒绝策略与流控的艺术
    • synchronized关键字使用指南
    • 深入源码解析synchronized关键字
    • 详解JUC包下的锁
    • 详解并发编程中的CAS原子类
    • LongAdder源码分析
    • AQS源码解析
    • 深入剖析Java并发编程中的死锁问题
    • Java并发容器总结
    • 详解Java并发编程volatile关键字
    • 并发编程ThreadLocal必知必会
    • CompletableFuture基础实践小结
    • CompletableFuture异步多任务最佳实践
    • 硬核详解FutureTask设计与实现
    • 线程池大小设置的底层逻辑与场景化方案
    • 来聊一个有趣的限流器RateLimiter
  • JVM相关

    • 从零开始掌握 JVM
    • JVM核心知识点小结
    • JVM指令集概览:基础与应用
    • JVM类加载器深度解析
    • JVM方法区深度解析
    • Java内存模型JMM详解
    • Java对象大小的精确计算方法
    • 逃逸分析在Java中的应用与优化
    • 从零开始理解JVM的JIT编译机制
    • G1垃圾回收器:原理详解与调优指南
    • JVM故障排查实战指南
    • JVM内存问题排错最佳实践
    • JVM内存溢出排查指南
    • 简明的Arthas使用教程
    • 简明的Arthas配置及基础运维教程
    • 基于Arthas Idea的JVM故障排查与指令生成
    • 基于arthas量化监控诊断java应用方法论与实践
    • 深入剖析arthas技术原理
  • 深入理解Spring框架

    • Spring 核心知识点全面解析
    • Spring核心功能IOC详解
    • Spring AOP 深度剖析与实践
    • Spring 三级缓存机制深度解析
    • 深入 Spring 源码,剖析设计模式的落地实践
    • 探索 Spring 事务的奥秘
    • 深入解析Spring Bean的生命周期管理
    • 解读 Spring Boot 核心知识点
    • Spring Boot 启动优化实战:1分钟到13秒的排查与优化之路
    • Spring Boot自动装配原理及实践
    • 一文快速上手Sharding-JDBC
    • sharding-jdbc如何实现分页查询
    • 基于DynamicDataSource整合分库分表框架Shardingsphere
  • 计算机组成原理

    • 计算机硬件知识小结
    • CPU核心知识点小结
    • 浅谈CPU流水线的艺术
    • 从Java程序员视角聊聊CPU缓存
    • CPU任务调度和伪共享问题小结
    • CPU MESI缓存一致性协议
    • CPU内存管理机制
    • 内存深度解析
    • 磁盘存储原理
    • 详解计算机启动步骤
    • CPU南北桥架构与发展史
    • CPU中断机制与硬件交互详解
  • 操作系统

    • 如何实现一个高性能服务器
    • Linux文件结构与文件权限
    • Linux常见压缩指令小结
    • Linux核心系统调用详解
    • Linux进程管理
    • Linux线程管理
    • 进程与线程深度解析
    • Linux进程间通信机制
    • 零拷贝技术原理与实践
    • CPU缓存一致性问题深度解析
    • IO任务与CPU调度艺术
  • 计算机网络

    • 网卡通信原理详解
    • 网卡数据包处理指南
    • 基于抓包详解TCP协议
  • 编码最佳实践

    • 浅谈现代软件工程TDD最佳实践
    • 浅谈TDD模式下并发程序设计与实现
    • 面向AI编程新范式Trae后端开发环境搭建与实践
    • 基于提示词工程的Redis签到功能开发实践
    • 基于Vibe Coding的Redis分页查询实现
    • 告别AI无效对话:资深工程师的提示词设计最佳实践
  • 实用技巧与配置

    • Mac常用快捷键与效率插件指南
    • Keynote技术科普短视频制作全攻略
  • 写作

    • 写好技术博客的5大核心原则:从认知科学到AI工具的全流程指南
  • 开发工具

    • IDEA配置详解与高效使用指南
  • Nodejs
  • 博客搭建
  • Redis

    • Redis核心知识小结
    • 解锁Redis发布订阅模式
    • 掌握Redis事务
    • Redis主从复制技术
    • Redis的哨兵模式详解
    • 深度剖析Redisson分布式锁
    • 详解redis单线程设计思路
    • 来聊聊Redis所实现的Reactor模型
    • Redis RDB持久化源码深度解析
    • 来聊聊redis的AOF写入
    • 来聊聊Redis持久化AOF管道通信的设计
    • 来聊聊redis集群数据迁移
    • Redis SDS动态字符串深度解析
    • 高效索引的秘密:redis跳表设计与实现
    • 聊聊redis中的字典设计与实现
  • MySQL

    • MySQL基础知识点小结
    • 解读MySQL 索引基础
    • MySQL 索引进阶指南
    • 解读MySQL Explain关键字
    • 探秘 MySQL 锁:原理与实践
    • 详解MySQL重做日志redolog
    • 详解undoLog在MySQL MVCC中的运用
    • MySQL二进制日志binlog核心知识点
    • MySQL高效插入数据的最佳实践
    • MySQL分页查询优化指南
    • MySQL流式查询的奥秘与应用解析
    • 来聊聊分库分表
    • 来聊聊大厂常用的分布式ID生成方案
  • ElasticSearch

    • 从Lucene到Elasticsearch:进化之路
    • ES 基础使用指南
    • ElasticSearch如何写入一篇文档
    • 深入剖析Elasticsearch文档读取原理
    • 聊聊ElasticSearch性能调优
    • Spring借助Easy-Es操作ES
  • Netty

    • 一文快速了解高性能网络通信框架Netty
    • Netty网络传输简记
    • 来聊聊Netty的ByteBuf
    • 来聊聊Netty消息发送的那些事
    • 解密Netty高性能之谜:NioEventLoop线程池阻塞分析
    • 详解Netty中的责任链Pipeline如何管理ChannelHandler
    • Netty Reactor模型常见知识点小结
    • Netty如何驾驭TCP流式传输?粘包拆包问题全解
    • Netty解码器源码解析
  • 消息队列

    • 一文快速入门消息队列
    • 消息队列RocketMQ入门指南
    • 基于RocketMQ实现分布式事务
    • RocketMQ容器化最佳实践
    • RocketMQ常见问题与深度解析
    • Kafka快速安装与使用指南
  • Nginx

    • Linux下的nginx安装
    • Nginx基础入门总结
    • Nginx核心指令小结
    • Nginx进程结构与核心模块初探
    • Nginx应用进阶HTTP核心模块配置
    • Nginx缓存及HTTPS配置小记
    • nginx高可用实践简记
    • Nginx性能优化
  • 微服务基础

    • 微服务基础知识小结
    • 分布式事务核心概念小结
    • OpenFeign核心知识小结
    • 微服务组件Gateway核心使用小结
    • 分布式事务Seata实践
    • 用 Docker Compose 完成 Seata 的整合部署
  • Nacos

    • Nacos服务注册原理全解析
    • Nacos服务订阅流程全解析
    • Nacos服务变更推送流程全解析
    • 深入解析SpringCloud负载均衡器Loadbalancer
    • Nacos源码环境搭建与调试指南
  • Seata

    • 深度剖析Seata源码
  • Docker部署

    • 一文快速掌握docker的理念和基本使用
    • 使用docker编排容器
    • 基于docker-compose部署微服务基本环境
    • 基于docker容器化部署微服务
    • Gateway全局异常处理及请求响应监控
    • Docker图形化界面工具Portainer最佳实践
  • Go基础

    • 一文带你速通Go语言基础语法
    • 一文快速掌握Go语言切片
    • 来聊聊go语言的hashMap
    • 一文速通go语言类型系统
    • 浅谈Go语言中的面向对象
    • go语言是如何实现协程的
    • 聊聊go语言中的GMP模型
    • 极简的go语言channel入门
    • 聊聊go语言基于epoll的网络并发实现
    • 写给Java开发的Go语言协程实践
  • mini-redis实战

    • 来聊聊我用go手写redis这件事
    • mini-redis如何解析处理客户端请求
    • 实现mini-redis字符串操作
    • 硬核复刻redis底层双向链表核心实现
    • 动手复刻redis之go语言下的字典的设计与落地
    • Go 语言下的 Redis 跳表设计与实现
    • Go 语言版 Redis 有序集合指令复刻探索
  • 项目编排

    • Spring脚手架创建简记
    • Spring脚手架集成分页插件
    • Spring脚手架集成校验框架
    • maven父子模块两种搭建方式简记
    • SpringBoot+Vue3前后端快速整合入门
    • 来聊聊Java项目分层规范
  • 场景设计

    • Java实现文件分片上传
    • 基于时间缓存优化浏览器轮询阻塞问题
    • 基于EasyExcel实现高效导出
    • 10亿数据高效插入MySQL最佳方案
    • 从开源框架中学习那些实用的位运算技巧
  • CI/CD

    • 基于NETAPP实现内网穿透
    • 基于Gitee实现Jenkins自动化部署SpringBoot项目
    • Jenkins离线安装部署教程简记
    • 基于Nexus搭建Maven私服基础入门
    • 基于内网的Jenkins整合gitlab综合方案简记
  • 监控方法论

    • SpringBoot集成Prometheus与Grafana监控
    • Java监控度量Micrometer全解析
    • 从 micrometer计量器角度快速上手promQL
    • 硬核安利一个监控告警开源项目Nightingale
  • Spring AI

    • Spring AI Alibaba深度实战:一文掌握智能体开发全流程
    • Spring AI Alibaba实战:JVM监控诊断Arthas Agent的工程化构建与最佳实践
  • 大模型评测

    • M2.7 真能打!我用两个真实场景测了测,结果有点意外
    • Qoder JetBrains插件评测:祖传代码重构与接口优化实战
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Go基础

  • mini-redis实战

    • 来聊聊我用go手写redis这件事
    • mini-redis如何解析处理客户端请求
    • 实现mini-redis字符串操作
    • 硬核复刻redis底层双向链表核心实现
    • 聊聊我说如何用go语言实现redis列表操作
      • 写在文章开头
      • 详解用手写列表指令完成队列操作
        • rpush尾部追加
        • lpop链首取出
        • lrange范围查询
        • lindex精准定位
      • 源码地址
    • 动手复刻redis之go语言下的字典的设计与落地
    • Go 语言下的 Redis 跳表设计与实现
    • Go 语言版 Redis 有序集合指令复刻探索
    • mini-redis复刻Redis的INCR指令
    • 聊聊我基于dict优化mini-redis数据性能这件事
  • Go语言
  • mini-redis实战
sharkchili
2024-10-09
目录

聊聊我说如何用go语言实现redis列表操作

# 写在文章开头

redis的列表支持尾部push和头部pop操作,基于这两个操作我们就可以实现一个简单的队列,所以本文将讲解笔者如何在mini-redis中复刻redis几条列表操作指令从而实现一个简单的无界队列。这里需要补充的是,因为笔者实现的mini-redis是采用go语言实现,为了保证逻辑上的精简更专注于复刻redis的设计理念,笔者底层所实现的列表并没有像原生redis那样基于长度在压缩列表也和双向链表中精准变换,而是统一采用双向链表实现。

mini-redis地址:https://github.com/shark-ctrl/mini-redis (opens new window)

Hi,我是 sharkChili ,是个不断在硬核技术上作死的技术人,是 CSDN的博客专家 ,也是开源项目 Java Guide 的维护者之一,熟悉 Java 也会一点 Go ,偶尔也会在 C源码 边缘徘徊。写过很多有意思的技术博客,也还在研究并输出技术的路上,希望我的文章对你有帮助,非常欢迎你关注我的公众号: 写代码的SharkChili 。

因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。

# 详解用手写列表指令完成队列操作

# rpush尾部追加

rpush指令就是将元素插入到链表的右端,如果我们想基于redis的列表实现队列,就可以用这条指令实现入队操作,相应的我们给出rpush的操作指令示例:

rpush list a 1
1

这条指令的含义就是依次将字符串a和数字1插入的链表的右端,redis服务端在解析这条指令时对应的步骤为:

  1. 查看list的key是否存在,如果存在则判断这个key是否为链表,如果不是则抛出异常,若不存在则在后续的操作中初始化链表。
  2. 从a开始遍历元素,因为a是字符串,所以就将其转为字符串类型的redis对象。
  3. 遍历到1发现是数字,则查看是否大于0且小于10000,如果符合要求则从redis提前分配好的常量池中取出对应数值对象完成参数封装。
  4. 将封装成redis对象的a和1存入list队列中。

对此笔者给出mini-redis的实现,可以看到rpush指令对应的函数为rpushCommand,它会传入客户端指针,该指针所指向的对象我们可以从中获取到要求添加的参数信息,而rpushCommand内部则是调用pushGenericCommand方法,通过REDIS_TAIL标识实现元素追加到链表末端:

func rpushCommand(c *redisClient) {
	/**
	pass in the REDIS_TAIL flag to indicate that 
	the current element should be appended to the tail of the list.
	 */
	pushGenericCommand(c, REDIS_TAIL)
}
1
2
3
4
5
6
7

步入pushGenericCommand即可看到列表判空、类型判断、元素类型转换、最后再存入链表的操作:

func pushGenericCommand(c *redisClient, where int) {
	//check if the corresponding key exists.
	i := lookupKeyWrite(c.db, c.argv[1])
	var lobj *robj
	//if the key exists, then determine if it is a list.
	//if it is not, then throw an error exception.
	if *i != nil && (*i).(*robj).encoding != REDIS_ENCODING_LINKEDLIST {
		addReply(c, shared.wrongtypeerr)
		return
	} else if *i != nil { //if it exists and is a list, then retrieve the Redis object for the list.
		lobj = (*i).(*robj)
	}
	//foreach element starting from index 2.
	var j uint64
	for j = 2; j < c.argc; j++ {
		//call `tryObjectEncoding` to perform special processing on the elements.
		c.argv[j] = tryObjectEncoding(c.argv[j])

		/**
		If the list is empty, then initialize it, create it, and store it in the Redis database.
		*/
		if lobj == nil {
			lobj = createListObject()
			dbAdd(c.db, c.argv[1], lobj)
		}
		/**
		pass in the list pointer, element pointer,
		and add flag to append the element to the head or tail of the list.
		*/
		listTypePush(lobj, c.argv[j], where)
	}
	//return the current length of the list.
	addReplyLongLong(c, (*lobj.ptr).(*list).len)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

为了逻辑的完整性,笔者这里也给出了编码转换的方法tryObjectEncoding,该方法如果发现元素可以转为整数则会按照数值范围从常量池或者手动的方式完成元素对象编码的转换,具体逻辑为:

  1. 如果在0~10000则从shared.integers这个常量数组中获取。
  2. 不在上述范围则手动转为整数并修改编码类型为REDIS_ENCODING_INT。
func tryObjectEncoding(o *robj) *robj {
	var value int64
	var s string
	var sLen int
	//get string value and length
	s = (*o.ptr).(string)
	sLen = len(s)
	/**
	If it can be converted into an integer and is between 0 and 10000,
	it is obtained from the constant pool.
	*/
	if sLen < 21 && string2l(&s, sLen, &value) {
		if value >= 0 && value < REDIS_SHARED_INTEGERS {
			return shared.integers[value]
		} else {
			/**
			If it is not within the scope of constant pool, 
			it will be manually converted into an object of integer encoding type.
			 */
			o.encoding = REDIS_ENCODING_INT
			num := interface{}(value)
			o.ptr = &num
		}
	}

	return o
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# lpop链首取出

完成了尾部追加的动作之后,我们就可以继续实现头部取出的指令lpop来模拟队列出队,整体逻辑为:

  1. 查看传入的key是否存在,如果存在则判断类型是否为链表,如果不是则抛出异常。
  2. 从列表中获取元素的值。
  3. 将该元素从链表从移除。
  4. 如果链表为空则将链表从redis内存中删除。
  5. 返回头节点值。

对此我们给出lpop指令的实现方法lpopCommand,可以看到其内部传入REDIS_HEAD给popGenericCommand告知要抛出链表首部元素:

func lpopCommand(c *redisClient) {
	// params is REDIS_HEAD, which means to retrieve the head element.
	popGenericCommand(c, REDIS_HEAD)
}
1
2
3
4

而内部获取链表头部元素就是我们所说的:

  1. 查询链表是否存在。
  2. 如果存在对类型进行校验,
  3. 根据where标识获取链表首或者尾部的元素。
  4. 返回链表取出的元素给客户端。
  5. 判断此时链表是否为空,若为空则删除redis数据库中的链表。
func popGenericCommand(c *redisClient, where int) {
	//check if the key exists, and if it doesn't, respond with an empty response.
	o := lookupKeyReadOrReply(c, c.argv[1], shared.nullbulk)
	r := (*o).(*robj)
	//If the type is not a linked list, throw an exception and return.
	if o == nil || checkType(c, r, REDIS_LIST) {
		return
	}

	value := listTypePop(r, where)
	//retrieve the first element of the linked list based on the WHERE identifier.
	if value == nil {
		addReply(c, shared.nullbulk)
	} else {
		/**
		return the element value, and check if the linked list is empty.
		If it is empty, delete the key-value pair in the Redis database.
		*/
		addReplyBulk(c, value)
		if listTypeLength(r) == 0 {
			dbDelete(c.db, c.argv[1])
		}
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

因为笔者的列表是通过双向链表实现的,所以这里就给出REDIS_ENCODING_LINKEDLIST 编码的实现,链表的处理逻辑就是根据标识获取首或尾的节点,然后将该节点从链表中删除:

func listTypePop(subject *robj, where int) *robj {
	var value *robj
	if subject.encoding == REDIS_ENCODING_ZIPLIST {
		//todo
	} else if subject.encoding == REDIS_ENCODING_LINKEDLIST {
		lobj := (*subject.ptr).(*list)
		var ln *listNode
		//get the head or tail element based on the linked list identifier.
		if where == REDIS_HEAD {
			ln = lobj.head
		} else {
			ln = lobj.tail
		}
		//read the value of the element and remove it from the linked list.
		value = (*ln.value).(*robj)
		listDelNode(lobj, ln)
	} else {
		log.Panic("Unknown list encoding")
	}
	return value
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# lrange范围查询

有了上述的操作指令我们就可以从链表中读取元素,如果我们希望读取链表中所有元素,我们可以键入下面这段lrange 指令:

lrange list 0 -1
1

该操作指明要查询的链表为list,从0开始获取到倒数第一个元素的元素,之所以-1可以理解为倒数第一个元素,因为lrange 对应的函数遇到end值为负数会让其加上链表长度。 假设我们链表长度为3,加上-1就得到2,最终得到的指令就等同于lrange list 0 2,由此就可以拿到链表中的所有元素:

基于这个思路我们给出lrangeCommand的实现,内部逻辑比较简单:

  1. 将索引2、3转为整数,获取查询的起止位置。
  2. 起止位置负数取正以及上限范围界定,例如end为-1则加上链表长度获取到最终的end值。
  3. 如果start小于0则设置为0。
  4. 基于上述计算后对start和end值进行边界值校验。
  5. 通过start获取起始位置开始遍历指定范围内的元素范围。
func lrangeCommand(c *redisClient) {
	var o *robj
	var start int64
	var end int64
	var llen int64
	var rangelen int64
	/**
	convert the strings at indexes 2 and 3 to numerical values.
	If an error occurs, respond with an exception and return.
	*/
	if !getLongFromObjectOrReply(c, c.argv[2], &start, nil) ||
		!getLongFromObjectOrReply(c, c.argv[3], &end, nil) {
		return
	}
	/**
	check if the linked list exists,
	and if it doesn't, respond with a null value.
	*/
	val := lookupKeyReadOrReply(c, c.argv[1], shared.emptymultibulk)
	o = (*val).(*robj)
	/**
	check if the type is a linked list; if it is not, return a type error.
	*/
	if o == nil || !checkType(c, o, REDIS_LIST) {
		return
	}
	//get the start and end values of a range query. If they are negative, add the length of the linked list.
	llen = (*o.ptr).(*list).len
	if start < 0 {
		start += llen
	}

	if start < 0 {
		start += llen
	}

	if end < 0 {
		end += llen
	}
	//if start is still less than 0, set it to 0.
	if start < 0 {
		start = 0
	}
	/**
	check if start is greater than the length of the linked list or if start is greater than end.
	If either of these exceptions occurs, respond with an error.
	*/
	if start >= llen || start > end {
		addReplyError(c, shared.emptymultibulk)
		return
	}
	//if end is greater than list length, set it to the list length.
	if end > llen {
		end = llen - 1
	}

	rangelen = end - start + 1
	addReplyMultiBulkLen(c, rangelen)

	if o.encoding == REDIS_ENCODING_ZIPLIST {
		//todo
	} else if o.encoding == REDIS_ENCODING_LINKEDLIST {
		lobj := (*o.ptr).(*list)
		node := listIndex(lobj, start)
		//foreach the linked list starting from "start" based on "rangelen."
		for rangelen > 0 {
			rObj := (*node.value).(*robj)
			addReplyBulk(c, rObj)
			node = node.next
			rangelen--
		}

	} else {
		log.Panic("List encoding is not LINKEDLIST nor ZIPLIST!")
	}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77

# lindex精准定位

当我们需要获取元素中某个索引位置元素时。就可以通过lindex指令,例如获取list第0个元素:

lindex list 0
1

实现精准定位的逻辑比较简单,将索引2的参数转为整数到链表中遍历获取到该索引位置元素返回即可,对应的笔者这里也给出的自己的实现:

  1. 查询列表是否存在。
  2. 索引2参数转整数。
  3. 调用listIndex获取对应元素。
  4. 读取其value值响应给客户端。
func lindexCommand(c *redisClient) {
	/**
	check if the linked list exists; if it doesn't, return empty.
	*/
	i := lookupKeyReadOrReply(c, c.argv[1], shared.nullbulk)
	r := (*i).(*robj)
	//verify if the type is a linked list.
	if *i == nil || checkType(c, r, REDIS_LIST) {
		return
	}

	if r.encoding == REDIS_ENCODING_ZIPLIST {
		//todo
	} else if r.encoding == REDIS_ENCODING_LINKEDLIST {
		/**
		retrieve the parameter at index 2 to obtain the index position,
		then fetch the element from the linked list at that index and return it.
		*/
		lobj := (*r.ptr).(*list)
		s := (*c.argv[2].ptr).(string)
		idx, _ := strconv.ParseInt(s, 10, 64)
		ln := listIndex(lobj, idx)

		if ln != nil {
			value := (*ln.value).(*robj)
			addReplyBulk(c, value)
		} else {
			addReply(c, shared.nullbulk)
		}
	} else {
		log.Panic("Unknown list encoding")
	}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

# 源码地址

自此,笔者将个人开源项目mini-redis对于列表的核心指令复刻完成,希望对你阅读笔者的项目源码有帮助。

mini-redis地址:https://github.com/shark-ctrl/mini-redis (opens new window)

我是 sharkchili ,CSDN Java 领域博客专家,mini-redis的作者,我想写一些有意思的东西,希望对你有帮助,如果你想实时收到我写的硬核的文章也欢迎你关注我的公众号: 写代码的SharkChili 。 因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。

编辑 (opens new window)
上次更新: 2026/03/26, 01:05:31
硬核复刻redis底层双向链表核心实现
动手复刻redis之go语言下的字典的设计与落地

← 硬核复刻redis底层双向链表核心实现 动手复刻redis之go语言下的字典的设计与落地→

最近更新
01
基于EasyExcel实现高效导出
03-25
02
从开源框架中学习那些实用的位运算技巧
03-25
03
浅谈分布式架构设计思想和常见优化手段
03-25
更多文章>
Theme by Vdoing | Copyright © 2025-2026 Evan Xu | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×
×