List相关源码解析--这一篇全了解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/w372426096/article/details/82586738

ArrayList源码解析(基于JDK1.7,JDK1.8比较)

ArrayList属性

//JDK1.7

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
    private static final long serialVersionUID = 8683452581122892189L;
    private transient Object[] elementData;
    private int size;
         private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
         //省略方法
}

//JDK1.8
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, Serializable {
             // 序列化id
             private static final long serialVersionUID = 8683452581122892189L;
             // 默认初始的容量
             private static final int DEFAULT_CAPACITY = 10;
             // 一个空对象
             private static final Object[] EMPTY_ELEMENTDATA = new Object[0];
             // 一个空对象,如果使用默认构造函数创建,则默认对象内容默认是该值
             private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0];
             // 当前数据对象存放地方,当前对象不参与序列化
             transient Object[] elementData;//非私有化以简化嵌套类访问
             // 当前数组长度
             private int size;
             // 数组最大长度
             private static final int MAX_ARRAY_SIZE = 2147483639;

             // 省略方法。。

}

ArrayList构造函数

//JDK1.7
/**
  * 构造一个初始容量为 10 的空列表
  */
    public ArrayList() {
        this(10);
    }
    /**
     * 构造一个具有指定初始容量的空列表。
     */
public ArrayList(int initialCapacity) {
    super();
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
        this.elementData = new Object[initialCapacity];
    }

    /**
     *  构造一个包含指定 collection 的元素的列表,这些元素是按照该 collection 的迭代器返回它们的顺序排列的。
     */
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        size = elementData.length;
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    }

//JDK1.8
无参构造函数
如果不传入参数,则使用默认无参构建方法创建ArrayList对象,如下:
   public ArrayList() {

             this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;

   }

注意:此时我们创建的ArrayList对象中的elementData中的长度是1,size是0,当进行第一次add的时候,elementData将会变成默认的长度:10.

带int类型的构造函数

如果传入参数,则代表指定ArrayList的初始数组长度,传入参数如果是大于等于0,则使用用户的参数初始化,如果用户传入的参数小于0,则抛出异常,构造方法如下:       

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

带Collection对象的构造函数

1)将collection对象转换成数组,然后将数组的地址的赋给elementData。

2)更新size的值,同时判断size的大小,如果是size等于0,直接将空对象EMPTY_ELEMENTDATA的地址赋给elementData

3)如果size的值大于0,则执行Arrays.copy方法,把collection对象的内容(可以理解为深拷贝)copy到elementData中。

注意:this.elementData = arg0.toArray(); 这里执行的简单赋值时浅拷贝,所以要执行Arrays,copy 做深拷贝

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

新增方法

//JDK1.7

ArrayList提供了add(E e)、add(int index, E element)、addAll(Collection<? extends E> c)、addAll(int index, Collection<? extends E> c)、set(int index, E element)这个五个方法来实现ArrayList增加。

     add(E e):将指定的元素添加到此列表的尾部。

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

    这里ensureCapacity()方法是对ArrayList集合进行扩容操作,elementData(size++) = e,将列表末尾元素指向e。

     add(int index, E element):将指定的元素插入此列表中的指定位置。

public void add(int index, E element) {
        //判断索引位置是否正确
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
        //扩容检测
        ensureCapacity(size+1); 
        /*
         * 对源数组进行复制处理(位移),从index + 1到size-index。
         * 主要目的就是空出index位置供数据插入,
         * 即向右移动当前位于该位置的元素以及所有后续元素。
         */
        System.arraycopy(elementData, index, elementData, index + 1,size - index);
        //在指定位置赋值
        elementData[index] = element;
        size++;
}

      在这个方法中最根本的方法就是System.arraycopy()方法,该方法的根本目的就是将index位置空出来以供新数据插入,这里需要进行数组数据的右移,这是非常麻烦和耗时的,所以如果指定的数据集合需要进行大量插入(中间插入)操作,推荐使用LinkedList。

      addAll(Collection<? extends E> c):按照指定 collection 的迭代器所返回的元素顺序,将该 collection 中的所有元素添加到此列表的尾部。

public boolean addAll(Collection<? extends E> c) {
        // 将集合C转换成数组
        Object[] a = c.toArray();
        int numNew = a.length;
        // 扩容处理,大小为size + numNew
        ensureCapacity(size + numNew); // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }

    这个方法无非就是使用System.arraycopy()方法将C集合(先准换为数组)里面的数据复制到elementData数组中。这里就稍微介绍下System.arraycopy(),因为下面还将大量用到该方法。该方法的原型为:public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)。它的根本目的就是进行数组元素的复制。即从指定源数组中复制一个数组,复制从指定的位置开始,到目标数组的指定位置结束。将源数组src从srcPos位置开始复制到dest数组中,复制长度为length,数据从dest的destPos位置开始粘贴。

     addAll(int index, Collection<? extends E> c):从指定的位置开始,将指定 collection 中的所有元素插入到此列表中。

public boolean addAll(int index, Collection<? extends E> c) {
        //判断位置是否正确
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
        //转换成数组
        Object[] a = c.toArray();
        int numNew = a.length;
        //ArrayList容器扩容处理
        ensureCapacity(size + numNew); // Increments modCount
        //ArrayList容器数组向右移动的位置
        int numMoved = size - index;
        //如果移动位置大于0,则将ArrayList容器的数据向右移动numMoved个位置,确保增加的数据能够增加
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew, numMoved);
        //添加数组
        System.arraycopy(a, 0, elementData, index, numNew);
        //容器容量变大
        size += numNew;  
        return numNew != 0;

}
//JDK1.8
与JDK1.7的方法基本相同,主要不同点主要在对ArrayList集合进行扩容操作的方法上
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++;
}

执行逻辑如下:

1)确保数插入的位置小于等于当前数组长度,并且不小于0,否则抛出异常

2)确保数组已使用长度(size)加1之后足够存下 下一个数据

3)修改次数(modCount)标识自增1,如果当前数组已使用长度(size)加1后的大于当前的数组长度,则调用grow方法,增长数组

4)grow方法会将当前数组的长度变为原来容量的1.5倍。

5)确保有足够的容量之后,使用System.arraycopy 将需要插入的位置(index)后面的元素统统往后移动一位。

6)将新的数据内容存放到数组的指定位置(index)上

扩容方法

//JDK1.7
public void ensureCapacity(int minCapacity) {
        //修改计时器
        modCount++;
        //ArrayList容量大小
        int oldCapacity = elementData.length;
        /*
         * 若当前需要的长度大于当前数组的长度时,进行扩容操作
         */
        if (minCapacity > oldCapacity) {
            Object oldData[] = elementData;
            //计算新的容量大小,为当前容量的1.5倍
            int newCapacity = (oldCapacity * 3) / 2 + 1;
            if (newCapacity < minCapacity)
                newCapacity = minCapacity;
            //数组拷贝,生成新的数组
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
}
//JDK1.8
private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        ensureExplicitCapacity(minCapacity);

}
private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
}
private void grow(int arg0) {
      int arg1 = this.elementData.length;
      int arg2 = arg1 + (arg1 >> 1);
      if (arg2 - arg0 < 0) {
           arg2 = arg0;
      }
      if (arg2 - 2147483639 > 0) {
           arg2 = hugeCapacity(arg0);
      }
      this.elementData = Arrays.copyOf(this.elementData, arg2);

}

在这里有一个疑问,为什么每次扩容处理会是1.5倍,而不是2.5、3、4倍呢?通过google查找,发现1.5倍的扩容是最好的倍数。因为一次性扩容太大(例如2.5倍)可能会浪费更多的内存(1.5倍最多浪费33%,而2.5被最多会浪费60%,3.5倍则会浪费71%……)。但是一次性扩容太小,需要多次对数组重新分配内存,对性能消耗比较严重。所以1.5倍刚刚好,既能满足性能需求,也不会造成很大的内存消耗。

Get方法,Get方法,Contains方法

//JDK1.7

public E get(int index) {
        rangeCheck(index);
        return elementData(index);
}

@SuppressWarnings("unchecked")
    E elementData(int index) {
        return (E) elementData[index];
}

public E set(int index, E element) {
        //检测插入的位置是否越界
        RangeCheck(index);
        E oldValue = (E) elementData[index];
        //替代
        elementData[index] = element;
        return oldValue;
}

public boolean contains(Object o) {
        return indexOf(o) >= 0;

}
//JDK1.8
public E get(int index) {
   rangeCheck(index);
   checkForComodification();
   return ArrayList.this.elementData(offset + index);

}
public E set(int index, E element) {
        rangeCheck(index);
        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
}

public boolean contains(Object o) {
        return indexOf(o) >= 0;
}

final void checkForComodification() {
      if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

Remove方法

//JDK1.7

public E remove(int index) {
        //位置验证
        RangeCheck(index);
        modCount++;
        //需要删除的元素
        E oldValue = (E) elementData[index];  
        //向左移的位数
        int numMoved = size - index - 1;
        //若需要移动,则想左移动numMoved位
        if (numMoved > 0)
            System.arraycopy(elementData, index + 1, elementData, index,numMoved);
        //置空最后一个元素
        elementData[--size] = null; // Let gc do its work
        return oldValue;
}
public boolean remove(Object o) {
        //因为ArrayList中允许存在null,所以需要进行null判断
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    //移除这个位置的元素
                    fastRemove(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++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,numMoved);
        elementData[--size] = null; // Let gc do its work
}
protected void removeRange(int fromIndex, int toIndex) {
        modCount++;
        int numMoved = size - toIndex;
        System.arraycopy(elementData, toIndex, elementData, fromIndex, numMoved);
        // Let gc do its work
        int newSize = size - (toIndex - fromIndex);
        while (size != newSize)
            elementData[--size] = null;
}

public boolean removeAll(Collection<?> c) {
        boolean modified = false;
        Iterator<?> e = iterator();
        while (e.hasNext()) {
            if (c.contains(e.next())) {
                e.remove();
                modified = true;
            }
        }
        return modified;

}

//JDK1.8

基本没有变化

trimToSize方法

//JDK1.7

public void trimToSize() {
        modCount++;
        int oldCapacity = elementData.length;
        if (size < oldCapacity) {
            elementData = Arrays.copyOf(elementData, size);
        }
}
//JDK1.8
public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = (size == 0)? EMPTY_ELEMENTDATA: Arrays.copyOf(elementData, size);
        }
}

1)修改次数加1

2)将elementData中空余的空间(包括null值)去除,例如:数组长度为10,其中只有前三个元素有值,其他为空,那么调用该方法之后,数组的长度变为3.

iterator方法

interator方法返回的是一个内部类,由于内部类的创建默认含有外部的this指针,所以这个内部类可以调用到外部类的属性。

public Iterator<E> iterator() {

    return new Itr();

}

一般的话,调用完iterator之后,我们会使用iterator做遍历,这里使用next做遍历的时候有个需要注意的地方,就是调用next的时候,可能会引发ConcurrentModificationException,当修改次数,与期望的修改次数(调用iterator方法时候的修改次数)不一致的时候,会发生该异常,详细我们看一下代码实现:
  

@SuppressWarnings("unchecked")
public E next() {
    checkForComodification();
    int i = cursor;
    if (i >= size)
        throw new NoSuchElementException();
    Object[] elementData = ArrayList.this.elementData;
    if (i >= elementData.length)
        throw new ConcurrentModificationException();
    cursor = i + 1;
    return (E) elementData[lastRet = i];
}

expectedModCount这个值是在用户调用ArrayList的iterator方法时候确定的,但是在这之后用户add,或者remove了ArrayList的元素,那么modCount就会改变,那么这个值就会不相等,将会引发ConcurrentModificationException异常,这个是在多线程使用情况下,比较常见的一个异常。
 

 final void checkForComodification() {
     if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

System.arraycopy 方法

参数

说明

src

原数组

srcPos

原数组

dest

目标数组

destPos

目标数组的起始位置

length

要复制的数组元素的数目

Arrays.copyOf方法

original - 要复制的数组 
newLength - 要返回的副本的长度 
newType - 要返回的副本的类型

其实Arrays.copyOf底层也是调用System.arraycopy实现的源码如下:

//基本数据类型(其他类似byte,short···)

public static int[] copyOf(int[] original, int newLength) {
        int[] copy = new int[newLength];
        System.arraycopy(original, 0, copy, 0,Math.min(original.length, newLength));
        return copy;
}

总结

ArrayList总体来说比较简单,不过ArrayList还有以下一些特点:

  • ArrayList自己实现了序列化和反序列化的方法,因为它自己实现了 private void writeObject(java.io.ObjectOutputStream s)和 private void readObject(java.io.ObjectInputStream s) 方法
  • ArrayList基于数组方式实现,无容量的限制(会扩容)
  • 添加元素时可能要扩容(所以最好预判一下),删除元素时不会减少容量(若希望减少容量,trimToSize()),删除元素时,将删除掉的位置元素置为null,下次gc就会回收这些元素所占的内存空间
  • 线程不安全
  • add(int index, E element):添加元素到数组中指定位置的时候,需要将该位置及其后边所有的元素都整块向后复制一位
  • get(int index):获取指定位置上的元素时,可以通过索引直接获取(O(1))
  • remove(Object o)需要遍历数组
  • remove(int index)不需要遍历数组,只需判断index是否符合条件即可,效率比remove(Object o)高
  • contains(E)需要遍历数组
  • 使用iterator遍历可能会引发多线程异常

整体来看JDK1.8较JDK1.7来看性能都更好,更加严谨。

LinkedList源码解析(基于JDK1.7)

一、概述

         LinkedList与ArrayList一样实现List接口,只是ArrayList是List接口的大小可变数组的实现,LinkedList是List接口链表的实现。基于链表实现的方式使得LinkedList在插入和删除时更优于ArrayList,而随机访问则比ArrayList逊色些。

         LinkedList实现所有可选的列表操作,并允许所有的元素包括null。

除了实现 List 接口外,LinkedList 类还为在列表的开头及结尾 get、remove 和 insert 元素提供了统一的命名方法。这些操作允许将链接列表用作堆栈、队列或双端队列。此类实现 Deque 接口,为 add、poll 提供先进先出队列操作,以及其他堆栈和双端队列操作。

         所有操作都是按照双重链接列表的需要执行的。在列表中编索引的操作将从开头或结尾遍历列表(从靠近指定索引的一端)。同时,与ArrayList一样此实现不是同步的。

二、源码分析

2.1、定义

       首先我们先看LinkedList的定义:

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

       从这段代码中我们可以清晰地看出LinkedList继承AbstractSequentialList,实现List、Deque、Cloneable、Serializable。其中AbstractSequentialList提供了 List 接口的骨干实现,从而最大限度地减少了实现受“连续访问”数据存储(如链接列表)支持的此接口所需的工作,从而以减少实现List接口的复杂度。Deque一个线性 collection,支持在两端插入和移除元素,定义了双端队列的操作。

2.2、属性

在LinkedList中提供了两个基本属性size、header。

private transient Entry<E> header = new Entry<E>(null, null, null);  
private transient int size = 0;  
    //其中size表示的LinkedList的大小,header表示链表的表头,Entry为节点对象。
 private static class Entry<E> {  
        E element;        //元素节点  
        Entry<E> next;    //下一个元素  
        Entry<E> previous;  //上一个元素  
        Entry(E element, Entry<E> next, Entry<E> previous) {  
            this.element = element;  
            this.next = next;  
            this.previous = previous;  
        }  
}  

       上面为Entry对象的源代码,Entry为LinkedList的内部类,它定义了存储的元素。该元素的前一个元素、后一个元素,这是典型的双向链表定义方式。

2.3、构造方法

       LinkedList提高了两个构造方法:LinkedLis()和LinkedList(Collection<? extends E> c)。

/** 构造一个空列表。 
  */  
public LinkedList() {  
     header.next = header.previous = header;  
}  

/** 
 *  构造一个包含指定 collection 中的元素的列表,这些元素按其 collection 的迭代器返回的顺序排列。 
*/  
public LinkedList(Collection<? extends E> c) {  
   this();  
   addAll(c);  
}  

       LinkedList()构造一个空列表。里面没有任何元素,仅仅只是将header节点的前一个元素、后一个元素都指向自身。

       LinkedList(Collection<? extends E> c): 构造一个包含指定 collection 中的元素的列表,这些元素按其 collection 的迭代器返回的顺序排列。该构造函数首先会调用LinkedList(),构造一个空列表,然后调用了addAll()方法将Collection中的所有元素添加到列表中。以下是addAll()的源代码:

/** 
  *  添加指定 collection 中的所有元素到此列表的结尾,顺序是指定 collection 的迭代器返回这些元素的顺序。 
  */  
public boolean addAll(Collection<? extends E> c) {  
    return addAll(size, c);  
}  
/** 
  * 将指定 collection 中的所有元素从指定位置开始插入此列表。其中index表示在其中插入指定collection中第一个元素的索引 
  */  
public boolean addAll(int index, Collection<? extends E> c) {  
    //若插入的位置小于0或者大于链表长度,则抛出IndexOutOfBoundsException异常  
    if (index < 0 || index > size)  
       throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);  
    Object[] a = c.toArray();  
    int numNew = a.length;    //插入元素的个数  
    //若插入的元素为空,则返回false  
    if (numNew == 0)  
        return false;  
    //modCount:在AbstractList中定义的,表示从结构上修改列表的次数  
    modCount++;  
    //获取插入位置的节点,若插入的位置在size处,则是头节点,否则获取index位置处的节点  
    Entry<E> successor = (index == size ? header : entry(index));  
    //插入位置的前一个节点,在插入过程中需要修改该节点的next引用:指向插入的节点元素  
     Entry<E> predecessor = successor.previous;  
     //执行插入动作  
     for (int i = 0; i < numNew; i++) {  
        //构造一个节点e,这里已经执行了插入节点动作同时修改了相邻节点的指向引用  
        Entry<E> e = new Entry<E>((E) a[i], successor, predecessor);  
        //将插入位置前一个节点的下一个元素引用指向当前元素  
        predecessor.next = e;  
        //修改插入位置的前一个节点,这样做的目的是将插入位置右移一位,保证后续的元素是插在该元素的后面,确保这些元素的顺序  
        predecessor = e;  
     }  
     successor.previous = predecessor;  
     //修改容量大小  
     size += numNew;  
     return true;  
    }  

       在addAll()方法中,涉及到了两个方法,一个是entry(int index),该方法为LinkedList的私有方法,主要是用来查找index位置的节点元素。

/** 
 * 返回指定位置(若存在)的节点元素 
 */  
private Entry<E> entry(int index) {  
    if (index < 0 || index >= size)  
       throw new IndexOutOfBoundsException("Index: " + index + ", Size: "   + size);  
     //头部节点  
     Entry<E> e = header;  
     //判断遍历的方向  
     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是双向链表,这也是在构造方法中为什么需要将header的前、后节点均指向自己。

       如果对数据结构有点了解,对上面所涉及的内容应该问题,我们只需要清楚一点:LinkedList是双向链表,其余都迎刃而解。

由于篇幅有限,下面将就LinkedList中几个常用的方法进行源码分析。

2.4、增加方法

       add(E e): 将指定元素添加到此列表的结尾。

public boolean add(E e) {  
    addBefore(e, header);  
        return true;  
}  

       该方法调用addBefore方法,然后直接返回true,对于addBefore()而已,它为LinkedList的私有方法。

private Entry<E> addBefore(E e, Entry<E> entry) {  
        //利用Entry构造函数构建一个新节点 newEntry,  
        Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);  
        //修改newEntry的前后节点的引用,确保其链表的引用关系是正确的  
        newEntry.previous.next = newEntry;  
        newEntry.next.previous = newEntry;  
        //容量+1  
        size++;  
        //修改次数+1  
        modCount++;  
        return newEntry;  
}  

       在addBefore方法中无非就是做了这件事:构建一个新节点newEntry,然后修改其前后的引用。

       LinkedList还提供了其他的增加方法:

       add(int index, E element):在此列表中指定的位置插入指定的元素。

       addAll(Collection<? extends E> c):添加指定 collection 中的所有元素到此列表的结尾,顺序是指定 collection 的迭代器返回这些元素的顺序。

       addAll(int index, Collection<? extends E> c):将指定 collection 中的所有元素从指定位置开始插入此列表。

       AddFirst(E e): 将指定元素插入此列表的开头。

       addLast(E e): 将指定元素添加到此列表的结尾。

2.5、移除方法

       remove(Object o):从此列表中移除首次出现的指定元素(如果存在)。该方法的源代码如下:

public boolean remove(Object o) {  
        if (o==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) {  
                if (o.equals(e.element)) {  
                    remove(e);  
                    return true;  
                }  
            }  
        }  
        return false;  
    }  

       该方法首先会判断移除的元素是否为null,然后迭代这个链表找到该元素节点,最后调用remove(Entry<E> e),remove(Entry<E> e)为私有方法,是LinkedList中所有移除方法的基础方法,如下:

private E remove(Entry<E> e) {  
        if (e == header)  
            throw new NoSuchElementException();  
        //保留被移除的元素:要返回  
        E result = e.element;  
        //将该节点的前一节点的next指向该节点后节点  
        e.previous.next = e.next;  
        //将该节点的后一节点的previous指向该节点的前节点  
        //这两步就可以将该节点从链表从除去:在该链表中是无法遍历到该节点的  
        e.next.previous = e.previous;  
        //将该节点归空  
        e.next = e.previous = null;  
        e.element = null;  
        size--;  
        modCount++;  
        return result;  
    }  

       其他的移除方法:

       clear(): 从此列表中移除所有元素。

       remove():获取并移除此列表的头(第一个元素)。

       remove(int index):移除此列表中指定位置处的元素。

       remove(Objec o):从此列表中移除首次出现的指定元素(如果存在)。

       removeFirst():移除并返回此列表的第一个元素。

       removeFirstOccurrence(Object o):从此列表中移除第一次出现的指定元素(从头部到尾部遍历列表时)。

       removeLast():移除并返回此列表的最后一个元素。

       removeLastOccurrence(Object o):从此列表中移除最后一次出现的指定元素(从头部到尾部遍历列表时)。

2.5、查找方法

       对于查找方法的源码就没有什么好介绍了,无非就是迭代,比对,然后就是返回当前值。

       get(int index):返回此列表中指定位置处的元素。

       getFirst():返回此列表的第一个元素。

       getLast():返回此列表的最后一个元素。

       indexOf(Object o):返回此列表中首次出现的指定元素的索引,如果此列表中不包含该元素,则返回 -1。

       lastIndexOf(Object o):返回此列表中最后出现的指定元素的索引,如果此列表中不包含该元素,则返回 -1。

Vector源码解析(基于JDK1.7)

一、Vector简介

        Vector可以实现可增长的对象数组。与数组一样,它包含可以使用整数索引进行访问的组件。不过,Vector的大小是可以增加或者减小的,以便适应创建Vector后进行添加或者删除操作。

        Vector实现List接口,继承AbstractList类,所以我们可以将其看做队列,支持相关的添加、删除、修改、遍历等功能。

        Vector实现RandmoAccess接口,即提供了随机访问功能,提供提供快速访问功能。在Vector我们可以直接访问元素。

        Vector 实现了Cloneable接口,支持clone()方法,可以被克隆。

public class Vector<E> extends AbstractList<E>  
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable  
//Vector提供了四个构造函数:
/** 
 * 构造一个空向量,使其内部数据数组的大小为 10,其标准容量增量为零。 
 */  
public Vector() {  
      this(10);  
}  

/** 
  * 构造一个包含指定 collection 中的元素的向量,这些元素按其 collection 的迭代器返回元素的顺序排列。 
  */  
    public Vector(Collection<? extends E> c) {  
        elementData = c.toArray();  
        elementCount = elementData.length;  
        // c.toArray might (incorrectly) not return Object[] (see 6260652)  
        if (elementData.getClass() != Object[].class)  
            elementData = Arrays.copyOf(elementData, elementCount,  
                    Object[].class);  
    }  
    /** 
     * 使用指定的初始容量和等于零的容量增量构造一个空向量。 
     */  
    public Vector(int initialCapacity) {  
        this(initialCapacity, 0);  
    }  

    /** 
     *  使用指定的初始容量和容量增量构造一个空的向量。 
     */  
public Vector(int initialCapacity, int capacityIncrement) {  
    super();  
    if (initialCapacity < 0)  
      throw new IllegalArgumentException("Illegal Capacity: "+  initialCapacity);  
    this.elementData = new Object[initialCapacity];  
    this.capacityIncrement = capacityIncrement;  
}  

        在成员变量方面,Vector提供了elementData , elementCount, capacityIncrement三个成员变量。其中

        elementData :"Object[]类型的数组",它保存了Vector中的元素。按照Vector的设计elementData为一个动态数组,可以随着元素的增加而动态的增长,其具体的增加方式后面提到(ensureCapacity方法)。如果在初始化Vector时没有指定容器大小,则使用默认大小为10.

        elementCount:Vector 对象中的有效组件数。

        capacityIncrement:向量的大小大于其容量时,容量自动增加的量。如果在创建Vector时,指定了capacityIncrement的大小;则,每次当Vector中动态数组容量增加时>,增加的大小都是capacityIncrement。如果容量的增量小于等于零,则每次需要增大容量时,向量的容量将增大一倍。

        同时Vector是线程安全的!

二、源码解析

        对于源码的解析,LZ在这里只就增加(add)删除(remove)两个方法进行讲解。

2.1增加:add(E e)

        add(E e):将指定元素添加到此向量的末尾。

public synchronized boolean add(E e) {  
        modCount++;       
        ensureCapacityHelper(elementCount + 1);    //确认容器大小,如果操作容量则扩容操作  
        elementData[elementCount++] = e;   //将e元素添加至末尾  
        return true;  
}  

        这个方法相对而言比较简单,具体过程就是先确认容器的大小,看是否需要进行扩容操作,然后将E元素添加到此向量的末尾。

private void ensureCapacityHelper(int minCapacity) {  
  //如果  
  if (minCapacity - elementData.length > 0)  
      grow(minCapacity);  
  }  
/** 
  * 进行扩容操作 
  * 如果此向量的当前容量小于minCapacity,则通过将其内部数组替换为一个较大的数组俩增加其容量。 
  * 新数据数组的大小姜维原来的大小 + capacityIncrement, 
  * 除非 capacityIncrement 的值小于等于零,在后一种情况下,新的容量将为原来容量的两倍,不过,如果此大小仍然小于 minCapacity,则新容量将为 minCapacity。 
  */  
    private void grow(int minCapacity) {  
        int oldCapacity = elementData.length;     //当前容器大小  
        /* 
         * 新容器大小 
         * 若容量增量系数(capacityIncrement) > 0,则将容器大小增加到capacityIncrement 
         * 否则将容量增加一倍 
         */  
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?   capacityIncrement : oldCapacity);  
        if (newCapacity - minCapacity < 0)  
            newCapacity = minCapacity;  
        if (newCapacity - MAX_ARRAY_SIZE > 0)  
            newCapacity = hugeCapacity(minCapacity);  
        elementData = Arrays.copyOf(elementData, newCapacity);  
    }  
/** 
  * 判断是否超出最大范围 
  * MAX_ARRAY_SIZE:private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; 
  */  
    private static int hugeCapacity(int minCapacity) {  
        if (minCapacity < 0)  
            throw new OutOfMemoryError();  
        return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;  
    }  

对于Vector整个的扩容过程,就是根据capacityIncrement确认扩容大小的,若capacityIncrement <= 0 则扩大一倍,否则扩大至capacityIncrement 。当然这个容量的最大范围为Integer.MAX_VALUE即,2^32 - 1,所以Vector并不是可以无限扩充的。

2.2、remove(Object o)

/**
  * 从Vector容器中移除指定元素E
  */  
    public boolean remove(Object o) {  
        return removeElement(o);  
    }  
    public synchronized boolean removeElement(Object obj) {  
        modCount++;  
        int i = indexOf(obj);   //计算obj在Vector容器中位置  
        if (i >= 0) {  
            removeElementAt(i);   //移除  
            return true;  
        }  
        return false;  
    }     
    public synchronized void removeElementAt(int index) {  
        modCount++;     //修改次数+1  
        if (index >= elementCount) {   //删除位置大于容器有效大小  
            throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount);  
        }else if (index < 0) {    //位置小于 < 0  
            throw new ArrayIndexOutOfBoundsException(index);  
        }  
        int j = elementCount - index - 1;  
        if (j > 0) {     
            //从指定源数组中复制一个数组,复制从指定的位置开始,到目标数组的指定位置结束。  
            //也就是数组元素从j位置往前移  
            System.arraycopy(elementData, index + 1, elementData, index, j);  
        }  
        elementCount--;   //容器中有效组件个数 - 1  
        elementData[elementCount] = null;    //将向量的末尾位置设置为null  
    } 

因为Vector底层是使用数组实现的,所以它的操作都是对数组进行操作,只不过其是可以随着元素的增加而动态的改变容量大小,其实现方法是是使用Arrays.copyOf方法将旧数据拷贝到一个新的大容量数组中。Vector的整个内部实现都比较简单,这里就不在重述了。

三、Vector遍历

        Vector支持4种遍历方式。

3.1、随机访问

        因为Vector实现了RandmoAccess接口,可以通过下标来进行随机访问。

for(int i = 0 ; i < vec.size() ; i++){  
        value = vec.get(i);  
}  

3.2、迭代器

Iterator it = vec.iterator();  
    while(it.hasNext()){  
        value = it.next();  
        //do something  
}  

3.2、for循环

for(Integer value:vec){  
        //do something  
}  

3.4、Enumeration循环

Vector vec = new Vector<>();  
    Enumeration enu = vec.elements();  
    while (enu.hasMoreElements()) {  
        value = (Integer)enu.nextElement();  
}  

Stack源码解析(基于JDK1.7)

在Java中Stack类表示后进先出(LIFO)的对象堆栈。栈是一种非常常见的数据结构,它采用典型的先进后出的操作方式完成的。每一个栈都包含一个栈顶,每次出栈是将栈顶的数据取出,如下:

        Stack通过五个操作对Vector进行扩展,允许将向量视为堆栈。这个五个操作如下:

            操作

                                          说明

empty()

测试堆栈是否为空。

peek()

查看堆栈顶部的对象,但不从堆栈中移除它。

pop()

移除堆栈顶部的对象,并作为此函数的值返回该对象。

push(E item)

把项压入堆栈顶部。

search(Object o)

返回对象在堆栈中的位置,以 1 为基数。

        Stack继承Vector,他对Vector进行了简单的扩展:

public class Stack<E> extends Vector<E>  

        Stack的实现非常简单,仅有一个构造方法,五个实现方法(从Vector继承而来的方法不算与其中),同时其实现的源码非常简单

/** 
  * 构造函数 
  */  
public Stack() {  
}  
/** 
  *  push函数:将元素存入栈顶 
  */  
public E push(E item) {  
   // 将元素存入栈顶。  
   // addElement()的实现在Vector.java中  
   addElement(item);    
   return item;  
}  

/** 
  * pop函数:返回栈顶元素,并将其从栈中删除 
  */  
public synchronized E pop() {  
   E    obj;  
   int    len = size();  
   obj = peek();  
   // 删除栈顶元素,removeElementAt()的实现在Vector.java中  
   removeElementAt(len - 1);  
   return obj;  
}  

/** 
  * peek函数:返回栈顶元素,不执行删除操作 
  */  
public synchronized E peek() {  
   int len = size();  
   if (len == 0)  
      throw new EmptyStackException();  
   // 返回栈顶元素,elementAt()具体实现在Vector.java中  
    return elementAt(len - 1);  

}  
/** 
  * 栈是否为空 
  */  
public boolean empty() {  
   return size() == 0;  
}   
/** 
  *  查找“元素o”在栈中的位置:由栈底向栈顶方向数 
  */  
public synchronized int search(Object o) {  
   // 获取元素索引,elementAt()具体实现在Vector.java中  
   int i = lastIndexOf(o);  
   if (i >= 0) {  
       return size() - i;  
   }  
   return -1;  
}  

        Stack的源码很多都是基于Vector

猜你喜欢

转载自blog.csdn.net/w372426096/article/details/82586738