LinkedList的方法分析

本文深入分析了LinkedList的数据结构实现,包括双向循环链表的特点、节点插入与删除操作的具体流程,并探讨了get和add等核心方法的实现细节。

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

LinkedList继承关系
可以看出来LinkedList不仅实现了List接口,还实现了Deque(双端队列),这使得LinkedList可以实现队列的一些特性

// 成员变量:header是一个entry实例,既然知道linkedlist是实现了链表结构,header
// 显然是头结点,而且可以看出来,header并不用于存放元素,而是为其他元素提供一个初始
// 位置
private transient Entry<E> header = new Entry<E>(null, null, null);
// size指当前链表中节点个数,不包括头结点
private transient int size = 0;

....

// entry静态内部类
private static class Entry<E> {
    // 当前节点自身的内容
    E element;
    // 后一个节点的引用
    Entry<E> next;
    // 前一个节点的引用
    Entry<E> previous;
    // 第一个参数,第二个参数是next,第三个是previous,顺序要记住
    Entry(E element, Entry<E> next, Entry<E> previous) {
        this.element = element;
        this.next = next;
        this.previous = previous;
    }
}

// 头结点的前一个节点指向自身,后一个节点也指向自身,由这个空链表也可以看出来LinkedList是一个双向循环
// 链表
public LinkedList() {
    header.next = header.previous = header;
}

// 把集合转化成LinkedList
public LinkedList(Collection<? extends E> c) {
    // 仍然首先定义一个空链表
    this();
    addAll(c);
}

这是前面提到的hashmap内部数组上链表的结构

这里写图片描述

这里写图片描述

LinkedList的内部结构

public boolean addAll(Collection<? extends E> c) {
    // 在size处插入集合元素
    return addAll(size, c);
}

// index,从链表的index处(在index-1和index之间)开始插入给定的集合
public boolean addAll(int index, Collection<? extends E> c) {

    // 如果index为负数或者index大于链表中节点个数,抛出异常
    if (index < 0 || index > size)
        throw new IndexOutOfBoundsException("Index: "+index+ ", Size: "+size);
    // 集合转数组,如果数组长度为0(集合元素为空),返回false
    Object[] a = c.toArray();
    int numNew = a.length;
    if (numNew==0)
        return false;
    // 结构变化指标+1
    modCount++;
// 如果index=size,就在header前插入,如果不是就在index对应的元素前插入
// successor是待插入的位置后的节点
Entry<E> successor = (index==size ? header : entry(index));
// 获得带插入位置的前一个节点,保存predecessor 
Entry<E> predecessor = successor.previous;

// 遍历集合元素
    for (int i=0; i<numNew; i++) {
        // new一个新的entry,也就是带插入的节点,next为刚刚得到插入位置后的,previous是插入位置前的
        // 这个new节点的步骤,实际上完成了插入节点本身前后两个属性引用的指向,也就是说到这个步骤为止
        // 根据这个节点可以找到整个链表的元素,但是根据链表的其他节点还不能找到这个新插入的节点,因为和
        // 它相邻的节点的next,previous的指向还没有改
        Entry<E> e = new Entry<E>((E)a[i], successor, predecessor);
        // 前一个节点的next指向新插入的节点,插入完成了2/3
        predecessor.next = e;
        // 把新插入的节点作为下一个带插入的节点的前一个节点,循环
        predecessor = e;
}
// 循环结束,实际上这时候所有的集合元素,除了最后一个,都已经首尾相连,最后一个元素自身也已经指向原链表的
// index处的节点

// 插入工作的最后一步,index处的节点,previous引用指向集合的最后一个节点
successor.previous = predecessor;

    // size加上集合长度
    size += numNew;
    return true;
}
// add方法,试图在linkedlist尾部插入一个元素
public boolean add(E e) {
    addBefore(e, header);
        return true;
    }
=============================
 // 在给定index之前插入一个元素
  public void add(int index, E element) {
  // 如果index=size,那么和直接add到链表末尾一样,调用addbefore插入到header之前
  // 如果不是size,调用entry()返回带插入位置的当前节点
       addBefore(element, (index==size ? header :entry(index)));
    }
==============================   
 // 在给定参数entry节点前插入一个元素,对于header来说,header.previous
 // 就是链表的最后一个,这也说明了linkedlist实现了一个双向循环链表
 private Entry<E> addBefore(E e, Entry<E> entry) {
 // 构成一个新的节点,previous是entry.previous,next是entry
 // 对于header来说,新节点已经插入了last和header之间(仅其自身引用指向改变)
    Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
    // newEntry.previous就是新节点的前一个节点,但是它的next引用目前仍然
    // 指向newEntry.next,所以修改之,下面同理。
    newEntry.previous.next = newEntry;
    newEntry.next.previous = newEntry;
    // 节点数+1,结构变化+1,返回新插入的entry
    size++;
    modCount++;
    return newEntry;
}

可以看出来add方法调用addbefore完成了在header之前,也就是链表末尾的插入工作。

get(int index)和addBefore(E e, Entry entry)都用到了entry(int index)方法,这个方法实际上是根据index对链表遍历返回对应的节点

 public E get(int index) {
        return entry(index).element;
    }
private Entry<E> entry(int index) {
        if (index < 0 || index >= size)
            throw new IndexOutOfBoundsException("Index: "+index+
                                                ", Size: "+size);
        // header保存在局部变量e                                    
        Entry<E> e = header;
        // 判断index在链表中的位置,跟size/2比较,决定是从前往后遍历还
        // 是从后往前遍历
        if (index < (size >> 1)) {
            for (int i = 0; i <= index; i++)
                e = e.next;
        } else {
            for (int i = size; i > index; i--)
                e = e.previous;
        }
        return e;
    }
// 从linkedlist中删除某个元素
 public boolean remove(Object o) {
 // 这里把非空作为分支条件主要是因为o.equals(),会空指针异常
        if (o==null) {
        // 从header往后遍历,如果为空(e.element==null),就删除该节点
            for (Entry<E> e = header.next; e != header; e = e.next) {
                if (e.element==null) {
                    remove(e);
                    return true;
                }
            }
        } else {
            for (Entry<E> e = header.next; e != header; e = e.next) {
            // 如果element和给定的元素equals返回true,则删除该节点
                if (o.equals(e.element)) {
                    remove(e);
                    return true;
                }
            }
        }
        return false;
    }
===================================

private E remove(Entry<E> e) {
// header不能删除
    if (e == header)
        throw new NoSuchElementException();
// 把要删除的元素(不是节点),作为删除方法的返回值
        E result = e.element;
// 修改其头尾节点的next和previous指向,跳过待删除的节点
    e.previous.next = e.next;
    e.next.previous = e.previous;
    // 把删除的节点各个引用属性都改为null,等待gc回收
        e.next = e.previous = null;
        e.element = null;
    // 
    size--;
    modCount++;
        return result;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值