集合底层源码分析之List(六)

前言

本章节讲解ArrayListLinkedListArrayList是数组结构,继承AbstractList,实现ListRandomAccessCloneableSerializable 接口;LinkedList是链表结构,继承AbstractSequentialList,实现ListDequeCloneableSerializable接口。

源码分析

ArrayList主要属性及构造方法分析

    public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
        private static final long serialVersionUID = 8683452581122892189L;
        //默认初始化数组容器容量
        private static final int DEFAULT_CAPACITY = 10;
        //空数组
        private static final Object[] EMPTY_ELEMENTDATA = {};
        //默认空数组容器
        private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
        //用于存放元素列表的数组
        transient Object[] elementData; // non-private to simplify nested class access
        //元素大小
        private int size;
        //Interger最大值减8
        private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

        //传入初始化容器的有参构造方法
        public ArrayList(int initialCapacity) {
            if (initialCapacity > 0) {//判断初始化容器大于0
                this.elementData = new Object[initialCapacity];//创建一个指定大小的容器赋值给elementData数组
            } else if (initialCapacity == 0) {//如果初始化容器大小等于0
                this.elementData = EMPTY_ELEMENTDATA;//elementData等于空数组
            } else { //否则抛异常
                throw new IllegalArgumentException("Illegal Capacity: "+
                        initialCapacity);
            }
        }

        //无参构造elementData等于默认空数组且容器容量等于10
        public ArrayList() {
            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
        }

        //有参构造传入集合元素
        public ArrayList(Collection<? extends E> c) {
            elementData = c.toArray();//转换为数组赋值给elementData
            if ((size = elementData.length) != 0) {//判断元素大小等于数组长度且不等于0
                // 判断class不相等
                if (elementData.getClass() != Object[].class)
                    elementData = Arrays.copyOf(elementData, size, Object[].class);//elementData的容器容量等于集合的容器大小
            } else {// 否则替换为空数组
                this.elementData = EMPTY_ELEMENTDATA;
            }
        }

        //获取元素数量
        public int size() {
            return size;
        }

        //判断元素是否为0
        public boolean isEmpty() {
            return size == 0;
        }
    }

ensureCapacityInternal()方法源码分析

    //指定容器大小
    private void ensureCapacityInternal(int minCapacity) {
        //判断element数组等于默认空数组
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            //比较默认容器大小和传入的容器大小获取最大值
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        ensureExplicitCapacity(minCapacity);
    }

ensureExplicitCapacity()方法源码分析

    //保证容器大小
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;//操作次数累加
        // 判断传入的容器大小减数组容器大小大于0
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);//调用grow方法
    }

grow()方法源码分析

    //增加容量
    private void grow(int minCapacity) {
        // 获取数组长度
        int oldCapacity = elementData.length;
        //新容器大小等于旧容器大小加旧容器大小的一半
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)//判断新容器大小减传入的容器大小小于0
            newCapacity = minCapacity;//新容器大小等于传入容器大小
        if (newCapacity - MAX_ARRAY_SIZE > 0)//判断新容器大小减最大容器大小大于0
            newCapacity = hugeCapacity(minCapacity);//获取最大容器
        // 数组容器大小等于新容器大小
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

hugeCapacity()方法源码分析

    //超大容器
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) //判断容器参数小于0
            throw new OutOfMemoryError();//抛出异常
        //容器参数大于最大数组容器,返回Integer最大值,否则返回最大数组容器
        return (minCapacity > MAX_ARRAY_SIZE) ?
                Integer.MAX_VALUE :
                MAX_ARRAY_SIZE;
    }

rangeCheckForAdd()方法源码分析

    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)//判断下标大于元素大小或元素小于0抛异常        
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

fastRemove()方法源码分析

    private void fastRemove(int index) {//通过下标删除
        modCount++;//操作次数累加
        int numMoved = size - index - 1;//计算移动的元素个数
        if (numMoved > 0)//判断移动的元素个数大于0
            System.arraycopy(elementData, index + 1, elementData, index,
                    numMoved);//数组复制
        elementData[--size] = null; // size减一后size位的下标位设为null
    }

add()方法源码分析

新增有两个方法,一个按顺序插入,一个按指定位置插入,分别进行讲解

    //顺序插入
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);//容器
        elementData[size++] = e;//数组容器大小累加且对应下标赋值
        return true;
    }
    //指定容器大小
    private void ensureCapacityInternal(int minCapacity) {
        //判断element数组等于默认空数组
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            //比较默认容器大小和传入的容器大小获取最大值
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        ensureExplicitCapacity(minCapacity);
    }
        //保证容器大小
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;//操作次数累加
        // 判断传入的容器大小减数组容器大小大于0
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);//调用grow方法
    }
        //增加容量
    private void grow(int minCapacity) {
        // 获取数组长度
        int oldCapacity = elementData.length;
        //新容器大小等于旧容器大小加旧容器大小的一半
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)//判断新容器大小减传入的容器大小小于0
            newCapacity = minCapacity;//新容器大小等于传入容器大小
        if (newCapacity - MAX_ARRAY_SIZE > 0)//判断新容器大小减最大容器大小大于0
            newCapacity = hugeCapacity(minCapacity);//获取最大容器
        // 数组容器大小等于新容器大小
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
案例讲解

在这里插入图片描述
第二次插入
在这里插入图片描述
然后再来讲解指定位置插入

    //指定位置添加
    public void add(int index, E element) {
        rangeCheckForAdd(index);//校验数组越界      
        ensureCapacityInternal(size + 1);//获取容器大小     
        System.arraycopy(elementData, index, elementData, index + 1,
                size - index);//数组复制     
        elementData[index] = element; //数组容器大小累加且对应下标赋值     
        size++;//元素累加 
    }
    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)//判断下标大于元素大小或元素小于0抛异常        
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    //指定容器大小
    private void ensureCapacityInternal(int minCapacity) {
        //判断element数组等于默认空数组
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            //比较默认容器大小和传入的容器大小获取最大值
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        ensureExplicitCapacity(minCapacity);
    }
    //保证容器大小
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;//操作次数累加
        // 判断传入的容器大小减数组容器大小大于0
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);//调用grow方法
    }
    //增加容量
    private void grow(int minCapacity) {
        // 获取数组长度
        int oldCapacity = elementData.length;
        //新容器大小等于旧容器大小加旧容器大小的一半
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)//判断新容器大小减传入的容器大小小于0
            newCapacity = minCapacity;//新容器大小等于传入容器大小
        if (newCapacity - MAX_ARRAY_SIZE > 0)//判断新容器大小减最大容器大小大于0
            newCapacity = hugeCapacity(minCapacity);//获取最大容器
        // 数组容器大小等于新容器大小
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

在这里插入图片描述
最后再来讲解ArrayList是如何扩容的
在这里插入图片描述

add()方法:小结
  • ArrayList的添加元素,如果没给定初始化容器大小,第一次进来,默认分配数组容器大小为10,每次操作累计元素个数。当插入的元素大于数组容器大小时,就会进行扩容,扩容大小为1.5倍。
  • 指定位置添加元素,要保证元素个数大于下标位置,否则会抛异常。通过将指定下标元素(包括下标后的元素)往后挪动的方式,去替换当前下标的元素

get()方法源码分析

    //获取元素 index=数组下标
    public E get(int index) {
        rangeCheck(index);//校验是否越界      
        return elementData(index);//返回下标对应的元素 
    }

    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)//判断下标大于元素大小或元素小于0抛异常         
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    E elementData(int index) {
        return (E) elementData[index];//通过下标获取数组元素 
    }
案例讲解

在这里插入图片描述

set()方法源码分析

    //修改元素 index=下标,element=替换元素
    public E set(int index, E element) {
        rangeCheck(index);//校验是否越界
        E oldValue = elementData(index);//获取下标节点
        elementData[index] = element;//替换元素
        return oldValue;//返回原元素
    }

    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)//判断下标大于元素大小或元素小于0抛异常
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    E elementData(int index) {
        return (E) elementData[index];//通过下标获取数组元素
    }
案例讲解

在这里插入图片描述

get()和set()方法:小结
  • get()和set() 方法都很简单,由于底层是数组结构,获取和替换元素都只需要通过下标就能很快速的操作

remove()方法源码分析

删除也分为两种,一种是通过值删除,另一种通过下标删除。一般来说,删除元素都是通过值来删除,因为你无法确认你删除的元素在什么位置,由源码可知,删除效率不高。

    //删除元素
    public boolean remove(Object o) {
        if (o == null) {//判断o等于null
            for (int index = 0; index < size; index++)//从0开始遍历size次
                if (elementData[index] == null) {//判断每次遍历的值等于null
                    fastRemove(index);//调用删除方法
                    return true;//返回true
                }
        } else {//否则o不等于null
            for (int index = 0; index < size; index++)//从0开始遍历size次
                if (o.equals(elementData[index])) {//判断传入的值等于每次遍历的值
                    fastRemove(index);//调用删除方法
                    return true;//返回true
                }
        }
        return false;//返回false
    }
    private void fastRemove(int index) {//通过下标删除
        modCount++;//操作次数累加
        int numMoved = size - index - 1;//计算移动的元素个数
        if (numMoved > 0)//判断移动的元素个数大于0
            System.arraycopy(elementData, index + 1, elementData, index,
                    numMoved);//数组复制
        elementData[--size] = null; // size减一后size位的下标位设为null
    }
案例讲解

在这里插入图片描述
再来讲解按照下标删除

    //删除指定下标元素
    public E remove(int index) {
        rangeCheck(index);//校验是否越界    
        modCount++;//操作次数累计    
        E oldValue = elementData(index);//通过下标获取值    
        int numMoved = size - index - 1;//计算移动的元素个数    
        if (numMoved > 0)//判断移动的元素个数大于0        
            System.arraycopy(elementData, index + 1, elementData, index,
                    numMoved);//数组复制    
        elementData[--size] = null;// size减一后size位的下标位设为null    
        return oldValue;//返回值
    }
    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)//判断下标大于元素大小或元素小于0抛异常
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    E elementData(int index) {
        return (E) elementData[index];//通过下标获取数组元素
    }

在这里插入图片描述

remove()方法:小结
  • ArrayList的删除也是非常的简单,删除有两种方式,一种通过值进行删除,一种通过下标删除
  • 通过值删除。首先通过遍历获取值对应的下标,通过将指定下标后的元素往前挪动的方式,去删除最后一个元素,通过值删除返回的ture或false,false表示没找到对应的下标。
  • 通过下标删除。和通过值删除差不多,通过将指定下标后的元素往前挪动的方式,去删除最后一个元素,省去了遍历的操作,不过要保证元素个数大于下标位置,否则会抛异常。通过下标删除返回的是下标的值,如果没有返回的是null

LinkedList主要属性及构造方法分析

    public class LinkedList<E>
            extends AbstractSequentialList<E>
            implements List<E>, Deque<E>, Cloneable, java.io.Serializable {
        //元素大小默认0     
        transient int size = 0;
        //头节点     
        transient Node<E> first;
        //尾节点     
        transient Node<E> last;

        //无参构造函数     
        public LinkedList() {
        }

        //有参构造     
        public LinkedList(Collection<? extends E> c) {
            this();
            addAll(c);
        }

        private static class Node<E> {//node节点         
            E item;//当前节点的值         
            Node<E> next;//下一个节点         
            Node<E> prev;//上一个节点          

            Node(Node<E> prev, E element, Node<E> next) {//构造方法             
                this.item = element;//给item赋值参数element             
                this.next = next;//给item的下一个节点赋值             
                this.prev = prev;//给item的上一个节点赋值     
            }
        }
    }

linkLast()方法源码分析

    //尾部插入
    void linkLast(E e) {
        final Node<E> l = last;//获取最后一个节点     
        final Node<E> newNode = new Node<>(l, e, null);//创建新节点且和最后一个节点建立关系     
        last = newNode;//last节点等于新的节点     
        if (l == null)//判断l节点等于null         
            first = newNode;//first节点等于新节点     
        else //否则l节点不等于null         
            l.next = newNode;//l节点的下一个节点等于新节点(奖励双向关系)     
        size++;//元素个数累加     
        modCount++;//操作次数累加 
    }

checkPositionIndex()方法源码分析

    private void checkPositionIndex(int index) {//校验是否越界     
        if (!isPositionIndex(index))//调用判断方法如果返回false,抛异常         
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

isPositionIndex()方法源码分析

    private boolean isPositionIndex(int index) {
        return index >= 0 && index <= size;//判断节点位置大于等于0且小于等于元素个数 
    }

node()方法源码分析

    Node<E> node(int index) {//通过节点位置获取节点     
        // assert isElementIndex(index);      
        if (index < (size >> 1)) {//判断节点位置小于元素个数的一半         
            Node<E> x = first;//获取头节点         
            for (int i = 0; i < index; i++)//从0开始正序遍历index次             
                x = x.next;//x节点等于x节点的下一个节点         
            return x;//返回x节点     
        } else { // 否则节点位置大于元素个数的一半         
            Node<E> x = last;//获取尾节点         
            for (int i = size - 1; i > index; i--)//从尾部开始倒叙遍历             
                x = x.prev;//x节点等于x节点的上一个节点         
            return x;//返回x节点     
        }
    }

linkBefore()方法源码分析

    void linkBefore(E e, Node<E> succ) {//前插入方法     
        // assert succ != null;     
        final Node<E> pred = succ.prev;//获取节点的上一个节点     
        final Node<E> newNode = new Node<>(pred, e, succ);//创建新节点建立上下关系     
        succ.prev = newNode;//succ节点的上一个节点等于新节点(建立双向关系)     
        if (pred == null)//判断上一个节点等于null         
            first = newNode;//first节点等于新节点     
        else //否则上一个节点不等于null         
            pred.next = newNode;
        上一个节点的下一个节点等于新节点
        size++;//元素个数累加     
        modCount++;//操作次数累加 
    }

checkElementIndex()方法源码分析

    private void checkElementIndex(int index) {
        if (!isElementIndex(index))//结果为false,抛异常         
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

isElementIndex()方法源码分析

    private boolean isElementIndex(int index) {
        return index >= 0 && index < size; //判断节点位置大于等于0且小于等于元素个数 
    }

unlink()方法源码分析

    //解除
    E unlink(Node<E> x) {
        // assert x != null;     
        final E element = x.item;//获取x节点的值     
        final Node<E> next = x.next;//获取x节点的下一个节点     
        final Node<E> prev = x.prev;//获取x节点的上一个节点      
        if (prev == null) {//判断上一个节点等于null         
            first = next;//头节点的下一个节点     
        } else {//否则上一个节点不等于null         
            prev.next = next;//上一个节点的下一个节点等于x节点的下一个节点         
            x.prev = null;//将x节点的上一个节点设为null     
        }
        if (next == null) {//判断下一个节点等于null         
            last = prev;//尾节点等于上一个节点     
        } else {//否则下一个节点不等于null        
            next.prev = prev;//下一个节点的上一个节点等于x节点的上一个节点         
            x.next = null;//x节点的下一个节点设为null     
        }
        x.item = null;//将x节点的值设为null     
        size--;//元素个数减一     
        modCount++;//操作次数累加     
        return element;//返回对应的值
    }

add()方法源码分析

LinkedList也是一样有两种插入方式,下面分别进行讲解

    //添加元素
    public boolean add(E e) {
        linkLast(e);//调用尾部插入方法     
        return true;//返回true 
    }

    //尾部插入
    void linkLast(E e) {
        final Node<E> l = last;//获取最后一个节点     
        final Node<E> newNode = new Node<>(l, e, null);//创建新节点且和最后一个节点建立关系     
        last = newNode;//last节点等于新的节点     
        if (l == null)//判断l节点等于null         
            first = newNode;//first节点等于新节点     
        else //否则l节点不等于null         
            l.next = newNode;//l节点的下一个节点等于新节点(奖励双向关系)     
        size++;//元素个数累加     
        modCount++;//操作次数累加 
    }
案例讲解

在这里插入图片描述

    //指定位置插入
    public void add(int index, E element) {
        checkPositionIndex(index);//校验是否越界      
        if (index == size)//判断插入的节点位置等于元素个数         
            linkLast(element);//调用尾部插入方法     
        else //否则节点位置不等于元素个数         
            linkBefore(element, node(index));//获取对应节点后调用linkBefore()插入方法 
    }

    private void checkPositionIndex(int index) {//校验是否越界     
        if (!isPositionIndex(index))//调用判断方法如果返回false,抛异常         
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    private boolean isPositionIndex(int index) {
        return index >= 0 && index <= size;//判断节点位置大于等于0且小于等于元素个数 
    }

    Node<E> node(int index) {//通过节点位置获取节点     
        // assert isElementIndex(index);      
        if (index < (size >> 1)) {//判断节点位置小于元素个数的一半         
            Node<E> x = first;//获取头节点         
            for (int i = 0; i < index; i++)//从0开始正序遍历index次             
                x = x.next;//x节点等于x节点的下一个节点         
            return x;//返回x节点     
        } else { // 否则节点位置大于元素个数的一半         
            Node<E> x = last;//获取尾节点         
            for (int i = size - 1; i > index; i--)//从尾部开始倒叙遍历             
                x = x.prev;//x节点等于x节点的上一个节点         
            return x;//返回x节点     
        }
    }

    void linkBefore(E e, Node<E> succ) {//前插入方法     
        // assert succ != null;     
        final Node<E> pred = succ.prev;//获取节点的上一个节点     
        final Node<E> newNode = new Node<>(pred, e, succ);//创建新节点建立上下关系     
        succ.prev = newNode;//succ节点的上一个节点等于新节点(建立双向关系)     
        if (pred == null)//判断上一个节点等于null         
            first = newNode;//first节点等于新节点     
        else //否则上一个节点不等于null         
            pred.next = newNode;
        上一个节点的下一个节点等于新节点
        size++;//元素个数累加     
        modCount++;//操作次数累加 
    }

在这里插入图片描述
在这里插入图片描述

add()方法:小结
  • LinkedList的添加元素,由于是链表结构,第一个元素为头节点,其他情况向尾部添加元素即可
  • 指定位置添加元素,稍微比较复杂,先判断插入的节点位置是否等于元素个数,如果等于则尾部插入即可,否则,要先去获取指定位置的节点,不过获取节点这也做了优化,判断节点是否大于元素个数的一半,如果小于一半从上往下开始查找,大于元素个数的一半,从最后一个节点开始往上查找,这样遍历的速度就会快很多,获取完节点,然后在获取上一个节点,使其获取的节点及上一个节点于插入的节点建立双向关系。

get()方法源码分析

    //获取值
    public E get(int index) {
        checkElementIndex(index);//校验是否越界     
        return node(index).item;
    }

    private void checkElementIndex(int index) {
        if (!isElementIndex(index))//结果为false,抛异常         
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    private boolean isElementIndex(int index) {
        return index >= 0 && index < size; //判断节点位置大于等于0且小于等于元素个数 
    }

    Node<E> node(int index) {//通过节点位置获取节点     
        // assert isElementIndex(index);      
        if (index < (size >> 1)) {//判断节点位置小于元素个数的一半         
            Node<E> x = first;//获取头节点         
            for (int i = 0; i < index; i++)//从0开始正序遍历index次             
                x = x.next;//x节点等于x节点的下一个节点         
            return x;//返回x节点     
        } else { // 否则节点位置大于元素个数的一半         
            Node<E> x = last;//获取尾节点         
            for (int i = size - 1; i > index; i--)//从尾部开始倒叙遍历             
                x = x.prev;//x节点等于x节点的上一个节点         
            return x;//返回x节点     
        }
    }
案例讲解

在这里插入图片描述

set()方法源码分析

    //修改节点值
    public E set(int index, E element) {
        checkElementIndex(index);//校验节点位置是否越界     
        Node<E> x = node(index);//获取节点位置的节点     
        E oldVal = x.item;//获取节点的值     
        x.item = element;//将节点的值替换为传入的参数     
        return oldVal;//返回原节点的值 
    }

    private void checkElementIndex(int index) {
        if (!isElementIndex(index))//结果为false,抛异常         
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    private boolean isElementIndex(int index) {
        return index >= 0 && index < size; //判断节点位置大于等于0且小于等于元素个数 
    }

    Node<E> node(int index) {//通过节点位置获取节点     
        // assert isElementIndex(index);      
        if (index < (size >> 1)) {//判断节点位置小于元素个数的一半         
            Node<E> x = first;//获取头节点         
            for (int i = 0; i < index; i++)//从0开始正序遍历index次             
                x = x.next;//x节点等于x节点的下一个节点         
            return x;//返回x节点     
        } else { // 否则节点位置大于元素个数的一半         
            Node<E> x = last;//获取尾节点         
            for (int i = size - 1; i > index; i--)//从尾部开始倒叙遍历             
                x = x.prev;//x节点等于x节点的上一个节点         
            return x;//返回x节点     
        }
    }

在这里插入图片描述

get()和set()方法:小结
  • get()方法通过节点的位置获取节点的值,判断节点的位置是否大于元素个数的一半,如果小于一半从上往下开始查找,大于元素个数的一半,从最后一个节点开始往上查找,这样遍历的次速度就会快很多。
  • 既然已经知道了get()方法,set()方法修改就很简单,将获取的节点对应的值替换为要修改的值。

remove()方法源码分析

//通过值删除节点
    public boolean remove(Object o) {
        if (o == null) {//判断值等于null         
            for (Node<E> x = first; x != null; x = x.next) {//从first节点开始遍历             
                if (x.item == null) {//判断x节点的值等于null                 
                    unlink(x);//调用删除方法                 
                    return true;//返回true             
                }
            }
        } else {//否则值不等于null         
            for (Node<E> x = first; x != null; x = x.next) {/从first节点开始遍历
                if (o.equals(x.item)) {//判断参数值等于x节点的值                 
                    unlink(x); //调用删除方法                 
                    return true; //返回true             
                }
            }
        }
        return false; //返回false 
    }

    //解除
    E unlink(Node<E> x) {
        // assert x != null;     
        final E element = x.item;//获取x节点的值     
        final Node<E> next = x.next;//获取x节点的下一个节点     
        final Node<E> prev = x.prev;//获取x节点的上一个节点      
        if (prev == null) {//判断上一个节点等于null         
            first = next;//头节点的下一个节点     
        } else {//否则上一个节点不等于null         
            prev.next = next;//上一个节点的下一个节点等于x节点的下一个节点         
            x.prev = null;//将x节点的上一个节点设为null     
        }
        if (next == null) {//判断下一个节点等于null         
            last = prev;//尾节点等于上一个节点     
        } else {//否则下一个节点不等于null        
            next.prev = prev;//下一个节点的上一个节点等于x节点的上一个节点         
            x.next = null;//x节点的下一个节点设为null     
        }
        x.item = null;//将x节点的值设为null     
        size--;//元素个数减一     
        modCount++;//操作次数累加     
        return element;//返回对应的值
    }
案例讲解

在这里插入图片描述
在这里插入图片描述

    //通过节点位置删除
    public E remove(int index) {
        checkElementIndex(index);//校验是否越界    
        return unlink(node(index));//先获取节点后删除
    }

    Node<E> node(int index) {//通过节点位置获取节点    
        // assert isElementIndex(index);    
        if (index < (size >> 1)) {//判断节点位置小于元素个数的一半        
            Node<E> x = first;//获取头节点        
            for (int i = 0; i < index; i++)//从0开始正序遍历index次            
                x = x.next;//x节点等于x节点的下一个节点        
            return x;//返回x节点    
        } else { // 否则节点位置大于元素个数的一半        
            Node<E> x = last;//获取尾节点        
            for (int i = size - 1; i > index; i--)//从尾部开始倒叙遍历            
                x = x.prev;//x节点等于x节点的上一个节点        
            return x;//返回x节点    
        }
    }
        //解除
    E unlink(Node<E> x) {
        // assert x != null;     
        final E element = x.item;//获取x节点的值     
        final Node<E> next = x.next;//获取x节点的下一个节点     
        final Node<E> prev = x.prev;//获取x节点的上一个节点      
        if (prev == null) {//判断上一个节点等于null         
            first = next;//头节点的下一个节点     
        } else {//否则上一个节点不等于null         
            prev.next = next;//上一个节点的下一个节点等于x节点的下一个节点         
            x.prev = null;//将x节点的上一个节点设为null     
        }
        if (next == null) {//判断下一个节点等于null         
            last = prev;//尾节点等于上一个节点     
        } else {//否则下一个节点不等于null        
            next.prev = prev;//下一个节点的上一个节点等于x节点的上一个节点         
            x.next = null;//x节点的下一个节点设为null     
        }
        x.item = null;//将x节点的值设为null     
        size--;//元素个数减一     
        modCount++;//操作次数累加     
        return element;//返回对应的值
    }

在这里插入图片描述

remove()方法:小结
  • LinkedList删除方法也是非常简单的,有两种方式删除:通过值删除和通过节点位置删除,两个都需要先遍历获得节点,不过通过节点位置删除遍历速度要快些,然后调用删除方法,节点删除也是操作起来很简单。如果删除的是第一个节点,那么下一个节点成为头节点;如果是最后一个节点,上一个节点成为尾节点;如果是中间节点,就让上一个节点和下一个节点建立连接即可。最后把删除节点的连接关系设为null。

知识扩展

看完整个源码,也就知道他们之间的异同点,这里就不再过多的讲解

章节目录

上一篇:集合底层源码分析之Set(五)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值