[Java 集合] LinkedList源码分析

本文深入分析了Java的LinkedList集合的源码,包括其内部节点结构、构造方法和主要操作如增(addFirst、addLast、add)、删(removeFirst、removeLast、remove)、改(set)以及查(getFirst、getLast、get)等方法的实现细节,展示了LinkedList如何作为一个双向链表实现List接口,并能作为双端队列或栈使用的特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

[Java 集合] LinkedList源码分析

源码基于jdk 11
LinkedList是一个以双向链表实现的List,它除了作为List使用,还可以作为双端队列或者栈来使用

一、属性

	//元素的个数
    transient int size;
    //头节点
    transient LinkedList.Node<E> first;
    //尾节点
    transient LinkedList.Node<E> last;
    private static final long serialVersionUID = 876323262645176354L;

简单明了:头节点,尾节点,节点个数

二、主要内部类

    private static class Node<E> {
    	//节点元素
        E item;
        //后节点
        LinkedList.Node<E> next;
        //前节点
        LinkedList.Node<E> prev;

        Node(LinkedList.Node<E> prev, E element, LinkedList.Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

每个节点都包含:前节点、当前节点元素、后节点
Node节点

三、构造方法

	//空参构造方法
    public LinkedList() {
        this.size = 0;
    }

无参构造函数,初始化size=0

    public LinkedList(Collection<? extends E> c) {
    	//先调用空参构造,创建一个size为0的LindList
        this();
        this.addAll(c);
    }

在构造函数LinkedList(Collection<? extends E> c)中,
首先通过无参构造函数,创建一个size为0的LinkList集合,
然后通过addAll方法将传入的集合c添加到当前集合的后面。
addAll(Collection<? extends E> c) 作为成员方法,下文中有详细分析

四、常用方法

接下来,就是增删改查了

addFirst(E e)

在LinkList集合头部插入元素

    public void addFirst(E e) {
        this.linkFirst(e);
    }

    private void linkFirst(E e) {
    	//获取头部节点
        LinkedList.Node<E> f = this.first;
        //创建新节点,新节点的前置节点是null,存储元素是e,后置节点是f(头部节点)
        LinkedList.Node<E> newNode = new LinkedList.Node((LinkedList.Node)null, e, f);
        //将新节点置为头部节点
        this.first = newNode;
        //如果原头部节点为null,也就是说,添加的是集合的第一个元素
        if (f == null) {
        	//将新节点置为尾部节点,此时,因为只有一个节点,所以此节点既是头部节点也是尾部节点
            this.last = newNode;
        } else {
        	//当原头部节点不为null时,则将其前节点的地址prev指向新节点(原prev指向的null)
            f.prev = newNode;
        }
        //节点个数自增
        ++this.size;
        //修改次数自增
        ++this.modCount;
    }
  1. 获取集合的头部节点;
  2. 构建一个新的节点:前置节点指向null,存储元素设置为需要插入的元素,后置节点指向头部节点;
  3. 将新构建的节点设置为头部节点;
  4. 判断插入的是否时集合的第一个元素
    – 是第一个元素,将新节点同时设置成尾部节点
    – 不是第一个元素,将原头部节点的前置节点地址指向新节点
  5. 节点个数自增
  6. 修改次数自增
addLast(E e)

在LinkList集合尾部插入元素

    public void addLast(E e) {
        this.linkLast(e);
    }

    void linkLast(E e) {
        LinkedList.Node<E> l = this.last;
        LinkedList.Node<E> newNode = new LinkedList.Node(l, e, (LinkedList.Node)null);
        this.last = newNode;
        if (l == null) {
            this.first = newNode;
        } else {
            l.next = newNode;
        }

        ++this.size;
        ++this.modCount;
    }
  1. 获取集合的尾部节点;
  2. 构建一个新的节点:前置节点指向尾部节点,存储元素设置为需要插入的元素,后置节点指向null;
  3. 将新构建的节点设置为尾部节点;
  4. 判断插入的是否时集合的第一个元素
    – 是第一个元素,将新节点同时设置成头部节点
    – 不是第一个元素,将原尾部节点的后置节点地址指向新节点
  5. 节点个数自增
  6. 修改次数自增
add(E e)

向LinkList集合添加元素的方法

    public boolean add(E e) {
    	//可以看到,默认就是在LinkList集合尾部插入元素
        this.linkLast(e);
        return true;
    }
add(int index, E element)

在指定索引位置插入元素

    public void add(int index, E element) {
    	//判断索引是否越界
        this.checkPositionIndex(index);
        //是否向链表末端插入一个元素(当前节点最大索引值为size-1)
        if (index == this.size) {
            this.linkLast(element);
        } else {
        	//不是在集合尾部添加元素,调用linkBefore方法,传入要添加的元素和指定索引处的节点
            this.linkBefore(element, this.node(index));
        }
    }

    void linkBefore(E e, LinkedList.Node<E> succ) {
    	//获取[原节点的前置节点]
        LinkedList.Node<E> pred = succ.prev;
        //构建一个新的节点,新节点的前置节点指向[原节点的前置节点],存储元素设置为需要插入的元素,后置节点指向原节点
        LinkedList.Node<E> newNode = new LinkedList.Node(pred, e, succ);
        //再将[原节点的前置节点]指向新节点
        succ.prev = newNode;
        //判断[原节点的前置节点]是否为null
        if (pred == null) {
        	//如果[原节点的前置节点]为空,说明它是头节点。此时,将新插入的节点设置为头部节点
            this.first = newNode;
        } else {
        	//将[原节点的前置节点] 的后置节点指向新节点(好拗口啊。。)
            pred.next = newNode;
        }
        ++this.size;
        ++this.modCount;
    }
  1. 检查索引是否越界;
  2. 是否向集合末端添加元素;
    • 是,在结合末端插入元素(同addLast方法)
      • 获取[原节点的前置节点]
      • 构建一个新的节点,新节点的前置节点指向[原节点的前置节点],存储元素设置为需要插入的元素,后置节点指向原节点
      • 将[原节点的前置节点]指向新节点
      • 判断[原节点的前置节点]是否为null
        – 为null,说明它是头节点。此时,将新插入的节点设置为头部节点
        – 不为null,将[原节点的前置节点] 的后置节点指向新节点
      • 节点个数自增
      • 修改次数自增
addAll(Collection<? extends E> c)

向LinkList中添加集合

    public boolean addAll(Collection<? extends E> c) {
        return this.addAll(this.size, c);
    }

直接调用了addAll(int index, Collection<? extends E> c)方法

addAll(int index, Collection<? extends E> c)

在LinkList指定索引位置插入集合

    public boolean addAll(int index, Collection<? extends E> c) {
    	//检查索引是否越界
        this.checkPositionIndex(index);
        //将需要添加的集合转成数组
        Object[] a = c.toArray();
        //判断添加的集合是否为空
        int numNew = a.length;
        if (numNew == 0) {
        	//添加的集合为空,就不用添加了
            return false;
        } else {
        	//pred节点是关键
            LinkedList.Node pred;
            //index(指定索引)处的节点
            LinkedList.Node succ;
            //判断插入位置是否在集合末尾
            if (index == this.size) {
            	//在原集合尾部插入集合,将pred指向原集合的尾节点
                succ = null;
                //pred节点指向原集合的尾节点
                pred = this.last;
            } else {
            	//不是在尾部插入,取索引处的节点
                succ = this.node(index);
                //pred节点指向索引处节点的前置节点
                pred = succ.prev;
            }
			//需要添加数组
            Object[] var7 = a;
            //数组的长度
            int var8 = a.length;
			//遍历数组,利用数组里的元素构建一个链表
            for(int var9 = 0; var9 < var8; ++var9) {
            	//获取元素
                Object o = var7[var9];
                //构建新节点newNode:前置节点为pred,元素为当前获取的元素,后置节点为null
                //newNode 的前置节点指向pred
                LinkedList.Node<E> newNode = new LinkedList.Node(pred, o, (LinkedList.Node)null);
                //判断是否是头节点
                if (pred == null) {
                	//如果前置节点为null,则将新构建的节点设置为头节点
                    this.first = newNode;
                } else {
                	//前置节点不为null,则将pred节点的后置节点指向新构建的节点
                	//pred 的后置节点指向newNode 
                    pred.next = newNode;
                }
				//pred指向新节点
                pred = newNode;
            }
			//判断指定索引处的节点是否为null
            if (succ == null) {
            	//是尾节点的后置节点,将尾节点指向pred节点
                this.last = pred;
            } else {
            	//不是空集合,将pred节点的后置节点指向索引处节点
                pred.next = succ;
                //再将索引处节点的前置节点指向pred节点
                succ.prev = pred;
            }
			//集合元素个数更新
            this.size += numNew;
            //修改次数自增
            ++this.modCount;
            return true;
        }
    }
  1. 检查索引是否越界;
  2. 将需要添加的集合c转成数组a;
  3. 检查数组a是否不为空;(为空就不添加了,后面只讨论不为空的情况)
  4. 定义临时节点pred和索引处节点succ;
  5. 判断索引指向的节点是否是集合的尾节点的后置节点(尾节点的后置节点指向null)
    – 是,那么此时索引处节点succ指向null ,将pred节点指向当前集合的尾节点。
    – 否,获取索引处节点succ,并将pred节点置为succ节点的前置节点。
  6. 遍历数组,在pred节点后面,利用数组里的元素构建一个链表(从前往后构建链表)
    • 判断pred节点是否为null
      – 是,说明原集合为空,则将新构建的节点置为头节点
      – 否,则将新节点放在pred后面
    • 更新pred节点,使其指向新节点
  7. 判断索引指向的节点是否是集合的尾节点的后置节点(尾节点的后置节点指向null)
    – 是尾节点的后置节点,相当于在原链表的末尾插入新链表;
    那么就将pred节点(当前表示的是新链表的最后一个节点)置为尾节点
    – 不是尾节点的后置节点,相当于在原链表的中间插入新链表;
    那么先将新链表的最后一个节点pred 的后置节点指向索引处节点succ,
    然后再将索引处节点succ的前置节点指向pred节点
  8. 更行集合元素个数
  9. 修改字数自增

remove()

默认删除第一个元素

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

直接调用removeFirst()方法,下面介绍

removeFirst()

删除LinkList集合中的第一个元素

    public E removeFirst() {
    	//获取链表的头节点
        LinkedList.Node<E> f = this.first;
        //检查头节点是否存在
        if (f == null) {
            throw new NoSuchElementException();
        } else {
            return this.unlinkFirst(f);
        }
    }

    private E unlinkFirst(LinkedList.Node<E> f) {
    	//获取第一个节点中的元素
        E element = f.item;
        //获取第二个节点
        LinkedList.Node<E> next = f.next;
        //将头节点置空
        f.item = null;
        f.next = null;
        //将第二个节点置为头节点
        this.first = next;
        //判断第二个节点是否为null
        if (next == null) {
        	//如果第二个节点为null,说明集合已经没有元素了,将尾节点也置为null
            this.last = null;
        } else {
        	//如果第二个节点不为空,那么作为头节点,它的前置节点要指向null
            next.prev = null;
        }
		//删除了一个元素,集合的元素个数要自减
        --this.size;
        //修改次数要自增
        ++this.modCount;
        //返回被删除的元素
        return element;
    }
removeLast()

删除LinkList集合中的最后一个元素

    public E removeLast() {
    	//获取链表的尾节点
        LinkedList.Node<E> l = this.last;
        //检查尾节点是否存在
        if (l == null) {
            throw new NoSuchElementException();
        } else {
            return this.unlinkLast(l);
        }
    }

    private E unlinkLast(LinkedList.Node<E> l) {
    	//获取尾节点的元素
        E element = l.item;
        //获取倒数第二个节点
        LinkedList.Node<E> prev = l.prev;
        //将尾节点置空
        l.item = null;
        l.prev = null;
        //将倒数第二个节点置为尾节点
        this.last = prev;
        //判断倒数第二个节点是否为空
        if (prev == null) {
        	//如果倒数第二个节点为空,说明集合已经没有元素了,将头节点也置为null
            this.first = null;
        } else {
        	//如果倒数第二个节点不为空,作为尾节点,它的后置节点要指向null
            prev.next = null;
        }
		//删除了一个元素,集合的元素个数要自减
        --this.size;
        //修改次数要自增
        ++this.modCount;
        //返回被删除的元素
        return element;
    }
remove(Object o)

删除LinkList集合中指定的元素

   public boolean remove(Object o) {
        LinkedList.Node x;
        //判断删除的元素是否是null
        if (o == null) {
        	//如果删除的元素是null,顺向遍历链表
            for(x = this.first; x != null; x = x.next) {
            	//判断遍历到的节点中的元素是否为null
                if (x.item == null) {
                	//如果当前节点的元素为null,则调用unlink方法删除此节点
                    this.unlink(x);
                    //完成一次删除就结束
                    return true;
                }
            }
        } else {
        	//如果删除的元素不是null,顺向遍历链表
            for(x = this.first; x != null; x = x.next) {
            	//判断遍历到的节点中的元素是要删除的元素
                if (o.equals(x.item)) {
                	//如果是要删除的元素,则调用unlink方法删除此节点
                    this.unlink(x);
                    //完成一次删除就结束
                    return true;
                }
            }
        }
		//遍历结束都没返回true,说明没有找到,删除失败,返回false
        return false;
    }

    E unlink(LinkedList.Node<E> x) {
    	//获取要删除节点的元素
        E element = x.item;
        //分别获取要删除节点的后置节点和前置节点
        LinkedList.Node<E> next = x.next;
        LinkedList.Node<E> prev = x.prev;
        //判断前置节点是否为null
        if (prev == null) {
        	//前置节点为null,说明被删除的是头节点,那么就要将它的后置节点置为头节点
            this.first = next;
        } else {
         	//被删除的不是头节点,则将前置节点的next指向被删除节点的后置节点(断开了被删除节点)
            prev.next = next;
            //同时,将被删除的节点的prev指向null(一后一前,双向指针都断掉了)
            x.prev = null;
        }
		//判断后置节点是否为null
        if (next == null) {
        	//后置节点为null,说明被删除的是尾节点,那么就要将它的前置节点置为尾节点
            this.last = prev;
        } else {
        	//被删除的不是尾节点,则将后置节点的prev指向被删除节点的前置节点
            next.prev = prev;
            //同时,将被删除节点的next指向null
            x.next = null;
        }
		//被删除节点的元素置为null
        x.item = null;
        //集合元素个数自减
        --this.size;
        //修改次数自增
        ++this.modCount;
        //返回被删除的元素
        return element;
    }
remove(int index)

删除指定索引处的元素

    public E remove(int index) {
    	//检查索引是否越界
        this.checkElementIndex(index);
        //先找到索引处的节点,再调unlink方法传入索引处的节点
        return this.unlink(this.node(index));
    }
	
    LinkedList.Node<E> node(int index) {
        LinkedList.Node x;
        int i;
        //这里要判断index是处于链表的前半段还是后半段
        if (index < this.size >> 1) {
        	//index位于链表前半段,获取头部节点
            x = this.first;
			//从前往后遍历,一直到index前面一位的位置
            for(i = 0; i < index; ++i) {
                x = x.next;
            }
			//index前面的节点的next指向的就是index处节点
            return x;
        } else {
        	//index位于链表后半段,获取尾部节点
            x = this.last;
			//从后往前遍历,一直到index后面一位的位置
            for(i = this.size - 1; i > index; --i) {
                x = x.prev;
            }
			//index后面的节点的prev指向的就是index处节点
            return x;
        }
    }
removeFirstOccurrence(Object o)

删除LinkList中第一次出现的指定元素

    public boolean removeFirstOccurrence(Object o) {
        return this.remove(o);
    }

通过调用 remove(Object o)方法实现(上文有介绍)

removeLastOccurrence(Object o)

删除LinkList中最后一次出现的指定元素

    public boolean removeLastOccurrence(Object o) {
        LinkedList.Node x;
        //判断指定元素是否为null
        if (o == null) {
        	//从后向前遍历链表
            for(x = this.last; x != null; x = x.prev) {
            	//找到元素为null的节点
                if (x.item == null) {
                	//删除该节点,结束循环
                    this.unlink(x);
                    return true;
                }
            }
        } else {
        	//要删除的元素不为null,从后向前,遍历链表
            for(x = this.last; x != null; x = x.prev) {
            	//找到第一个含有指定元素的节点
                if (o.equals(x.item)) {
                	//删除该节点,结束循环
                    this.unlink(x);
                    return true;
                }
            }
        }
		//集合中不包含该元素,则返回false
        return false;
    }

set(int index, E element)

将指定索引处的节点中元素修改为指定元素

    public E set(int index, E element) {
    	//检查索引是否越界
        this.checkElementIndex(index);
        //先获取指定索引处的节点
        LinkedList.Node<E> x = this.node(index);
        //获取该节点原来的元素
        E oldVal = x.item;
        //将该节点的元素修改为指定的元素
        x.item = element;
        //返回该节点原来的元素
        return oldVal;
    }

getFirst()

获取LinkList集合中第一个元素

    public E getFirst() {
    	//先获取头节点
        LinkedList.Node<E> f = this.first;
        if (f == null) {
            throw new NoSuchElementException();
        } else {
        	//如果头节点不为null,则取其元素
            return f.item;
        }
    }
getLast()

获取LinkList集合中最后一个元素

    public E getLast() {
    	//先获取尾节点
        LinkedList.Node<E> l = this.last;
        if (l == null) {
            throw new NoSuchElementException();
        } else {
        	//如果尾节点不为null,则取其元素
            return l.item;
        }
    }
get(int index)

获取LinkList集合中指定索引处节点的元素

    public E get(int index) {
    	//检查索引是否越界
        this.checkElementIndex(index);
        //先获取指定索引处的节点,再获取节点的元素
        return this.node(index).item;
    }

node(int index)方法在上文remove(int index)方法中有介绍

peek()

取第一个元素

    public E peek() {
    	//获取头节点
        LinkedList.Node<E> f = this.first;
        //如果节点不为空,取节点元素
        return f == null ? null : f.item;
    }
peekFirst()

同上

    public E peekFirst() {
        LinkedList.Node<E> f = this.first;
        return f == null ? null : f.item;
    }
peekLast()

取最后一个元素

    public E peekLast() {
    	//获取尾节点
        LinkedList.Node<E> l = this.last;
        //如果尾节点不为空,取节点元素
        return l == null ? null : l.item;
    }

get方法和peek方法的区别在于:如果集合为空,那么get方法会抛异常,而peek方法返回null

其他方法

pop()

弹栈,获取并删除第一个元素;集合为空时,抛出异常

    public E pop() {
        return this.removeFirst();
    }
poll()

弹栈,获取并删除第一个元素;集合为空时,返回null

    public E poll() {
        LinkedList.Node<E> f = this.first;
        return f == null ? null : this.unlinkFirst(f);
    }
pollFirst()

同上

    public E pollFirst() {
        LinkedList.Node<E> f = this.first;
        return f == null ? null : this.unlinkFirst(f);
    }
pollLast()

弹栈,获取并删除最后一个元素;集合为空时,返回null

    public E pollLast() {
        LinkedList.Node<E> l = this.last;
        return l == null ? null : this.unlinkLast(l);
    }

pop和poll的区别:pop只能单向,poll可以双向

push(E e)

压栈,在集合头部添加元素

    public void push(E e) {
        this.addFirst(e);
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值