Java集合系列源码分析(二)--LinkedList

本文深入解析了LinkedList的数据结构,探讨了其底层实现为双向链表的特点,分析了添加、删除和查询操作的具体实现,以及这些操作在链表上的效率表现。

一、LinkedList

linkedlist底层是双向链表,插入和删除操作较快,查询和随机查询操作较慢。非线程安全。

二、源码分析

2.1 继承结构和层次

在这里插入图片描述
在这里插入图片描述
实现了序列化、克隆、list接口。

Deque:双端队列接口,扩展自Queue
在这里插入图片描述

2.2 LinkedList属性
在这里插入图片描述

//集合元素个数
transient int size = 0;

/**
 * 首节点指针
transient Node<E> first;

/**
 * 尾节点指针
 */
transient Node<E> last;

//node节点
private static class Node<E> {
    E item;
    Node<E> next;
    Node<E> prev;

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

2.3 构造方法
在这里插入图片描述
构造方法一个无参构造一个带参构造

/**
 * Constructs an empty list.
 */
public LinkedList() {
}

/**
 *传入集合的构造器
 */
public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}

2.4 常用方法

2.4.1 添加
在这里插入图片描述

  1. add(E e)

直接添加,添加在链表尾部

public boolean add(E e) {
	//在尾部添加
    linkLast(e);
    return true;
}

    /**
     * 在链表尾部添加元素
     */
    void linkLast(E e) {
        final Node<E> l = last;
        //建立新节点,prev为l即last,next为null,值为e
        final Node<E> newNode = new Node<>(l, e, null);
        //把last指针指向newNode
        last = newNode;
        //
        if (l == null)
        	//I指向为空,则为空链表,添加的第一个元素
            first = newNode;
        else
        	//不是空链表,将链表尾元素的next指向新node
            l.next = newNode;
        //链表容量+1
        size++;
        //修改次数+1
        modCount++;
    }
  1. void add(int index, E element)

指定位置添加

public void add(int index, E element) {
	//检查加入位置
    checkPositionIndex(index);

    if (index == size)
    	//如果和链表大小相等,在尾部添加
        linkLast(element);
    else
    	//在相应位置添加
        linkBefore(element, node(index));
}

/**
 * 在节点succ节点前添加(succ节点不为空)
 */
void linkBefore(E e, Node<E> succ) {
    // assert succ != null;
    //pred为succ节点的prev节点
    final Node<E> pred = succ.prev;
    //插入新节点,值为e,pred为新节点的prev,succ为新节点的next
    final Node<E> newNode = new Node<>(pred, e, succ);
    //维护succ prev的关系,指向新节点
    succ.prev = newNode;
    if (pred == null)
    	//pred为空,说明succ为首节点
        first = newNode;
    else
		//succ的prev节点的next指向新节点
        pred.next = newNode;
    size++;
    modCount++;
}
  1. boolean addAll(Collection<? extends E> c)

加入集合

//在加入集合,不带位置参数,即调用addAll中位置参数为链表大小size,即链表尾部添加
public boolean addAll(Collection<? extends E> c) {
    return addAll(size, c);
}
	
	//在对应位置加入集合
   public boolean addAll(int index, Collection<? extends E> c) {
        checkPositionIndex(index);
		//转换为数组
        Object[] a = c.toArray();
        //新增元素长度
        int numNew = a.length;
        //新增长度为0,返回false
        if (numNew == 0)
            return false;

		//定义要加入的index节点的prev节点为pred,index节点为succ
        Node<E> pred, succ;
        //加入位置索引==链表大小,index在队尾添加,则其next节点为null
        if (index == size) {
            succ = null;
            pred = last;
        } else {
        	//不在队尾,则succ作为index节点
            succ = node(index);
            //定义index节点prev
            pred = succ.prev;
        }

        for (Object o : a) {
            @SuppressWarnings("unchecked") E e = (E) o;
            //类似于上面的插入操作
            Node<E> newNode = new Node<>(pred, e, null);
            if (pred == null)
                first = newNode;
            else
                pred.next = newNode;
            pred = newNode;
        }

		//队尾节点加入,设置尾节点为pred
        if (succ == null) {
            last = pred;
        } else {
        	//非队尾加入,设置succ节点前后关系
            pred.next = succ;
            succ.prev = pred;
        }

        size += numNew;
        modCount++;
        return true;
    }
  1. void addFirst(E e)

添加至链表头部

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

    /**
     * 添加至链表头部
     */
    private void linkFirst(E e) {
        final Node<E> f = first;
        //新节点的prev为空,next为f节点
        final Node<E> newNode = new Node<>(null, e, f);
        //first指针指向新的node
        first = newNode;
        if (f == null)
        	//链表为空,first和last都指向新加入的元素
            last = newNode;
        else
        	//原first元素的prev指向新node
            f.prev = newNode;
        size++;
        modCount++;
    }

addLast与addFirst操作相似,就不说了

简单总结下add操作:

  • 新加元素要维护附近node及自身的前后关系,维护时注意顺序及辅助指针的应用,否则找不到后面的节点了
  • add操作较简单,因为添加操作速度很快

2.4.2 remove操作

在这里插入图片描述

  1. remove()
public E remove() {
	//移除首节点
    return removeFirst();
}
	
	
    public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
        	//首节点为null,则抛出异常
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }
    
    
    private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
        //获取要移除节点的值
        final E element = f.item;
        //获取移除节点的next节点
        final Node<E> next = f.next;
        //移除节点值,next指向null,help GC
        f.item = null;
        f.next = null; // help GC
        //移动first指针指向节点next
        first = next;
        if (next == null)
        	//next为空,则链表为空
            last = null;
        else
        	//节点next已为首节点,首节点的prev置空
            next.prev = null;
        size--;
        modCount++;
        return element;
    }
  1. E remove(int index)

移除对应位置的元素

public E remove(int index) {
    checkElementIndex(index);
    return unlink(node(index));
}

   E unlink(Node<E> x) {
        // assert x != null;
        //获取要移除位置的值,next及prev节点
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;

        if (prev == null) {
        	//prev为空,要移除的是首节点,将first指针指向刚定义的节点next
            first = next;
        } else {
        	//修改要移除的prev节点的next节点指向移除节点的next
            prev.next = next;
            //要移除节点的prev置null
            x.prev = null;
        }

        if (next == null) {
        	//目标节点的next为null,则目标节点为尾节点,设置last指向节点prev
            last = prev;
        } else {
        	//修改目标节点next节点的前置节点关系
            next.prev = prev;
            x.next = null;
        }

        x.item = null;
        size--;
        modCount++;
        return element;
    }

其他删除操作都比较类似,不多分析了

2.4.3 修改

在这里插入图片描述
修改元素比较简单,先找到index对应的节点,然后修改节点的值,返回旧值,不多分析了。

public E set(int index, E element) {
    checkElementIndex(index);
    Node<E> x = node(index);
    E oldVal = x.item;
    x.item = element;
    return oldVal;
}

2.4.4 查询
在这里插入图片描述

  1. E get(int index)

查询index位置元素值

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

    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;
        }
    }

查询方法都比较简单,不做分析了。源码一看就懂了

三、总结

  1. LinkedList基于双向链表实现,因此写操作通过设置附近节点关系来实现,比较快速简单
  2. 查询操作较麻烦,需要从头开始遍历
  3. 不需要指定空间大小
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值