`

源码分析(二)——ArrayList(基于JDK1.6)

阅读更多

一、ArrayList的定义:

public class ArrayList<E>

           extends AbstractList<E>  

           implements List<E>, RandomAccess, Cloneable, java.io.Serializable

从上述定义可以看出:

1. ArrayList继承自AbstractList,实现了List,所以它是一个队列,支持相关的添加、删除、修改、遍历等功能。

2. ArrayList实现了RandomAccess接口,即提供了随机访问功能。RandmoAccess是java中用来被List实现,为List提供快速访问功能的。

3. ArrayList实现了Cloneable接口,即实现clone()函数。它能被克隆。

4. ArrayList实现了java.io.Serializable接口,支持序列化。

5. ArrayList是支持泛型的。

 

 

二、ArrayList的基本属性:

ArrayList只定义了两个基本属性:存放元素的数组和数组大小。

     /**
       * The array buffer into which the elements of the ArrayList are stored.
       * The capacity of the ArrayList is the length of this array buffer.
       */
      private transient Object[] elementData;
  
     /**
       * The size of the ArrayList (the number of elements it contains).
       *
       * @serial
       */
      private int size;

 

 

三、ArrayList的构造方法:

1. 第一个构造方法(不带参数):

 /**
  * Constructs an empty list with an initial capacity of ten.
  */
  public ArrayList() {
     this(10);
  }

 

2. 第二个构造方法(带一个参数:初始容量):

  /**
   * Constructs an empty list with the specified initial capacity.
   */
   public ArrayList(int initialCapacity) {
     super();
     if (initialCapacity < 0)
          throw new IllegalArgumentException("Illegal Capacity: "+
                                                initialCapacity);
     this.elementData = new Object[initialCapacity];
    }

 

3. 第三个构造方法(传入一个集合)

    /**
     * Constructs a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     */
    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);
    }

 

 

四、ArrayList增加元素方法:

1.  add(E e):在数组末尾增加一个元素。 

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

ensureCapacity():

public void ensureCapacity(int minCapacity) {

      modCount++;

      int oldCapacity = elementData.length;

      if (minCapacity > oldCapacity) {

         Object oldData[] = elementData;

         int newCapacity = (oldCapacity * 3)/2 + 1;

             if (newCapacity < minCapacity)

         newCapacity = minCapacity;

             // minCapacity is usually close to size, so this is a win:

             elementData = Arrays.copyOf(elementData, newCapacity);

      }

 }

增加modCount之后,判断minCapacity(即size+1)是否大于oldCapacity(即elementData.length),若大于,则调整容量为max((oldCapacity*3)/2+1,minCapacity),调整elementData容量为新的容量,即将返回一个内容为原数组元素,大小为新容量的数组赋给elementData;否则不做操作。

因此可以看出:

ArrayList容量的拓展将导致数组元素的复制,多次拓展容量将执行多次整个数组内容的复制。若提前能大致判断list的长度,调用ensureCapacity调整容量,将有效的提高运行速度。

另外,ArrayList每次容量扩展大概是原来的1.5倍,增长二分之一。

 

2. 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);  // Increments modCount!!
     System.arraycopy(elementData, index, elementData, index + 1,
              size - index);
     elementData[index] = element;
     size++;
 }

首先判断指定位置index是否超出elementData的界限,之后调用ensureCapacity调整容量(若容量足够则不会拓展),调用System.arraycopy将elementData从index开始的size-index个元素复制到index+1至size+1的位置(即index开始的元素都向后移动一个位置),然后将index位置的值指向element。 

 

3. addAll(Collection<? extends E> c):将一个arrayList添加到另一个arrayList中。 

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

先将集合c转换成数组,根据转换后数组的程度和ArrayList的size拓展容量,之后调用System.arraycopy方法复制元素到elementData的尾部,调整size。根据返回的内容分析,只要集合c的大小不为空,即转换后的数组长度不为0则返回true。

 

4. addAll(int index,Collection<? extends E> c):将一个arrayList添加到另一个arrayList的指定索引处。

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

先判断index是否越界。其他内容与addAll(Collection<? extends E> c)基本一致,只是复制的时候先将index开始的元素向后移动X(c转为数组后的长度)个位置(也是一个复制的过程),之后将数组内容复制到elementData的index位置至index+X。

 

 

 

五、ArrayList的删除元素方法:

1. remove(int index)方法:移除指定位置的元素。 

 public E remove(int index) {
     RangeCheck(index);
     modCount++;
     E oldValue = (E) elementData[index];
     int numMoved = size - index - 1;
     if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index, numMoved);
     elementData[--size] = null; // Let gc do its work
      return oldValue;
 }

首先是检查范围,修改modCount,保留将要被移除的元素,将移除位置之后的元素向前挪动一个位置,将list末尾元素置空(null),返回被移除的元素。

 

2. remove(Object o)方法:移除指定元素的元素。

 public boolean remove(Object o) {
     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;
    }

首先通过代码可以看到,当移除成功后返回true,否则返回false。remove(Object o)中通过遍历element寻找是否存在传入对象,一旦找到就调用fastRemove移除对象。为什么找到了元素就知道了index,不通过remove(index)来移除元素呢?因为fastRemove跳过了判断边界的处理,因为找到元素就相当于确定了index不会超过边界,而且fastRemove并不返回被移除的元素。下面是fastRemove的代码,基本和remove(index)一致。

fastRemove(): 

 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
 }

 

3. removeRange(int fromIndex,int toIndex):移除指定范围的元素。 

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

执行过程是将elementData从toIndex位置开始的元素向前移动到fromIndex,然后将toIndex位置之后的元素全部置空顺便修改size。

 

4. clear():清空ArrayList方法。

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

clear的时候并没有修改elementData的长度(好不容易申请、拓展来的,凭什么释放,留着搞不好还有用呢。这使得确定不再修改list内容之后最好调用trimToSize来释放掉一些空间),只是将所有元素置为null,size设置为0。即clear方法只是将数组里的元素置为null,ArrayList的大小置为0,并没有释放内存。

 

 

六、ArrayList的修改元素方法:

1.  set(int index,E element): 修改指定索引位置的元素的值。 

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

首先检查范围,用新元素替换旧元素并返回旧元素。

  

 

七、ArrayList的获取元素方法:

 1. get(int index):获取指定索引的元素。

public E get(int index) {
     RangeCheck(index); 
     return (E) elementData[index];
}

 先调用RangeCheck方法检测索引是否越界,然后直接根据数组下标获取数组元素,由此可以看出ArrayList是元素插入慢,但是查询快(数组的查询速度是很快的)。

 

 

 

 八、ArrayList的索引相关方法:

 1.  indexOf(Object):获取元素在ArrayList中索引。

  public int indexOf(Object o) {
      if (o == null) {
          for (int i = 0; i < size; i++)
            if (elementData[i]==null)
                return i;
      } else {
           for (int i = 0; i < size; i++)
             if (o.equals(elementData[i]))
                return i;
      }
      
     return -1;
 }

 通过遍历elementData数组来判断对象是否在list中,若存在,返回元素的index([0,size-1]),若不存在则返回-1。

 

2. lastIndexOf():获取指定元素最后出现的索引值。

  public int lastIndexOf(Object o) {
      if (o == null) {
          for (int i = size-1; i >= 0; i--)
          if (elementData[i]==null)
              return i;
      } else {
          for (int i = size-1; i >= 0; i--)
          if (o.equals(elementData[i]))
              return i;
     }
     return -1;
 }

  采用了从后向前遍历element数组,若遇到Object则返回index值,若没有遇到,返回-1。

 

 

 

九、ArrayList的其他方法:

1.  isEmpty():判断ArrayList是否为空。

    public boolean isEmpty() {

         return size == 0;

    }

    直接返回size是否等于0。

 

2. size():获取ArrayList的大小。

     public int size() {

           return size;

      }

 

3. contains(Object):判断元素是否在ArrayList中。

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

 

 4. toArray():将ArrayList转换成数组。

 public Object[] toArray() {
         return Arrays.copyOf(elementData, size);
 }

调用Arrays.copyOf将返回一个数组,数组内容是size个elementData的元素,即拷贝elementData从0至size-1位置的元素到新数组并返回。

以下是泛型方式写法:

 public <T> T[] toArray(T[] a) {
     if (a.length < size)
        // Make a new array of a's runtime type, but my contents:
        return (T[]) Arrays.copyOf(elementData, size, a.getClass());
     System.arraycopy(elementData, 0, a, 0, size);
     if (a.length > size)
         a[size] = null;
     return a;
  }

 如果传入数组的长度小于size,返回一个新的数组,大小为size,类型与传入数组相同。所传入数组长度与size相等,则将elementData复制到传入数组中并返回传入的数组。若传入数组长度大于size,除了复制elementData外,还将把返回数组的第size个元素置为空。

 

 5. trimToSize():压缩存放元素数组的空间大小。  

 public void trimToSize() {
     modCount++;
     int oldCapacity = elementData.length;
     if (size < oldCapacity) {
             elementData = Arrays.copyOf(elementData, size);
     }
 }

由于elementData的长度会被拓展,size标记的是其中包含的元素的个数。所以会出现size很小但elementData.length很大的情况,将出现空间的浪费。trimToSize将返回一个新的数组给elementData,元素内容保持不变,length很size相同,节省空间。

 

 

 

 十、ArrayList与LinkList的比较:

1. 实现方式:ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。

2. 随机访问速度:因为ArrayList底层由数组实现,LinkedList是双向链表,所以ArrayList的随机访问速度要比LinkedList快。

3. 插入删除速度:因为ArrayList底层由数组实现,在0号位置插入时将移动list的所有元素,在末尾插入元素时不需要移动。LinkedList是双向链表,在任意位置插入元素所需时间均相同。所以LinkedList插入删除速度要比ArrayList快。

综上所述,在List中有较多插入和删除操作的情况下应使用LinkedList来提高效率,而有较多索引查询的时候使用ArrayList(使用增强型的for循环或Iterator遍历LinkedList效率将提高很多)

 

 

 

 

 

 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics