Java7/8集合框架——ArrayList

时间:2022-11-08 19:33:18

 java.util.ArrayList

以下为主要介绍要点,从Java7出发:

  一、ArrayList的特点

  二、ArrayList的内部实现

  三、ArrayList添加元素和扩容

  四、ArrayList删除元素

  五、ArrayList查找元素

  六、ArrayList修改元素

  七、ArrayList的内部元素elementData为何用transient修饰

  八、ArrayList和Vector的比较

  九、Java8中ArrayList的改动

 

一、ArrayList的特点

      这里先入为主,从ArrayList本身特点介绍出发开始介绍。当然,集合Collection的各种实现自身的特点是大家比较常说的。

关注的问题点 ArrayList相关结论

是否允许空的元素

是否允许重复的元素

元素有序:读取数据和存放数据的顺序一致

是否线程安全
随机访问的效率 随机访问指定索引(数组的索引)的元素快
顺序添加元素的效率

在不涉及扩容时,顺序添加元素速度快;

当需要扩容时,涉及到元素的复制,相对较慢

删除和插入元素的效率

因涉及到复制和移动后续的元素,相对较慢

  

二、ArrayList的内部实现

ArrayList是一个内部以数组方式实现列表、可以自动扩容的集合。其内部实现有4个重要的变量:

  1. DEFAULT_CAPACITY:静态变量,是默认的初始化元素个数(容量)。
  2. EMPTY_ELEMENTDATA:静态变量,所有对象实例共享,是默认的空实现,所有调用空构造器public ArrayList()构造的ArrayLis默认指向该变量。
  3. elementData是常规的数组,存储列表的元素。这也是读取数据和存放数据的顺序一致、随机访问指定元素、顺序添加元素(在最后添加)的原因。
  4. sizeelementData数组中实际占用的有效数组元素个数。

源码如下:

 1    /**
 2      * Default initial capacity.
 3      */
 4     private static final int DEFAULT_CAPACITY = 10;
 5 
 6     /**
 7      * Shared empty array instance used for empty instances.
 8      */
 9     private static final Object[] EMPTY_ELEMENTDATA = {};
10 
11     /**
12      * The array buffer into which the elements of the ArrayList are stored.
13      * The capacity of the ArrayList is the length of this array buffer. Any
14      * empty ArrayList with elementData == EMPTY_ELEMENTDATA will be expanded to
15      * DEFAULT_CAPACITY when the first element is added.
16      */
17     private transient Object[] elementData;
18 
19     /**
20      * The size of the ArrayList (the number of elements it contains).
21      *
22      * @serial
23      */
24     private int size;

三、ArrayList添加元素和扩容

源码如下,

 1    /**
 2      * Appends the specified element to the end of this list.
 3      *
 4      * @param e element to be appended to this list
 5      * @return <tt>true</tt> (as specified by {@link Collection#add})
 6      */
 7     public boolean add(E e) {
 8         ensureCapacityInternal(size + 1);  // Increments modCount!!
 9         elementData[size++] = e;
10         return true;
11     }

 

add方法调用的了ensureCapacityInternal(size + 1),首先是判断元素的容量(capacity)大小,如果小于默认的DEFAULT_CAPACITY = 10,则使用默认容量,可以看出,只要添加了数组元素,数组的最小容量就是10。而ensureCapacityInternal(size + 1)调用了ensureExplicitCapacity(minCapacity),这个方法记录了总共需要扩容的次数modCount,同时进行了实际的数组扩容。当然只有实际数组长度size超过时才会进行扩容。

 1    private void ensureCapacityInternal(int minCapacity) {
 2         if (elementData == EMPTY_ELEMENTDATA) {
 3             minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
 4         }
 5 
 6         ensureExplicitCapacity(minCapacity);
 7     }
 8 
 9     private void ensureExplicitCapacity(int minCapacity) {
10         modCount++;
11 
12         // overflow-conscious code
13         if (minCapacity - elementData.length > 0)
14             grow(minCapacity);
15     }

 

扩容的方法如下。对于没有溢出的,实际的扩容是增加了一半容量,这里使用了位运算,右移一位类似与/2操作,取了一半,但是位操作快。

这里需要说明的是,默认空构造器时建立的ArrayList也是在这里首次进行扩容的,使用默认容量10。

 1     /**
 2      * Increases the capacity to ensure that it can hold at least the
 3      * number of elements specified by the minimum capacity argument.
 4      *
 5      * @param minCapacity the desired minimum capacity
 6      */
 7     private void grow(int minCapacity) {
 8         // overflow-conscious code
 9         int oldCapacity = elementData.length;
10         int newCapacity = oldCapacity + (oldCapacity >> 1);
11         if (newCapacity - minCapacity < 0)
12             newCapacity = minCapacity;
13         if (newCapacity - MAX_ARRAY_SIZE > 0)
14             newCapacity = hugeCapacity(minCapacity);
15         // minCapacity is usually close to size, so this is a win:
16         elementData = Arrays.copyOf(elementData, newCapacity);
17     }
18 
19     private static int hugeCapacity(int minCapacity) {
20         if (minCapacity < 0) // overflow
21             throw new OutOfMemoryError();
22         return (minCapacity > MAX_ARRAY_SIZE) ?
23             Integer.MAX_VALUE :
24             MAX_ARRAY_SIZE;
25     }

 

elementData[size++] = e;就是直接的元素赋值,然后增加size。另外还有指定索引添加元素,代码如下。从这两处源码可以看到,ArrayList并没有判断元素是什么,而是直接存储了,因此说:ArrayList允许空的或者重复的元素。

  1. 判断越界
  2. 扩容
  3. 从指定索引处开始的元素都往后移动一位
  4. 插入指定索引的指定元素,size加1,完成。

       从这里可以看出,因涉及到复制和移动其他的元素,插入元素比较慢。删除也是类似的。

 1     /**
 2      * Inserts the specified element at the specified position in this
 3      * list. Shifts the element currently at that position (if any) and
 4      * any subsequent elements to the right (adds one to their indices).
 5      *
 6      * @param index index at which the specified element is to be inserted
 7      * @param element element to be inserted
 8      * @throws IndexOutOfBoundsException {@inheritDoc}
 9      */
10     public void add(int index, E element) {
11         rangeCheckForAdd(index);
12 
13         ensureCapacityInternal(size + 1);  // Increments modCount!!
14         System.arraycopy(elementData, index, elementData, index + 1,
15                          size - index);
16         elementData[index] = element;
17         size++;
18     }

 

四、ArrayList删除元素

删除元素有2种:

  1. 指定索引删除元素
  2. 指定元素删除:这里找到第一个equals或者null(如元素为null)即可

1. 指定索引删除元素

  1. 越界判断
  2. 修改总共需要扩容的次数modCount
  3. 记录指定索引的旧元素
  4. 非最后一个元素,即index != size - index - 1,把元素前移一个单位。
  5. 清空最后一个索引元素
  6. 返回旧元素
 1     /**
 2      * Removes the element at the specified position in this list.
 3      * Shifts any subsequent elements to the left (subtracts one from their
 4      * indices).
 5      *
 6      * @param index the index of the element to be removed
 7      * @return the element that was removed from the list
 8      * @throws IndexOutOfBoundsException {@inheritDoc}
 9      */
10     public E remove(int index) {
11         rangeCheck(index);
12 
13         modCount++;
14         E oldValue = elementData(index);
15 
16         int numMoved = size - index - 1;
17         if (numMoved > 0)
18             System.arraycopy(elementData, index+1, elementData, index,
19                              numMoved);
20         elementData[--size] = null; // clear to let GC do its work
21 
22         return oldValue;
23     }

 

2. 指定元素删除

删除的步骤类似,只是这里是遍历找到第一个指定的元素而已

 1     /**
 2      * Removes the first occurrence of the specified element from this list,
 3      * if it is present.  If the list does not contain the element, it is
 4      * unchanged.  More formally, removes the element with the lowest index
 5      * <tt>i</tt> such that
 6      * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>
 7      * (if such an element exists).  Returns <tt>true</tt> if this list
 8      * contained the specified element (or equivalently, if this list
 9      * changed as a result of the call).
10      *
11      * @param o element to be removed from this list, if present
12      * @return <tt>true</tt> if this list contained the specified element
13      */
14     public boolean remove(Object o) {
15         if (o == null) {
16             for (int index = 0; index < size; index++)
17                 if (elementData[index] == null) {
18                     fastRemove(index);
19                     return true;
20                 }
21         } else {
22             for (int index = 0; index < size; index++)
23                 if (o.equals(elementData[index])) {
24                     fastRemove(index);
25                     return true;
26                 }
27         }
28         return false;
29     }
30 
31     /*
32      * Private remove method that skips bounds checking and does not
33      * return the value removed.
34      */
35     private void fastRemove(int index) {
36         modCount++;
37         int numMoved = size - index - 1;
38         if (numMoved > 0)
39             System.arraycopy(elementData, index+1, elementData, index,
40                              numMoved);
41         elementData[--size] = null; // clear to let GC do its work
42     }

 

五、ArrayList查找元素

这里只做了越界检查,如果没有越界,直接返回指定索引的元素即可,因此说速度比较快。

 1     /**
 2      * Returns the element at the specified position in this list.
 3      *
 4      * @param  index index of the element to return
 5      * @return the element at the specified position in this list
 6      * @throws IndexOutOfBoundsException {@inheritDoc}
 7      */
 8     public E get(int index) {
 9         rangeCheck(index);
10 
11         return elementData(index);
12     }

 

六、ArrayList修改元素

指定索引修改元素,按照索引,速度也是比较快。

 1     /**
 2      * Replaces the element at the specified position in this list with
 3      * the specified element.
 4      *
 5      * @param index index of the element to replace
 6      * @param element element to be stored at the specified position
 7      * @return the element previously at the specified position
 8      * @throws IndexOutOfBoundsException {@inheritDoc}
 9      */
10     public E set(int index, E element) {
11         rangeCheck(index);
12 
13         E oldValue = elementData(index);
14         elementData[index] = element;
15         return oldValue;
16     }

 

七、ArrayList的内部元素elementData为何用transient修饰

ArrayList本身实现了CloneableSerializable,但是关键的成员变量却是用了transient进行了修饰,不希望被序列化。

1 public class ArrayList<E> extends AbstractList<E>
2         implements List<E>, RandomAccess, Cloneable, java.io.Serializable

 

从序列化的实现来看,只对elementData中有元素的部分进行了序列化,并没有全部元素,这是合理的,一般elementData的容量比实际的size大,没有必要所有元素都行序列化。这也提高了时间效率,同时节省了空间。

 1     /**
 2      * Save the state of the <tt>ArrayList</tt> instance to a stream (that
 3      * is, serialize it).
 4      *
 5      * @serialData The length of the array backing the <tt>ArrayList</tt>
 6      *             instance is emitted (int), followed by all of its elements
 7      *             (each an <tt>Object</tt>) in the proper order.
 8      */
 9     private void writeObject(java.io.ObjectOutputStream s)
10         throws java.io.IOException{
11         // Write out element count, and any hidden stuff
12         int expectedModCount = modCount;
13         s.defaultWriteObject();
14 
15         // Write out size as capacity for behavioural compatibility with clone()
16         s.writeInt(size);
17 
18         // Write out all elements in the proper order.
19         for (int i=0; i<size; i++) {
20             s.writeObject(elementData[i]);
21         }
22 
23         if (modCount != expectedModCount) {
24             throw new ConcurrentModificationException();
25         }
26     }

 八、ArrayList和Vector的比较

       1、Vector是线程安全的:Vector大部分方法都和ArrayList差不多,但是其实现都添加了synchronized来保证操作的安全性。

       2、Vector可以指定扩容的增长因子capacityIncrement,每次需要扩容时会根据扩容因子进行判断,直接扩展指定的因子,或者是倍增,如下:

 1 private void grow(int minCapacity) {
 2         // overflow-conscious code
 3         int oldCapacity = elementData.length;
 4         int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
 5                                          capacityIncrement : oldCapacity);
 6         //判断是否溢出
 7         if (newCapacity - minCapacity < 0)
 8             newCapacity = minCapacity;
 9         if (newCapacity - MAX_ARRAY_SIZE > 0)
10             newCapacity = hugeCapacity(minCapacity);
11         elementData = Arrays.copyOf(elementData, newCapacity);
12     }    

 

九、Java8中ArrayList的改动 

  Java8中的ArrayList对于以上方法基本没有改动,只是增加了DEFAULTCAPACITY_EMPTY_ELEMENTDATA,作为默认空构造器时的空实现,而原来的EMPTY_ELEMENTDATA则改为在指定容量为0时的空实现。

 1     /**
 2      * Shared empty array instance used for empty instances.
 3      */
 4     private static final Object[] EMPTY_ELEMENTDATA = {};
 5 
 6     /**
 7      * Shared empty array instance used for default sized empty instances. We
 8      * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
 9      * first element is added.
10      */
11     private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};