LinkedList源码解析

news2024/11/19 17:43:17

LinkedList源码解析

简介

LinkedList 是一个双向链表(内部是 Node 节点)实现的 List,并且还实现了 Deque 接口,它除了作为 List 使用,还可以作为队列或者来使用。

这样看来,LinkedList 简直就是个全能冠军。当你需要使用栈或者队列时,可以考虑使用 LinkedList,一方面是因为 Java 官方已经声明不建议使用 Stack 类,更遗憾的是,Java 里根本没有一个叫做 Queue 的类(它是个接口名字)。

关于栈或队列,现在的首选是 ArrayDeque,它有着比 LinkedList(当作栈或队列使用时)有着更好的性能。

image-20221129212840604

继承体系

image-20221129212705220

通过继承体系,我们可以看到 LinkedList 不仅实现了 List 接口,还实现了 Queue 和 Deque 接口,所以它既能作为 List 使用,也能作为双端队列使用,当然也可以作为栈使用。

LinkedList 实现了 Cloneable 和 Serializable 接口,说明其可以被克隆,也可以被序列化。

同样的,LinkedList 被克隆的时候,和 ArrayList 一样,二者均是浅拷贝。

源码解析

属性

	// 元素个数
	transient int size = 0;
	
	// 双向链表的尾结点
    transient Node<E> first;
	
	// 双向链表的头结点
    transient Node<E> last;

属性很简单,定义了元素个数 size 和链表的首尾节点。

Node内部类

  • 典型的双链表结构。
    private static class Node<E> {
        E item; // data
        Node<E> next; // 前驱节点
        Node<E> prev; // 后继节点
        
        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

构造方法

空参

   public LinkedList() {
   }

传入一个Collection集合

    public LinkedList(Collection<? extends E> c) {
        this();
        // 将c中的每一个元素构造成Node插入到链表中
        addAll(c);
    }
			     || 
			     \/ 
    public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
    }
				   ||
				   \/
	/*
	 * 从指定的索引位置开始插入一个Collection
	 */
    public boolean addAll(int index, Collection<? extends E> c) {
       	// 检查index是否越界,index >= 0 && index <= size
        checkPositionIndex(index);
		// 将c转换为数组,并获取其长度
        Object[] a = c.toArray();
        int numNew = a.length;
        if (numNew == 0)
            return false;
        /*
         * pred 指定index位置的Node节点的前驱节点
         * succ 存储index位置的Node节点,插入完毕后拼接链表时要使用到这个节点
         */
        Node<E> pred, succ;
        // index == size 说明当前插入位置就是在链表的末尾开始
        if (index == size) {
            // succ置为null,因为直接在链表末尾插入时插入完毕不需要拼接
            succ = null;
            // pred直接指向尾结点即可
            pred = last;
        // index != size 说明此时就是在链表中插入
        } else {
            // succ存index位置的元素
            succ = node(index);
            // pred指向index的前驱节点
            pred = succ.prev;
        }
		
        // 遍历c中的每一个元素,依次进行插入。
        for (Object o : a) {
            @SuppressWarnings("unchecked") E e = (E) o;
            // 构造节点,前驱节点就是pred
            Node<E> newNode = new Node<>(pred, e, null);
            // pred为null,说明链表为空,此时将first指向newNode
            if (pred == null)
                first = newNode;
            // 非空链表,将pred.next指向newNode
            else
                pred.next = newNode;
            // pred向后移动
            pred = newNode;
        }
                       
        // succ == null,即上面的if判断是 -> 当前是在链表末尾插入元素
        if (succ == null) {
            // 直接将last指向新的尾结点即可
            last = pred;
        // 非在链表末尾插入元素
        } else {
            /*
             * 此时pred指向的是尾结点
             * succ指向的是原index位置的节点
             * 此时需要做的事就是将两部分链表连接起来
             */
            pred.next = succ;
            succ.prev = pred;
        }
        // 当前元素个数 + numNew
        size += numNew;
        // 链表修改次数+1 
        modCount++;
        return true;
    }	

核心方法

添加元素

linkLast(E e)

	/*
	 * 此方法程序员无法调用,供程序员使用的add()/addLast()方法内部调用的就是此方法。
	 * 作用:在链表末尾添加节点。
	 */
	void linkLast(E e) {
        // l指向尾结点
        final Node<E> l = last;
        // 构造newNode,newNode的前驱指向尾结点。
        final Node<E> newNode = new Node<>(l, e, null);
        // 尾结点指向newNode
        last = newNode;
        // l == null 说明当前链表中没有元素
        if (l == null)
            // 将first也指向newNode
            first = newNode;
        // 原尾结点的next指向newNode成功添加
        else
            l.next = newNode; 
        size++;
        // modCount+1 表示修改了一次链表结构
        modCount++;
    }

linkFirst(E e)

    /*
	 * 此方法程序员无法调用,供程序员使用的addFirst()方法内部调用的就是此方法。
	 * 作用:将新添加的节点作为链表头部。
	 */
	private void linkFirst(E e) {
        // f指向原头结点
        final Node<E> f = first;
        // 创建新节点,前驱设置为null
        final Node<E> newNode = new Node<>(null, e, f);
        // first指向newNode,即newNode是当前的头结点
        first = newNode;
        // f == null,说明原链表中没有节点
        if (f == null)
            // 此时向链表中添加一个元素后,链表中有一个元素,所以将last也指向newNode。
            last = newNode;
        else
            // 将原头结点的前驱指向newNode。
            f.prev = newNode;
        size++;
        // modCount+1 表示修改了一次链表结构
        modCount++;
    }

add() & offer()

   	/*
   	 * 在链表尾部添加元素
   	 */
	public boolean add(E e) {
        // 调用的是linkLast()
        linkLast(e);
        return true;
    }

// ------------------------------------------------------------
	/*
	 * 在指定索引位置前插入Node。
	 */
    public void add(int index, E value) {
        // 检查索引是否越界
        checkPositionIndex(index);
        if (index == size)
            linkLast(element);
        else
            // 将value构造成Node插入到index位置Node的前面。
            linkBefore(element, node(index));
    }
			    ||
 			    \/
	/*
	 * @param succ,要在succ位置前面插入一个Node。
	 */
    void linkBefore(E e, Node<E> succ) {
        // 获取succ的前驱
        final Node<E> pred = succ.prev;
        // 构造一个Node,前驱是pred,后继是succ
        final Node<E> newNode = new Node<>(pred, e, succ);
		// succ的前驱指向newNode
        succ.prev = newNode;
        if (pred == null)
            first = newNode;
        // 一般情况 pred的后继指向newNode 完成插入操作。
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

// ------------------------------------------------------------
	/*
   	 * 在链表尾部添加元素
   	 */
	public boolean offer(E e) {
    	// 调用add()
        return add(e);
    }

addFirst() & push() & offerFirst()

    /*
     * 头部添加元素
     */
	public void addFirst(E e) {
        // 调用的是linkFirst()
        linkFirst(e);
    }

// --------------------------------------------
   public void push(E e) {
        // 调用addFirst()
        addFirst(e);
   }

// --------------------------------------------
    public boolean offerFirst(E e) {
        // 调用addFirst()
        addFirst(e);
        return true;
    }

addLast() & offerLast()

	/*
	 * 尾部添加元素
	 */
	public void addLast(E e) {
        // 调用的是linkLast()
        linkLast(e);
    }

// --------------------------------------------
	public boolean offerLast(E e) {
        // 调用addLast()
        addLast(e);
        return true;
    }

在中间添加元素的方法也很简单,典型的双链表在中间添加元素的方法。

添加元素的三种方式大致如下图所示:

image-20221201213454154

在队列首尾添加元素很高效,时间复杂度为 O ( 1 ) O(1) O(1)

在中间添加元素比较低效,首先要先找到插入位置的节点,再修改前后节点的指针,时间复杂度为 O ( n ) O(n) O(n)

查看元素

peek()

    /*
     * 查看头结点
     */
	public E peek() {
        // 获取头结点
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }

peekFirst()

	/*
	 * 查看头结点
	 */     
	public E peekFirst() {
        // 获取头结点
        final Node<E> f = first;
        return (f == null) ? null : f.item;
     }

peekLast()

    /*
	 * 查看尾结点
	 */  
	public E peekLast() {
        // 获取尾结点
        final Node<E> l = last;
        return (l == null) ? null : l.item;
    }

删除元素

unlinkFirst(Node<E> f)

	/*
	 * 删除元素的核心方法,外部无法调用。
	 * 能进入这个方法的前提就是 (f为头结点 && f != null)
	 * 此方法作用:将头结点删除。
	 */
	private E unlinkFirst(Node<E> f) {
        // 获取头结点的value,最后返回。
        final E element = f.item;
        // next是f的后继节点
        final Node<E> next = f.next;
        // 将头结点f的value置为null,便于GC。
        f.item = null;
        // 将头结点的后继置为null
        f.next = null; // help GC
        // 头结点指向后继
        first = next;
       	/*
       	 * next后继为null,说明原链表中只有一个节点,此时删除之后链表中没有节点了,所以将last指向null
       	 */	
        if (next == null)
            last = null;
        else
            // 普通情况,直接将next的前驱置为null,即彻底断开原头结点。
            next.prev = null;
        size--;
        modCount++;
        // 最终将原头结点的value返回。
        return element;
    }

unlinkLast(Node<E> l)

	/*
	 * 删除元素的核心方法,外部无法调用。
	 * 能进入这个方法的前提就是 (l为尾结点 && l != null)
	 * 此方法作用:将尾结点删除。 
	 */
	private E unlinkLast(Node<E> l) {
        // assert l == last && l != null;
        final E element = l.item;
        // 获取尾结点的前驱节点
        final Node<E> prev = l.prev;
        // 将尾结点的value和前驱节点置为null
        l.item = null;
        l.prev = null; // help GC
        // 将last指向尾结点的前驱节点
        last = prev; 
        // prev == null,说明删除尾结点后链表为null,此时需要将first也置为null。
        if (prev == null)
            first = null;
        else
            // 普通情况,将前驱节点的后继置为null,彻底断开尾结点
            prev.next = null;
        size--;
        modCount++;
        // 返回原尾结点的value。
        return element;
    }

removeFirst() & pollFirst() & pop() & poll()

    /*
     * 删除头结点
     */
	public E removeFirst() {
        final Node<E> f = first;
        // 没有元素,抛出异常
        if (f == null)
            throw new NoSuchElementException();
        // 调用unlinkFirst() 删除头结点
        return unlinkFirst(f);
    }

// ------------------------------------------------
    public E pop() {
    	// 调用removeFirst()删除头结点
        return removeFirst();
    }

// ------------------------------------------------
    public E pollFirst() {
    	final Node<E> f = first;
        // 调用unlinkFirst() 删除头结点
        return (f == null) ? null : unlinkFirst(f);
    }

// ------------------------------------------------
    public E poll() {
        final Node<E> f = first;
        // 调用unlinkFirst() 删除头结点
        return (f == null) ? null : unlinkFirst(f);
    }

removeLast() & pollLast()

    /*
     * 删除尾结点 
     */
	public E removeLast() {
        final Node<E> l = last;
        // 没有元素,抛出异常
        if (l == null)
            throw new NoSuchElementException();
        // 调用unlinkLast() 删除尾结点
        return unlinkLast(l);
    }

// --------------------------------------------
    public E pollLast() {
        final Node<E> l = last;
        // 调用unlinkLast()删除尾结点
        return (l == null) ? null : unlinkLast(l);
    }

remove(int index)

	/*
	 * 删除指定索引位置的Node。
	 */    
	public E remove(int index) {
        // 检查索引
        checkElementIndex(index);
        // 遍历找到index位置的Node,然后将其删除。
        return unlink(node(index));
    }
			    ||
 			    \/
// --------------------------------------------
	E unlink(Node<E> x) {
        // assert x != null;
        final E element = x.item;
        // 获取x节点的后继
        final Node<E> next = x.next;
        // 获取x节点的前驱
        final Node<E> prev = x.prev;
		// 如果前驱节点为空 则说明当前元素是链表的头结点 直接将first指向当前节点的后继节点
        if (prev == null) {
            first = next;
        } else { // 前驱节点不为空
            // x.prev.next = x.next 变相的删除x节点
            prev.next = next;
            // 断开x的前驱节点 彻底删除x
            x.prev = null;
        }
		// 如果后继节点为空 则说明当前元素是链表的尾结点 直接将last指向当前节点的前驱节点
        // 内部逻辑与上面一样
        if (next == null) {
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }
		// 将当前元素置为空 help GC
        x.item = null;
        size--;
        modCount++;
        return element;
    }

删除元素的三种方法都是典型的双链表删除元素的方法,大致流程如下图所示。

image-20221201213617929

在队列首尾删除元素很高效,时间复杂度为 O ( 1 ) O(1) O(1)

在中间删除元素比较低效,首先要找到删除位置的节点,再修改前后指针,时间复杂度为 O ( n ) O(n) O(n)

获取元素

getFirst()

   	 /*
   	  * 获取头结点的value值
   	  */
	 public E getFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return f.item;
    }

getLast()

    /*
     * 获取尾节点的value值
     */
	public E getLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return l.item;
    }

get()

    /*
     * 获取指定索引位置节点的value。
     */
	public E get(int index) {
        // 检查索引是否越界(index >= 0 && index < size)
        checkElementIndex(index); 
        return node(index).item;
    }

// -----------------------------------------------------------
	/*
	 * 遍历链表返回指定位置的Node。
	 */
	Node<E> node(int index) {
        // 这里判断要查找的Node是在链表的前半部分,从头节点向后找
        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        // 要查找的Node在链表的后半部分,从尾结点向前找
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

常用方法

set()

    /*
     * 修改指定索引位置节点的value。
     */
	public E set(int index, E element) {
        // 检查索引是否越界
        checkElementIndex(index);
        // 获取index位置的Node
        Node<E> x = node(index);
        // 获取value
        E oldVal = x.item;
        // 修改value
        x.item = element;
        // 返回原value
        return oldVal;
    }

indexOf()

    /*
     * 从链表的头结点开始找,找到指定value的Node第一次出现的索引。
     */	
	public int indexOf(Object o) {
        int index = 0;
        if (o == null) {
            // 从头开始遍历链表
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null)
                    return index;
                index++;
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item))
                    return index;
                index++;
            }
        }
        // 不存在,返回-1
        return -1;
    }

contains()

	/*
	 * 判断链表中是否存在指定的value的Node
	 * 底层调用的是indexOf() 
	 */  		
	public boolean contains(Object o) {
        // indexOf()返回-1说明链表中没有指定value的Node
        return indexOf(o) >= 0;
    }

size()

    /*
     * 返回链表中的元素个数
     */
	public int size() {
        return size; // 直接返回size
    }

栈操作

前面我们说了,LinkedList 是双端队列,还记得双端队列可以作为栈使用吗?

    public void push(E e) {
        addFirst(e);
    }

    public E pop() {
        return removeFirst();
    }

栈的特性是 LIFO(Last In First Out),所以作为栈使用也很简单,添加删除元素都只操作队列首节点即可。

总结

(1)LinkedList 是一个以 双向链表 实现的 List;

(2)LinkedList 还是一个双端队列,具有 队列双端队列 的特性;

(3)LinkedList 在队列首尾添加、删除元素非常高效,时间复杂度为 O ( 1 ) O(1) O(1)

(4)LinkedList 在中间添加、删除元素比较低效,时间复杂度为 O ( n ) O(n) O(n)

(5)LinkedList 不支持随机访问,所以访问非队列首尾的元素比较低效;

(6)LinkedList 在功能上等于 ArrayList + ArrayDeque;




参考文章

  • 彤哥读源码_死磕 java集合之LinkedList源码分析
  • shstart7_LinkedList源码解析
  • Java 全栈知识体系_Collection - LinkedList源码解析

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/82107.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【文字版】津津有味:感染新冠后没食欲,那咱也得吃饭啊!

点击文末“阅读原文”即可收听本期节目剪辑、音频 / 朱峰 编辑 / 姝琦 SandLiu监制 / 姝琦 文案 / 粒粒 产品统筹 / bobo你阳了吗&#xff1f;你嗓子疼吗&#xff1f;你的食欲还好吗&#xff1f;没有什么比好好吃饭更重要&#xff0c;生病的时候尤其是。营养对健康而言是预防…

浏览器上的Cookies有什么用?超级浏览器防关联如何实现?

Cookies是浏览器的指纹信息之一&#xff0c;它是一种文本文件&#xff0c;是网站为了辨别用户身份&#xff0c;对用户进行记录并由户客户端计算机进行暂时或永久保存的信息。一般情况下&#xff0c;网站对浏览器的指纹的记录主要是依靠Cookies来实现的。因为超级浏览器来可以生…

[附源码]JAVA毕业设计英语网站(系统+LW)

[附源码]JAVA毕业设计英语网站&#xff08;系统LW&#xff09; 项目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xf…

[附源码]Python计算机毕业设计大学生健康系统Django(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

c#和Python交互,完美解决Python调用OpenCV等第三方库以及分发时需配置python环境的问题

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言一、问题分析二、解决方案第一个问题第二个问题三、结果及源码四、总结前言 关于C#如何调用Python&#xff0c;网上提供了很多解决方案&#xff0c;有用ironPyt…

react组件深度解读

五、React 核心是组件 在 React 中&#xff0c;我们使用组件&#xff08;有状态、可组合、可重用&#xff09;来描述 UI 。 在任何编程语言中&#xff0c;你都可以将组件视为简单的函数。 React 组件也一样&#xff0c; 它的输入是 props&#xff0c;输出是关于 UI 的描述。我…

Win11 WSL Linux子系统安装与注销 配置conda环境 启动jupyter

1 前言 本篇博客讲解如何在Windows11系统中安装与注销Linux子系统&#xff0c;并配置conda环境、jupyter环境&#xff0c;实现在Local浏览器启动jupyter并运行项目。 2 安装Linux子系统&#xff08;参考文章[1]&#xff09; 1.1 WSL 在任务栏中的搜索功能中&#xff0c;搜索…

合并多文件后分组再结构化

【问题】 Heres the problem statement: In a folder in HDFS, therere a few csv files with each row being a record with the schema (ID, attribute1, attribute2, attribute3). Some of the columns (except ID) could be null or empty strings, and no 2 records wi…

汇编语言常用DOS功能调用示例

1.利用DOS功能调用输出响铃&#xff08;响铃的ASCII码为07H&#xff09;。建立源程序文件HELLO.ASM&#xff0c;通过汇编程序得到目标文件RING.OBJ以及列表文件RING.LST&#xff0c;通过连接程序得到可执行文件性文件 RING.EXE。对可执行性文件进行调试。 &#xff08;1&…

【数据结构】——栈和队列

目录 1.栈 1.1栈的概念及结构 1.2栈的实现 1.2.1具体实现 Stack.h 栈初始化 栈销毁 入栈 出栈 取栈顶数据 判空 栈中有效元素的个数 全部Stack.c的代码 测试Test.c代码 2.队列 2.1队列的概念及结构 2.2队列的实现 Queue.h 队列初始化 队列销毁 队尾入队列…

Tomcat8.0使用tomcat-redis-session-manager共享session【开源】,tomcat实现session共享

前言 【可跳过&#xff0c;比较简单】 由于以前的项目配置了多个tomcat服务使用了nginx代理&#xff0c;但是关闭某个tomcat的时候登录用户信息丢失&#xff0c;用户得重新登录&#xff0c;这就让人体验不好了&#xff1b;我们可以复制各个tomcat服务的session来实现的sessio…

【供给需求优化算法】基于适应度-距离-平衡供给需求优化算法FDB-SDO附matlab代码

​✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法…

毕设选题推荐基于python的django框架的学生课程管理系统

&#x1f496;&#x1f525;作者主页&#xff1a;计算机毕设老哥&#x1f525; &#x1f496; 精彩专栏推荐订阅&#xff1a;在 下方专栏&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; Java实战项目专栏 Python实…

前端二面常见手写面试题(必备)

用正则写一个根据name获取cookie中的值的方法 function getCookie(name) {var match document.cookie.match(new RegExp((^| ) name ([^;]*)));if (match) return unescape(match[2]); }获取页面上的cookie可以使用 document.cookie 这里获取到的是类似于这样的字符串&…

新一代推理部署工具FastDeploy与十大硬件公司联合打造:产业级AI模型部署实战课...

人工智能产业应用发展的越来越快&#xff0c;开发者需要面对的适配部署工作也越来越复杂。层出不穷的算法模型、各种架构的AI硬件、不同场景的部署需求、不同操作系统和开发语言&#xff0c;为AI开发者项目落地带来极大的挑战。为了解决AI部署落地难题&#xff0c;我们发布了新…

(附源码)springboot校园跳蚤市场 毕业设计 646515

基于Springboot校园跳蚤市场 摘 要 科技进步的飞速发展引起人们日常生活的巨大变化&#xff0c;电子信息技术的飞速发展使得电子信息技术的各个领域的应用水平得到普及和应用。信息时代的到来已成为不可阻挡的时尚潮流&#xff0c;人类发展的历史正进入一个新时代。现代社会越来…

Python解题 - CSDN周赛第14期 - 单词编码

本期其实没啥好写的&#xff0c;都是数学题&#xff0c;和算法关系不大&#xff0c;唯手熟尔。而且又出现了同一天的每日一练中包含了赛题&#xff0c;这算不算官方泄题呢&#xff1f;看来下次在竞赛之前先做完每日一练大有益处呢。 第一题&#xff1a;字符串全排列 对K个不同字…

算法leetcode|21. 合并两个有序链表(rust重拳出击)

文章目录21. 合并两个有序链表&#xff1a;样例 1&#xff1a;样例 2&#xff1a;样例 3&#xff1a;提示&#xff1a;原题传送门&#xff1a;分析&#xff1a;题解&#xff1a;rustgoccpythonjava21. 合并两个有序链表&#xff1a; 将两个升序链表合并为一个新的 升序 链表并…

一款基于SpringBoot+layui 开源的固定设备资产管理系统源码 源码免费分享

淘源码&#xff1a;国内专业的免费源码下载平台 分享一款开源的固定设备资产管理系统源码&#xff0c;系统可对常用资产设备进行信息化管理&#xff0c;包含自定义支持各类设备、自带导入导出、维护工作统计、采购管理、文档管理、合同管理等功能&#xff0c;包含对资产的登记、…

如何优雅的设计和使用缓存?

背景 在之前的文章中你应该知道的缓存进化史介绍了爱奇艺的缓存架构和缓存的进化历史。俗话说得好&#xff0c;工欲善其事&#xff0c;必先利其器&#xff0c;有了好的工具肯定得知道如何用好这些工具&#xff0c;本篇将介绍如何利用好缓存。 1.确认是否需要缓存 在使用缓存…