ArrayList源码剖析

来源:互联网 发布:java构造方法不写void 编辑:程序博客网 时间:2024/06/06 18:36
//--------------------------------------------------------------------
转载出处:http://blog.csdn.net/chdjj
作者:Rowandjj
//--------------------------------------------------------------------

从这篇文章开始,我将对java集合框架中的一些比较重要且常用的类进行分析。这篇文章主要介绍的是ArrayList。

依然延续之前StringBuilder和StringBuffer源码分析的行文思路,首先从整体上了解java集合框架,下面就是一幅java集合框架图。


从图中可以看到,ArrayList处在这棵继承树的最底部,也就是一个叶子结点,我们要想分析ArrayList的实现逻辑,必然少不了去研究的它的超类。那么下面我们就从Collection开始,自顶向下进行分析。

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public interface Collection<E> extends Iterable<E> {  
  2.     // Query Operations 查询操作  
  3.     int size();  
  4.     boolean isEmpty();  
  5.     boolean contains(Object o);  
  6.     Iterator<E> iterator();  
  7.     Object[] toArray();  
  8.     <T> T[] toArray(T[] a);  
  9.     // Modification Operations 修改操作  
  10.     boolean add(E e);  
  11.     boolean remove(Object o);  
  12.     // Bulk Operations 批量操作  
  13.     boolean containsAll(Collection<?> c);  
  14.     boolean addAll(Collection<? extends E> c);  
  15.     boolean removeAll(Collection<?> c);  
  16.     boolean retainAll(Collection<?> c);  
  17.     void clear();  
  18.     // Comparison and hashing 比较以及hash  
  19.     boolean equals(Object o);  
  20.     int hashCode();  
  21. }  

Collection中主要定义了查询、修改、批量以及比较等操作规范,因为是接口,所以并无实现。
另外,此接口继承了Iterable接口,表示“可迭代的”含义,这个接口是用来返回迭代器对象的:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. package java.lang;  
  2. import java.util.Iterator;  
  3. public interface Iterable<T> {  
  4.     Iterator<T> iterator();  
  5. }  

迭代器对象Iterator定义了一个遍历和删除的规范

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public interface Iterator<E> {  
  2.     boolean hasNext();  
  3.     E next();  
  4.     void remove();  
  5. }  

因而,Collection的实现类都能通过iterator方法获得与自己关联的迭代器对象,然后通过Iterator定义的遍历规则进行遍历(具体的遍历方式由实现类去实现)。这保证了Collection对外的一致性,也降低了学习难度。
最顶级的接口弄明白了,我们接着分析下面的List接口和AbstractCollection抽象类。
首先是List接口:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public interface List<E> extends Collection<E> {  
  2.     // Query Operations  
  3.     int size();  
  4.     boolean isEmpty();  
  5.     boolean contains(Object o);  
  6.     Iterator<E> iterator();  
  7.     Object[] toArray();  
  8.     <T> T[] toArray(T[] a);  
  9.   
  10.     // Modification Operations  
  11.     boolean add(E e);  
  12.     boolean remove(Object o);  
  13.     // Bulk Modification Operations  
  14.     boolean containsAll(Collection<?> c);  
  15.     boolean addAll(Collection<? extends E> c);  
  16.     boolean addAll(int index, Collection<? extends E> c);  
  17.     boolean removeAll(Collection<?> c);  
  18.     boolean retainAll(Collection<?> c);  
  19.     void clear();  
  20.     // Comparison and hashing  
  21.     boolean equals(Object o);  
  22.     int hashCode();  
  23.     // Positional Access Operations  
  24.     E get(int index);  
  25.     E set(int index, E element);  
  26.     void add(int index, E element);  
  27.     E remove(int index);  
  28.     // Search Operations  
  29.     int indexOf(Object o);  
  30.     int lastIndexOf(Object o);  
  31.     // List Iterators   
  32.     ListIterator<E> listIterator();     
  33.     ListIterator<E> listIterator(int index);  
  34.     // View  
  35.     List<E> subList(int fromIndex, int toIndex);  
  36. }  
可见,List接口多了一些通过索引查找、删除,以及遍历的方法,这主要是由于List集合是有有序的,其子类要么是数组要么是链表实现,都可以通过位置去索引元素,故而增加了像get(index)这样的方法,而Set集合是无序的,故而并不需要这些方法。
另外这个类中还提供了新的迭代器,那就是ListIterator,ListIterator继承自Iterator,并增加了向前遍历、增加元素等方法
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public interface ListIterator<E> extends Iterator<E> {  
  2.     // Query Operations  
  3.     boolean hasNext();  
  4.     E next();  
  5.     boolean hasPrevious();  
  6.     E previous();  
  7.     int nextIndex();  
  8.     int previousIndex();  
  9.     // Modification Operations  
  10.     void remove();  
  11.     void set(E e);  
  12.     void add(E e);  
  13. }  
下面分析AbstractCollection抽象类,注意哦,这个是抽象类,还记得之前介绍StringBuilder和StringBuffer么,StringBuilder和StringBuffer同样都继承了一个叫AbstractStringBuilder的抽象类,看命名,还真挺统一呢。
方法较多,这里挑几个重要的。首先看这个抽象类里面的抽象方法都有哪些:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public abstract Iterator<E> iterator();  
  2. public abstract int size();  

只有两个抽象方法,这也比较正常,迭代规则应该根据具体数据结构实现,而size也依赖具体的数据结构.
再看这个contains方法 :
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public boolean contains(Object o) {  
  2.       Iterator<E> it = iterator();  
  3.       if (o==null) {  
  4.           while (it.hasNext())  
  5.               if (it.next()==null)  
  6.                   return true;  
  7.       } else {  
  8.           while (it.hasNext())  
  9.               if (o.equals(it.next()))  
  10.                   return true;  
  11.       }  
  12.       return false;  
  13.   }  

看到没?这个方法会依据参数是否为空,进行两次遍历,类似的还有remove等方法,这告诉我们继承AbstractCollection的子类集合中允许有空的元素!
另外很有意思的是这个add方法:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public boolean add(E e) {  
  2.         throw new UnsupportedOperationException();  
  3.     }  
永远抛异常,这时因为AbstractCollection并不知道怎样去增加元素,这个方法必须被具体实现类所复写。
分析到这里,我们发现了java设计者对集合框架作了一层又一层的封装,每一层都添加了不一样的方法,并实现了能够实现的方法,提高了代码的复用性。
接下来,我们将依次分析AbstractList和ArrayList,AbstractList是个抽象类,继承了AbstractCollection,而ArrayList直接继承了AbstractList。
AbstractList与AbstractCollection的区别是它实现了一些跟ArrayList、LinkedList等有序集合的相关操作如ListIterator,因为AbstractList实现了List接口。
这个AbstractList里面有两个内部类,Itr和ListItr,这两个内部类分别实现Iterator接口和ListIterator。
先看Itr这个类,包含三个成员变量:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. int cursor = 0;//游标,下一次调用next的位置  
  2. int lastRet = -1;//保存上一次next的位置  
  3. int expectedModCount = modCount;//集合被改变的次数  
再看下具体实现:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public boolean hasNext() {  
  2.           return cursor != size();//判断游标是否等于集合大小,不是则返回true  
  3.       }  
  4.       public E next() {  
  5.           checkForComodification();//检查是否集合内容被更改过  
  6.           try {  
  7.               int i = cursor;//标记此次位置  
  8.               E next = get(i);//返回该位置的元素值  
  9.               lastRet = i;//标记上一次的位置  
  10.               cursor = i + 1;//游标指向下一个位置  
  11.               return next;  
  12.           } catch (IndexOutOfBoundsException e) {  
  13.               checkForComodification();  
  14.               throw new NoSuchElementException();  
  15.           }  
  16.       }  
  17.       public void remove() {  
  18.           if (lastRet < 0)  
  19.               throw new IllegalStateException();  
  20.           checkForComodification();  
  21.           try {  
  22.               AbstractList.this.remove(lastRet);  
  23.               if (lastRet < cursor)  
  24.                   cursor--;  
  25.               lastRet = -1;  
  26.               expectedModCount = modCount;  
  27.           } catch (IndexOutOfBoundsException e) {  
  28.               throw new ConcurrentModificationException();  
  29.           }  
  30.       }  
  31.       final void checkForComodification() {//更改的次数与所期待的的次数不一致  
  32.           if (modCount != expectedModCount)//则抛出异常  
  33.               throw new ConcurrentModificationException();  
  34.       }  
ListItr逻辑基本一致,在此不再敖述。
前面我们花了大量的篇幅介绍ArrayList的超类、父接口,为的是让大家对集合整个集合框架有个整体的认识,那下面呢,将进入ArrayList的源码分析
-----------------------------------------------------------------------
先看成员变量:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. private static final long serialVersionUID = 8683452581122892189L;  
  2. private transient Object[] elementData;//这即存放ArrayList元素的数组,可扩容。  
  3. private int size;//当前ArrayList的大小(实际元素数目)  

再看构造器:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public ArrayList(int initialCapacity) {//参数为初始容量  
  2.        super();  
  3.        if (initialCapacity < 0)  
  4.            throw new IllegalArgumentException("Illegal Capacity: "+  
  5.                                               initialCapacity);  
  6.        this.elementData = new Object[initialCapacity];  
  7.    }  
  8.    public ArrayList() {//默认容量是10  
  9.        this(10);  
  10.    }  
  11.    public ArrayList(Collection<? extends E> c) {//从集合中构造  
  12.        elementData = c.toArray();  
  13.        size = elementData.length;  
  14.        // c.toArray might (incorrectly) not return Object[] (see 6260652)  
  15.        if (elementData.getClass() != Object[].class)  
  16.            elementData = Arrays.copyOf(elementData, size, Object[].class);  
  17.    }  

跟StringBuilder/StringBuffer一样,通过构造器可以设置集合的大小,另外,ArrayList的默认大小为10
再看扩容的相关方法,这个ensureCapacity就是扩容的入口函数,非常重要:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public void ensureCapacity(int minCapacity) {  
  2.         if (minCapacity > 0)  
  3.             ensureCapacityInternal(minCapacity);  
  4.     }  
  5.     private void ensureCapacityInternal(int minCapacity) {  
  6.         modCount++;  
  7.         // overflow-conscious code  
  8.         if (minCapacity - elementData.length > 0)  
  9.             grow(minCapacity);  
  10.     }  

若所需集合的最小容量仍大于当前数组容量,那么将调用grow方法去扩容
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. private void grow(int minCapacity) {  
  2.         // overflow-conscious code  
  3.         int oldCapacity = elementData.length;  
  4.         int newCapacity = oldCapacity + (oldCapacity >> 1);//扩容为原来的1.5倍  
  5.         if (newCapacity - minCapacity < 0)//如果还是比最小容量小,那么干脆  
  6.             newCapacity = minCapacity;//就设置为minCapacity  
  7.         if (newCapacity - MAX_ARRAY_SIZE > 0)//看是否超过了数组容量的最大值  
  8.             newCapacity = hugeCapacity(minCapacity);  
  9.         // minCapacity is usually close to size, so this is a win:  
  10.         elementData = Arrays.copyOf(elementData, newCapacity);  
  11.     }  
  12.     private static int hugeCapacity(int minCapacity) {  
  13.         if (minCapacity < 0// overflow  
  14.             throw new OutOfMemoryError();  
  15.         return (minCapacity > MAX_ARRAY_SIZE) ?  
  16.             Integer.MAX_VALUE :  
  17.             MAX_ARRAY_SIZE;  
  18.     }  
  19. private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;  

每次扩容都会尝试将容量变为原来的1.5倍,然后再去跟最小容量进行比较,若比最小容量小,则直接设置为最小容量。
下面再来看几个常见的方法,首先是add,超类中的add方法一直没有具体的实现,而最终在ArrayList里面有了实现:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public boolean add(E e) {  
  2.        ensureCapacityInternal(size + 1);  // Increments modCount!!  
  3.        elementData[size++] = e;//一目了然  
  4.        return true;  
  5.    }  
clear方法:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public void clear() {  
  2.        modCount++;//改变次数加1  
  3.        // Let gc do its work  
  4.        for (int i = 0; i < size; i++)  
  5.            elementData[i] = null;//置空  
  6.        size = 0;//size归0  
  7.    }  
remove方法:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public boolean remove(Object o) {  
  2.      if (o == null) {  
  3.          for (int index = 0; index < size; index++)  
  4.              if (elementData[index] == null) {  
  5.                  fastRemove(index);  
  6.                  return true;  
  7.              }  
  8.      } else {  
  9.          for (int index = 0; index < size; index++)  
  10.              if (o.equals(elementData[index])) {  
  11.                  fastRemove(index);  
  12.                  return true;  
  13.              }  
  14.      }  
  15.      return false;  
  16.  }  
  17. private void fastRemove(int index) {  
  18.      modCount++;  
  19.      int numMoved = size - index - 1;  
  20.      if (numMoved > 0)  
  21.          System.arraycopy(elementData, index+1, elementData, index,  
  22.                           numMoved);  
  23.      elementData[--size] = null// Let gc do its work  
  24.  }  
这里需要先对参数进行两种操作,一种是当参数为空,另一种是当参数不为空时,ArrayList是允许空值的。
另外需要注意下这个System.arraycopy方法,这个方法其实是native的,最终是通过c语言实现的,非常高效的数组复制方式。
另外ArrayList中Itr和ListItr两个内部类,跟AbstractList基本一致。

另外我们需要注意下这个toArray方法的两个重载形式,返回Object[]的版本在使用时需要注意,不能对返回的数组整体转型:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. List<String> list = new ArrayList<String>();  
  2. list.add("zhangsan");  
  3. list.add("lisi");  
  4. list.add("wangwu");  
  5. //如果直接用向下转型的方法,将整个ArrayList集合转变为指定类型的Array数组,便会抛出ClassCast异常  
  6. String[] strs = (String[]) list.toArray();//错误!!!  
  7. for(int i = 0; i < strs.length; i++)  
  8. {  
  9.     System.out.println(strs[i]);  
  10. }  

上面这种方式会发生异常,正确的应该是这样的:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. List<String> list = new ArrayList<String>();  
  2. ist.add("zhangsan");  
  3. list.add("lisi");  
  4. list.add("wangwu");  
  5. Object[] strs = (Object[]) list.toArray();  
  6. for(int i = 0; i < strs.length; i++)  
  7. {  
  8.     System.out.println((String)strs[i]);  
  9. }  

当然,你使用另一个重载版本就没有这样的问题了:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. List<String> list = new ArrayList<String>();  
  2. list.add("zhangsan");  
  3. list.add("lisi");  
  4. list.add("wangwu");  
  5. String[] strs = list.toArray(new String[list.size()]);  
  6. for(int i = 0; i < strs.length; i++)  
  7. {  
  8.     System.out.println(strs[i]);  
  9. }  

最后,来个总结:
1.ArrayList内部是通过一个Object数组实现的,当数组填满之后会根据需要进行扩容;
2.最好预估ArrayList的大小,并设置其初始容量,以避免不必要的扩容所造成的性能问题;
3.ArrayList的初始容量为10;
4.ArrayList每次扩容都将容量变为原来的1.5倍,若还小于所需的最小值,那么直接分配容量为所需值。
5.ArrayList允许空(null)的元素。
6.ArrayList内部有两个内部类,分别实现Iterator和ListIterator,定义了迭代的规则。
0 0