LinkedList源码解析及LRU算法解析

LinkedList源码解析

简介

LinkedList 类
优点:尾插效率高,插入删除时间一致的,数据移动快
缺点:随机访问效率慢
在这里插入图片描述

public class LinkedList<E>
        extends AbstractSequentialList<E>
        implements List<E>, Deque<E>, Cloneable, java.io.Serializable {
    transient int size = 0;
    transient Node<E> first;
    transient Node<E> last;
    }

依赖关系

LinkedList依赖关系如下图
LinkedList

常用方法

1:add方法

add方法有很四个,咱们一个一个来分析,第一个尾插add方法
add(E e):

  public boolean add(E e) {
        linkLast(e);
        return true;
    }
  void linkLast(E e) {
        //取出最后的节点数据
        final Node<E> l = last;
        //新节点在last节点后面
        final Node<E> newNode = new Node<>(l, e, null);
        //newNode变为最后节点
        last = newNode;
//如果没有最后的节点(没有最后一个肯定也没有第一个),第一个节点变为newNode
        if (l == null)
            first = newNode;
        else
//有最后一个节点的话,就把newNode变为最后一个节点,绑定在之前last的后面
            l.next = newNode;
        //长度+1
        size++;
        //修改次数+1
        modCount++;
    }

指定位置插入,主要有三个方法,linkLast()尾插方法,node(),linkBefore()。
add(int index, E element):

    public void add(int index, E element) {
        //检验index是否越界
        checkPositionIndex(index);
        //如果index与size相同,代表要在最后一位插入,代表则用尾插
        if (index == size)
        //与上一个一致,见上面解析
            linkLast(element);
        else
            linkBefore(element, node(index));
    }
    //查询指定位置的node节点元素
        Node<E> node(int index) {
        // assert isElementIndex(index);
//判断插入的节点在上半部分还是下半部分
        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }
    //主要进行节点替换操作
        void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        //取出下标对应的节点的上一个节点对象
        final Node<E> pred = succ.prev;
        //把下标对应节点设置为newNode,更新上一个与下一个
        final Node<E> newNode = new Node<>(pred, e, succ);
        //上个节点的数据设置为newNode
        succ.prev = newNode;
        //下标对应的节点对象的上一个对象,如果不存在,则上一个设置为newNode;
        //不存在则为第一个节点,所以设置第一个节点为newNode
        if (pred == null)
            first = newNode;
        else
            //下标对应的节点的下一个节点对象设置为newNode
            pred.next = newNode;
        //长度+1
        size++;
        //修改次数+1
        modCount++;
    }

addFirst:头部插入

 public void addFirst(E e) {
        linkFirst(e);
    }
       private void linkFirst(E e) {
        //新的节点指向第一个节点
        final Node<E> f = first;
        //没有上一个节点,设置当前节点,设置后一个节点
        final Node<E> newNode = new Node<>(null, e, f);
        //第一个节点设置为newNode
        first = newNode;
        //如果此前没有第一个节点,则最后一个节点设置为newNode;因为只有
        if (f == null)
            last = newNode;
        else
            //如果此前有第一个节点,则上一个节点设置为newNode;因为链表每个数据要插入头部
            f.prev = newNode;
        //长度+1
        size++;
        //修改次数+1
        modCount++;
    }

addLast方法:

   public void addLast(E e) {
   		//上面已经描述过了
        linkLast(e);
    }

2:remove方法

remove方法有五种,removeFirst,removeLast,remove(Object o),remove(int index),remove()
remove(int index):删除指定位置数据,返回删除数据

  public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }
     E unlink(Node<E> x) {
        // assert x != null;
        //指定元素的对象
        final E element = x.item;
        //指定元素的下一个对象
        final Node<E> next = x.next;
        //指定元素的上一个对象
        final Node<E> prev = x.prev;
        //上一个元素是null,则first设置为指定元素的下一个,因为指定元素要删除,上一个也没,自己也没,只能下一个变为第一。
        if (prev == null) {
            first = next;
        } else {
            //上一个元素的下一个变为自己的下一个,因为自己要被删除
            prev.next = next;
            //自己制为null
            x.prev = null;
        }
        //如果指定对象的下一个为null,说明是最后一个元素,自己被删除,last变为自己的上一个元素
        if (next == null) {
            last = prev;
        } else {
            //下一个元素的上一个指向自己的上一个,因为本来指向自己的,自己没了
            next.prev = prev;
            //自己的下一个设为null,自己都没了,自己的上一个下一个都没意义了
            x.next = null;
        }
        //自己设为null
        x.item = null;
        //长度-1
        size--;
        //变更次数+1
        modCount++;
        return element;
    }

remove(Object o):删除指定对象,只删除一个

    public boolean remove(Object o) {
        //循环找出要删除的对象所处的节点,然后删除,只删除第一个
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null) {
                    unlink(x);
                    return true;
                }
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item)) {
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }

remove():删除链表头部数据,并返回该数据

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

removeFirst():删除链表头部数据,并返回该数据(小问号,你是否有很多的朋友)

     public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }

removeLast():删除并返回最后一个元素

 public E removeLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return unlinkLast(l);
    }
        private E unlinkLast(Node<E> l) {
        // assert l == last && l != null;
        //最后一个元素的数据
        final E element = l.item;
        //最后一个元素的数上一个节点
        final Node<E> prev = l.prev;
        //清除自己个自己的上一个
        l.item = null;
        l.prev = null; // help GC
        //最后一个变为自己的上一个
        last = prev;
        //没有上一个,则last=first,第一个为null
        if (prev == null)
            first = null;
        else
            //上一个节点的下一个制为null
            prev.next = null;
        //长度-1
        size--;
        //更改次数+1
        modCount++;
        return element;
    }

3:set方法

set(int index, E element):替换指定节点的数据

   public E set(int index, E element) {
        //校验下标是否越界
        checkElementIndex(index);
        //查询出指定节点的数据
        Node<E> x = node(index);
        //老节点数据返回使用
        E oldVal = x.item;
        //新节点赋值
        x.item = element;
        return oldVal;
    }

3:get方法

get方法有三个,getFirst(),getLast(),get()

public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }

有个小疑惑,为什么为null的时候不能返回null,而抛异常。
我理解就是为了node循环查询时,不在额外判断是否为null,增加效率,不知到对不对。

   public E getFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return f.item;
    }
    public E getLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return l.item;
    }

LRU算法解析

介绍

LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

  1. 新数据插入到链表头部;
  2. 每当缓存命中(即缓存数据被访问),则将数据移到链表头部;
  3. 当链表满的时候,将链表尾部的数据丢弃。

基于LinkedList实现LRU算法

既然是有插入淘汰的算法,肯定考虑空间大小,所以设置链表长度。

 public class LruLinkedList {
        int memory_size;//用于限定内存空间大小,也就是缓存大小

        static final int DEFAULT_CAP = 5;//默认空间为5个节点

        public LruLinkedList(int default_memory_size) {
            memory_size = default_memory_size;
        }
    }

新数据从头部增加

        public void lruAdd(E data) {
        //如果链表长度大于等于设置的缓存大小,移除队尾数据
            if (size >= memory_size) {
                removeLast();
                addFirst(data);
            } else {
                addFirst(data);
            }
        }

访问的节点移到头部,更改链表顺序,删除新增效率高

 public E LruGet(int index) {
            checkPositionIndex(index);
            Node<E> resultDate = node(index);
            //删除访问的节点,访问的节点移到头部
            unlink(resultDate);
            //因为使用的已有方法,每个方法都会modCount++,从而减少一次。
            modCount--;
            linkFirst(resultDate.item);
            return resultDate.item;
        }

测试代码与结果

 public static void main(String[] args) {
        LinkedList <String> linkedList=new LinkedList<>();
        for(int i=0;i<10;i++){
            linkedList.add(i+"");
        }
        System.out.println("默认的linked数据:"+linkedList);
        System.out.println("查询后linked数据:"+lruGet(linkedList,2));
        System.out.println("新增的linked数据:"+lruAdd(linkedList,10+""));
        System.out.println("新增的linked数据:"+lruAdd(linkedList,11+""));
    }

在这里插入图片描述
以上就是全部内容,有任何疑问可以留言,大家一起讨论。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LinkedList是一种基于双向链表的List数据结构。它的内部实现源于对链表的操作,所以适用于频繁增加和删除元素的情况。LinkedList的创建可以通过构造器来完成,而往LinkedList中添加对象可以使用add方法。LinkedList不是线程安全的,并且查询元素的效率相对较低,因为需要一个一个遍历链表来查找元素。LinkedList实现了Queue接口,所以它既可以作为队列使用,也可以作为栈使用。此外,LinkedList实现了Deque接口,即双端队列。与ArrayList相比,LinkedList的内部结构有所不同,LinkedList继承自AbstractSequentialList,然后再继承自AbstractList。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [第三章 LinkedList源码解析1](https://download.csdn.net/download/weixin_35760849/86324281)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [LinkedList源码解析](https://blog.csdn.net/qq_38826019/article/details/115677359)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值