java集合详解之LinkedList

本文详细分析了Java中的LinkedList数据结构,阐述了其作为有序列表允许重复和null值的特点,以及基于双向链表实现带来的高效插入和删除操作。同时,由于查询需要遍历,因此查询效率较低。源码分析部分展示了LinkedList的构造、添加、删除和获取元素等关键方法的实现细节,强调了其在不同操作中的性能表现。
摘要由CSDN通过智能技术生成

1 特点

  • 有序的,可以存储重复值和null值。
  • 底层是双向链表实现的,线程不安全,在进行任何位置的增删操作花费的时间都一样,不需要搬运数据,效率高,但是在查询的时候要移动指针,查询慢,不适合查询。链表不会涉及到扩容问题。
  • ArrayList 对空间的消耗是要低于LinkedList,因为LinkedList要维护前后指针,还要对数据进行封装

2 源码分析

image-20201206100001020

2.1 接口的定义和属性

/**
 *双向链表List,实现了 List 和 Deque接口,实现了list 接口的所有操作,并且允许所有的值,包括null
 *所有操作都符合对双向链表的预期,关于index(下标或者是索引)的操作都会从头或者从尾部遍历整个链表
 */
public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    /**
     * 集合的长度,表示LinkedList 存储了多少元素
     */
    transient int size = 0;

    /**
     * 头节点
     */
    transient Node<E> first;

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

    /**
     * 节点的定义
     */
    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.2 构造方法

    //创建一个空的LinkedList    
    public LinkedList() {
    }

    /**
     * 创建一个包含特定集合全部元素的list ,元素的顺序和特定集合的遍历顺序一致
     */
    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }

2.3 add(E e)

    public boolean add(E e) {
        linkLast(e);
        return true;
    }

    //添加元素e为最后一个元素
    void linkLast(E e) {
        final Node<E> l = last;
        // 添加e 到list 的尾部,那么此时的last 则成为e 的前置,那么e 的后置则是null
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            // last 为null 意味着是空表,此时e(newNode) 则成为first
            first = newNode;
        else
            // 否则 e(newNode) 则成为 last 的后置
            l.next = newNode;
        size++;
        modCount++;
    }

2.4 add(int index, E element)

LinkedList插入效率高是相对的,因为它省去了ArrayList插入数据可能的数组扩容和数据元素移动时所造成的开销,但数据扩容和数据元素移动却并不是时时刻刻都在发生的。

    //在指定位置插入指定的元素
    public void add(int index, E element) {
        // 检测下标的合法性
        checkPositionIndex(index);
        // 则判断是不是添加到集合尾部
        if (index == size)
            // 当index=size 的时候则是添加到集合的尾部
            linkLast(element);
        else
            // 如果不是添加到最后,这个里通过node(index) 方法得到这个位置当前的元素
            linkBefore(element, node(index));
    }

    // 检测下标的合法性
    private void checkPositionIndex(int index) {
        if (!isPositionIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    private boolean isPositionIndex(int index) {
        return index >= 0 && index <= size;
    }

    // 是返回特定位置的节点,链表中的节点,所以不可能为空
    Node<E> node(int index) {
        // 判断是从头开始遍历还是从尾部开始遍历,如果index<size/2 则从头遍历,否则从尾部开始遍历
        //越靠近中间的元素,调用get(int 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;
        }
    }

    //在一个非空节点前插入一个元素,succ 是传入的当前位置上已经存在的元素,接下来要做的就是将e 和 succ 连接起来
    void linkBefore(E e, Node<E> succ) {
        // 将e节点插入到succ 节点前
        final Node<E> pred = succ.prev;
        final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;
        // 如果pree是null ,则succ就是first节点,那么e 就要代替succ 成为新的first
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

2.5 get(int index)

随机读取元素不是LinkedList所擅长的,读取效率比起ArrayList也低得多,因为LinkedList需要遍历

    public E get(int index) {
        //检测下标是否合法,与add方法中isPositionIndex条件不同,这里的index必须小于size
        checkElementIndex(index);
        // 返回特定位置的节点
        return node(index).item;
    }

    private void checkElementIndex(int index) {
        if (!isElementIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    private boolean isElementIndex(int index) {
        return index >= 0 && index < size;
    }

    // 是返回特定位置的节点,链表中的节点,所以不可能为空
    Node<E> node(int index) {
        // 判断是从头开始遍历还是从尾部开始遍历,如果index<size/2 则从头遍历,否则从尾部开始遍历
        //越靠近中间的元素,调用get(int 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;
        }
    }

2.6 removeFirst()和removeLast()

    //移除list 中的第一个元素
    public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }

    //取消first 的连接
    private E unlinkFirst(Node<E> f) {
        
        final E element = f.item;
        final Node<E> next = f.next;
        //移除first节点
        f.item = null;
        f.next = null;
        first = next;
        if (next == null)
            last = null;
        else
            next.prev = null;
        size--;
        modCount++;
        return element;
    }


    //移除list 中的最后一个元素
    public E removeLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return unlinkLast(l);
    }

    //取消last的连接
    private E unlinkLast(Node<E> l) {
        // assert l == last && l != null;
        final E element = l.item;
        final Node<E> prev = l.prev;
        //移除last节点
        l.item = null;
        l.prev = null;
        last = prev;
        if (prev == null)
            first = null;
        else
            prev.next = null;
        size--;
        modCount++;
        return element;
    }

2.7 remove(Object o) 和 remove(int index)

思路相似,都是先找到要删除的节点,remove(Object o)方法遍历整个集合,通过 == 或 equals方法进行判断;remove(int index)通过node(index)方法。

    //删除某个对象    
    public boolean remove(Object o) {
        //遍历LinkedList,找到要删除的节点
        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;
    }

    //删除某节点,并将该节点的上一个节点(如果有)和下一个节点(如果有)关联起来
    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;

        if (prev == null) {
            first = next;
        } else {
            prev.next = next;
            x.prev = null;
        }

        if (next == null) {
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }

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

    //删除某个位置的元素
    public E remove(int index) {
        checkElementIndex(index);
        //遍历LinkedList,先找到指定位置的节点,然后再删除
        return unlink(node(index));
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值