Java集合系列之List源码分析

博主也是从大学刚刚毕业面试过来的,深知刚刚毕业的我们面试遇到的各种坑,所以今天写一篇面试百分之百会问到的知识点,用最通俗易懂的语言分享给大家。

面试官基本会问我们了解哪些容器啊?

那么我想作为小菜鸟的我们,可以简单的回答一下,我目前碰到的容器有Collection和Map

接下来面试官会问,那你说下他们之间具体的差别吗?

接下来就让我们来看下这些容器吧。

《Java集合系列之List源码分析》

List:有序列表,允许存放重复的元素

其实上图中东西满多的,但是在工作做我们运用最多的就是ArrayList , LinkedList 和Vector(很难用到,但是面试官喜欢问到,为什么呢,因为它是线程安全的,其实就是线程安全版本的Arraylist),所以面试官喜欢听到的也是这些答案。对于集合框架,我会从基础和进阶进行分享,如有错误,希望指出。

  基础版:

 ArrayList

底层是基于Object数组。

优点:查询速度快

缺点:增删速度慢

LinkedList

底层是基于链表

在这个链表上每个节点都有三部分组成,前指针,数据Data,后指针。最后一个节点的后指针指向第一个节点的前指针,形成一个循环。

优点:增删效率高

缺点:查询效率低

利用LinkedList实现栈(stack)、队列(queue)等

经常用在增删操作较多而查询操作很少的情况下:

队列和堆栈。

队列:先进先出的数据结构。

栈:后进先出的数据结构。

注意:使用栈的时候一定不能提供方法让不是最后一个元素的元素获得出栈的机会。

Vector

 是ArrayList的线程安全版,区别是Vector是重量级的组件,使用的时候消耗的资源比较多,所以在现在的代码中基本很少用。

 

结论:在考虑查询速度优先的情况下用ArrayList,在考虑增删改优先的前提下考虑LinkedList,在考虑并发的情况下用Vector(保证线程的安全),在不考虑并发的情况下用ArrayList(不能保证线程的安全)。当然还可以用List list = Collections.synchronizedList(new ArrayList())这个保证线程安全。

 

   进阶版:

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

    /**
     *elementData存储ArrayList内的元素
     * 当 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 将自动扩容
     */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     * 数组的容量
     *
     * @serial
     */
    private int size;


    /**
    *带参构造器
    **/
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }


    /**
    * 无参构造器
    **/
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    /**
    *传入了一个Collection 的子类的构造器
    **/
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }


}

  一、构造器

从代码中可以看出ArrayList源码中提供了三种构造器。

第一种:该构造函数传入一个 int 值作为数组的长度值。如果大于 0,则创建一个长度为出入值大小的新数组。如果等于 0,则使用一个空数组。 如果小于 0,则抛出一个运行时异常( IllegalArgumentException)。

第二种:为无参构造器,直接调用一个Object类型的DEFAULTCAPACITY_EMPTY_ELEMENTDATA数组进行实例化。

    /**
     * Shared empty array instance used for default sized empty instances. We
     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
     * first element is added.
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

由于博主才疏学浅,get不到DEFAULTCAPACITY_EMPTY_ELEMENTDATA这个数据项(哎。。。),小伙伴们可自行百度。反正这个构造器就是调用而已。

第三种:传入了一个Collection 的子类,首先把传入的子类转化为数组,然后判断数组的大小Size,如果不为0,则进入if语句判断 a.getClass 是否等于 Object[].class,其实一般都是相等的,然后把这个数组赋给elementData。如果为0,则用空数组替换。

二、增删改查

           增加:

     public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }


     public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }


    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }


    /**
    *在某一个位子,传入了一个Collection 的子类的构造器
    **/
    public boolean addAll(int index, Collection<? extends E> c) {
        rangeCheckForAdd(index);

        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount

        int numMoved = size - index;
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);

        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }

源码中提供了三种增加的方法,如上图所示

1:add(E e)解析

往list中增加单个元素,可以看出它调用了ensureCapacityInternal()方法,把当前数组的长度+1,然后把值赋给新的数组。然后return true结束。

minCapacity=老数组容量+1;

ensureCapacityInternal的具体方法如下:

  private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

而ensureCapacityInternal又调用了ensureExplicitCapacity()和calculateCapacity()方法。

calculateCapacity具体方法如下:

private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

通过这一步来判断,当前elementData是否为空数组,如果是,则使用 Math.max(DEFAULT_CAPACITY, minCapacity)进行选择一个较大的初始量,其中,DEFAULT_CAPACITY是静态常量10,返回值供ensureExplicitCapacity方法调用。

ensureExplicitCapacity具体方法如下:

  private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

ensureExplicitCapacity中首先让modCount计数+1,其次判断新增元素后的大小minCapacity是否超过当前集合的容量elementData.length,如果超过,则调用grow方法进行扩容。

 其中出现了modCount这个变量,查看官方的解释:

/**
     * The number of times this list has been <i>structurally modified</i>.
     * Structural modifications are those that change the size of the
     * list, or otherwise perturb it in such a fashion that iterations in
     * progress may yield incorrect results.
     *
     * <p>This field is used by the iterator and list iterator implementation
     * returned by the {@code iterator} and {@code listIterator} methods.
     * If the value of this field changes unexpectedly, the iterator (or list
     * iterator) will throw a {@code ConcurrentModificationException} in
     * response to the {@code next}, {@code remove}, {@code previous},
     * {@code set} or {@code add} operations.  This provides
     * <i>fail-fast</i> behavior, rather than non-deterministic behavior in
     * the face of concurrent modification during iteration.
     *
     * <p><b>Use of this field by subclasses is optional.</b> If a subclass
     * wishes to provide fail-fast iterators (and list iterators), then it
     * merely has to increment this field in its {@code add(int, E)} and
     * {@code remove(int)} methods (and any other methods that it overrides
     * that result in structural modifications to the list).  A single call to
     * {@code add(int, E)} or {@code remove(int)} must add no more than
     * one to this field, or the iterators (and list iterators) will throw
     * bogus {@code ConcurrentModificationExceptions}.  If an implementation
     * does not wish to provide fail-fast iterators, this field may be
     * ignored.
     */
    protected transient int modCount = 0;

大概意思就是,使用迭代器遍历的时候,iterator.hasNext()方法和checkForComodification()方法会检查modCount和expectedModCount是否相等,从而判断多环境下,迭代器遍历过程中列表元素是否发生结构性变化,防止List混乱。

扩容机制grow方法如下:

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }



private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

该方法中首先把原来容器的长度赋给oldCapacity,然后定义新的扩容容量:int newCapacity = oldCapacity + (oldCapacity >> 1),oldCapacity >> 1 是位运算的右移操作相当于int newCapacity = oldCapacity + oldCapacity / 2;即容量为原来的1.5倍。接着把newCapacity和minCapacity进行比较,如果还是小于minCapacity,则直接让newCapacity 等于minCapacity,不进行扩容。然后判断的当前的newCapacity是否超过最大的容量 ,如果超过,则调用hugeCapacity方法,hugeCapacity方法就是定义长度而已,不做过多介绍。最后把原数组内容复制到一个新容量中。综上就完成了一次add方法。

 

2:add(int index, E element)解析

该方法的传入参数是位置index和实体element,具体业务代码不做介绍,和上面的add大同小异,对源码进行了简单分析如下:

public void add(int index, E element) {
        rangeCheckForAdd(index);  //rangeCheckForAdd方法判断当前位置是否会越界

        ensureCapacityInternal(size + 1);  // 进行扩容
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;  //把当前值插入数组的index位置
        size++;  //集合拥有的元素个数++
    }

 

3:addAll(Collection<? extends E> c)解析

该方法是增加一个Collection对象,具体业务代码不做介绍,和上面的add大同小异,对源码进行了简单分析如下:

    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray(); //把Collection对象转为arrayList,放入Object数组
        int numNew = a.length; //获取数组a的长度
        ensureCapacityInternal(size + numNew);  // 扩容=最小容量(一般为10)+数组a的长度
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;  //长度增加
        return numNew != 0;
    }

 

删除

1:remove(Int index)解析

删除方法有好几个,具体也不做过多分析,基本都大同小异,就拿其中最重要的这个做下详细分析:

public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

1:首先运行的是rangeCheck方法,判断输入的index是否越界。rangeCheck方法如下:

 private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

2:modCount计数加一

3:把这个位子放入数组中,找出这个位子原本所对应的值 oldValue

4:int numMoved = size – index – 1; 表示的需要移动的元素个数,也就是index后面的所有的元素个数

5:  if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);

表示的是,如果index后面的元素个数大于0(表示删除的不是最后那个),将index后面的所有元素全部往前依次移动一个位置,那么数组容器的最后的位置就会置空,不做强引用,可以GC(java虚拟机的特性垃圾回收机制)掉。

 

2:其他的删除源码简单解析

因为基本和上面的remove大同小异就不做过多分析,简单源码分析:

//删除该元素在数组中第一次出现的位置上的数据。 如果有该元素返回true,如果false。
public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);//根据index删除元素
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}
//不会越界 不用判断 ,也不需要取出该元素。
private void fastRemove(int index) {
    modCount++;//修改modCount
    int numMoved = size - index - 1;//计算要移动的元素数量
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);//以复制覆盖元素 完成删除
    elementData[--size] = null; // clear to let GC do its work  //置空 不再强引用
}

//批量删除
public boolean removeAll(Collection<?> c) {
    Objects.requireNonNull(c);//判空
    return batchRemove(c, false);
}
//批量移动
private boolean batchRemove(Collection<?> c, boolean complement) {
    final Object[] elementData = this.elementData;
    int r = 0, w = 0;//w 代表批量删除后 数组还剩多少元素
    boolean modified = false;
    try {
        //高效的保存两个集合公有元素的算法
        for (; r < size; r++)
            if (c.contains(elementData[r]) == complement) // 如果 c里不包含当前下标元素, 
                elementData[w++] = elementData[r];//则保留
    } finally {
        // Preserve behavioral compatibility with AbstractCollection,
        // even if c.contains() throws.
        if (r != size) { //出现异常会导致 r !=size , 则将出现异常处后面的数据全部复制覆盖到数组里。
            System.arraycopy(elementData, r,
                             elementData, w,
                             size - r);
            w += size - r;//修改 w数量
        }
        if (w != size) {//置空数组后面的元素
            // clear to let GC do its work
            for (int i = w; i < size; i++)
                elementData[i] = null;
            modCount += size - w;//修改modCount
            size = w;// 修改size
            modified = true;
        }
    }
    return modified;
}



    public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

1:还是和删除的时候一样,对当前位置的index做下是否越界判断

2:取出index这个位子的oldValue

3:把新值赋给这个数组的index位子

4:因为改的时候没有引起位子的改变,所以没有改变modcount值

 

   public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

1:判断是否越界

2:获取当前位置的值并返回。

 

总结: 

  • 向ArrayList中add元素的时,Java先计算容量(Capacity)是否适当,若容量不足则进行扩容,并把原数组copy到新数组中,同时,size进行自增1。在删除对象时,先使用拷贝方法把指定index后面的对象前移1位,然后把空出来的位置置null,交给GC,size自减1。
  • 增删改查中 , 增的时候会导致数组扩容(原数组长度的1.5倍)增删的时候会修改modCount值查找和改的时候不修改modCount值。
  • 增删操作和扩容操作会进行arraycopy数组复制操作,因此,增、删都相对低效。 而 改、查是比较高效的操作。

 

 LinkedList

《Java集合系列之List源码分析》

这就是linkedlist最基础的的链表结构图,包含了头指针,data数据,和尾指针。

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{


     /**
     * size是双向链表中节点实例的个数。
     */
    transient int size = 0;

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

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

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

    /**
     * 将一个指定的集合添加到LinkedList中,先完成初始化,在调用添加操作
     */
    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }


    //内部类定义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;
        }
    }

}

 LinkedList中包含了三个属性,分别是size ,first和last,其中first和last分别表示了头结点和尾结点。其中用了内部私有类Node,Node给定三个参数,分别前驱节点,本节点的值,后继结点供前面的first和last使用。

  一、构造器

LinkedList没有长度的概念(底层是用双向链表实现的,没有初始化大小,所以也没有扩容的机制。),所以不存在容量不足的问题,因此不需要提供初始化大小的构造方法,所以只提供了两个构造器,分别是无参构造器和一个传入了collection的有参构造器。

1:linkedList()

创建一个空的列表

2:LinkedList(Collection<? extends E> c)

将一个指定的集合添加到LinkedList中,先完成初始化,再调用addAll()操作,addAll方法如下:

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


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

    //将集合中的元素全部插入到List中,并从指定的index位置开始
    public boolean addAll(int index, Collection<? extends E> c) {
        checkPositionIndex(index);

        Object[] a = c.toArray();  //将集合转化为数组
        int numNew = a.length;  //获取长度
        if (numNew == 0)   
            return false;

        Node<E> pred, succ;
        if (index == size) { // 插入位置如果是size,则在头结点前面插入
            succ = null;
            pred = last;
        } else {  //否则在获取index处的节点插入
            succ = node(index); //获取位置为index的结点元素,并赋值给succ
            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;
        }

        if (succ == null) { //如果插入位置在尾部,重置last节点
            last = pred;
        }
     
        else {  //否则,将插入的链表与先前链表连接起来
            pred.next = succ;
            succ.prev = pred;
        }

        size += numNew;
        modCount++;
        return true;
}

1.从中可以看出先调用第一个addAll接口,把Collection插入的的链表尾部,将集合中的元素全部插入到List中。

2.判断插入位置是否溢出

3.把集合转为数组保存到一个Object的新数组中,并计算它的长度,如果为长度为0,表示这个collection是空的,return false

4.得到插入位置的前驱和后继节点

5.遍历数据,将数据插入到指定位置

  二、增删改查

增加:

1:add(E e)解析

    public boolean add(E e) {
        linkLast(e);//这里就只调用了这一个方法
        return true;
    }


     /**
     *作为最后一个节点插入
     */
    void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;//新建一个节点
        if (l == null)
            first = newNode;
        else
            l.next = newNode;//后继元素指向下一个元素
        size++;  //list容量+1
        modCount++;
    }

2:add(int index,E e)解析

 /**
  *在指定位置插入指定元素
 **/
 public void add(int index, E element) {
       checkPositionIndex(index); //检查是否越界了

        if (index == size)//添加在链表尾部
            linkLast(element);
        else//添加在链表中间
            linkBefore(element, node(index));
    }


     /**
     * 在一个非空的节点前插入元素
     *linkBefore方法需要给定两个参数,一个插入节点的值,一个指定的node
     */
      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节点的前面添加一个node
        if (pred == null) //如果该node节点的前驱为null ,则把插入的元素作为第一个节点
            first = newNode;
        else
            pred.next = newNode; //在succ前面那个节点的后驱指向当前插入元素
        size++;
        modCount++;
    }

3:addAll(Collection<? extends E> c)和addAll(int index, Collection c)解析

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

    //将集合中的元素全部插入到List中,并从指定的index位置开始
    public boolean addAll(int index, Collection<? extends E> c) {
        checkPositionIndex(index);

        Object[] a = c.toArray();  //将集合转化为数组
        int numNew = a.length;  //获取长度
        if (numNew == 0)   
            return false;

        Node<E> pred, succ;
        if (index == size) { // 插入位置如果是size,则在头结点前面插入
            succ = null;
            pred = last;
        } else {  //否则在获取index处的节点插入
            succ = node(index); //获取位置为index的结点元素,并赋值给succ
            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;
        }

        if (succ == null) { //如果插入位置在尾部,重置last节点
            last = pred;
        }
     
        else {  //否则,将插入的链表与先前链表连接起来
            pred.next = succ;
            succ.prev = pred;
        }

        size += numNew;
        modCount++;
        return true;
}

其中addAll(Collection<? extends E> c)只是调用了addAll(int index, Collection c),在最后的位置插入collection。

4.addFirst(E e)和addLast(E e)解析

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

    private void linkFirst(E e) {
        final Node<E> f = first;
        final Node<E> newNode = new Node<>(null, e, f);//新建节点,以头节点为后继节点
        first = newNode;
        //如果链表为空,last节点也指向该节点
        if (f == null)
            last = newNode;
        //将头节点的前驱指针指向新节点,也就是指向前一个元素
        else
            f.prev = newNode;
        size++;
        modCount++;
    }

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

addfisrt和addlist基本差不多,改变了插入位置,基本都是套用linklast。

改:

set(int index, E element)解析:

    /**
     * 在指定位置插入元素
     */
    public E set(int index, E element) {
        checkElementIndex(index); //判断是否越界
        Node<E> x = node(index); //找到对应index位置的节点
        E oldVal = x.item;  //把指定节点的值赋黑oldVal
        x.item = element;   //把新值给该节点
        return oldVal;
    }

查找:

1.get(int index)解析:

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

 2.getFirst(),peekFirst(),getLast()和peekLast()解析:

   /**
    *获取第一个节点的元素
    */
   public E getFirst() {
        final Node<E> f = first;
        if (f == null) 
            throw new NoSuchElementException();  //如果第一个元素为空则抛出异常
        return f.item;
    }
    

    /**
     * 查找在此堆栈顶部的对象
     */
    public E peekFirst() {
        final Node<E> f = first;
        return (f == null) ? null : f.item; //如果第一个元素为空则返回null否则返回该元素
     }


    /**
     * 获取最后一个节点的元素
     */
    public E getLast() {
        final Node<E> l = last;
        if (l == null)  //如果最后一个元素为空则抛出异常
            throw new NoSuchElementException();
        return l.item;
    }

     /**
     * 检索队列的最后一个元素(
     */
    public E peekLast() {
        final Node<E> l = last;
        return (l == null) ? null : l.item;//如果最后一个元素为空则返回null否则返回该元素
    }

 区别:getFirst(),peekFirst(),getLast()和peekLast(),这四个方法的区别在于对链表为空时的处理。一个是抛出异常,一个是返回null

 删除:

1.remove(Object o)解析:

/**
* 删除元素
*/
public boolean remove(Object o) {
      
        if (o == null) {   //如果删除的元素为null
            for (Node<E> x = first; x != null; x = x.next) { //从链表的头结点开始遍历
               
                if (x.item == null) { //找到这个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) {
        //定义传入元素的前驱后驱和元素值
        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;
    }

2.remove(int index)解析:

  /**
    *删除指定位置的元素
    */
  public E remove(int index) {
        checkElementIndex(index); //判断是否越界
        return unlink(node(index));  //删除元素
    }

3.remove(),removeFirst(),pop(),removeLast(),pollLast()解析:

public E pop() {
        return removeFirst(); //删除第一个元素
    }

public E remove() {
        return removeFirst(); //删除第一个元素
    }

public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException(); //如果第一个元素为null爆出异常
        return unlinkFirst(f);
    }


    /**
     * 删除不为空的节点
     */
    private E unlinkFirst(Node<E> f) {
        //定义节点的基本信息   获取该节点的后驱和值
        final E element = f.item;
        final Node<E> next = f.next;
        f.item = null; 
        f.next = null; // help GC   
        first = next;
        if (next == null) //如果该元素的后面那个元素为null
            last = null;  //设置last为null
        else
            next.prev = null; //否则把该元素的后面的那个元素作为链表的头结点
        size--;
        modCount++;
        return element;
    }


public E removeLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();  //如果最后一个元素为null报错
        return unlinkLast(l); //删除元素
    }

public E pollLast() {
        final Node<E> l = last;
        return (l == null) ? null : unlinkLast(l); //如果最后一个元素为null那么返回null,否则删除
    }

区别: removeLast()在链表为空时将抛出NoSuchElementException,而pollLast()方法返回null。 

 

总结: 

  • LinkedList 是通过双向链表去实现的。
  • 它有一个非常重要的数据结构格式Entity:前驱后驱和属性值即当前节点所包含的值,上一个节点,下一个节点。
  • 它不存在LinkedList容量不足的问题,所以不像arrayList一样需要扩容
  • 由于LinkedList实现了Deque。它提供了不同的插入、移除和检查元素的方法。每种方法都存在两种形式:一种形式在操作失败时抛出异常,另一种形式返回一个null

Vector

 public synchronized void insertElementAt(E obj, int index) {
        modCount++;
        if (index > elementCount) {
            throw new ArrayIndexOutOfBoundsException(index
                                                     + " > " + elementCount);
        }
        ensureCapacityHelper(elementCount + 1);
        System.arraycopy(elementData, index, elementData, index + 1, elementCount - index);
        elementData[index] = obj;
        elementCount++;
    }

  public synchronized void addElement(E obj) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = obj;
    }

 public synchronized void setSize(int newSize) {
        modCount++;
        if (newSize > elementCount) {
            ensureCapacityHelper(newSize);
        } else {
            for (int i = newSize ; i < elementCount ; i++) {
                elementData[i] = null;
            }
        }
        elementCount = newSize;
    }


public synchronized boolean removeElement(Object obj) {
        modCount++;
        int i = indexOf(obj);
        if (i >= 0) {
            removeElementAt(i);
            return true;
        }
        return false;
    }


 public synchronized void removeAllElements() {
        modCount++;
        // Let gc do its work
        for (int i = 0; i < elementCount; i++)
            elementData[i] = null;

        elementCount = 0;
    }

vector和arraylist基本一样,唯一不同的就是线程安全了,从源码中可以看出增删改前都加了synchronized关键词来保证线程安全。

结语

后续的set和map将会在以后的博客中更新,如文中出现理解不当的,希望指出。

    原文作者:java集合源码分析
    原文地址: https://blog.csdn.net/qq_29422747/article/details/84322244
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞