ArrayList源码解析
ArrayList简介:
ArrayList 是list接口的一个常用实现类。它的对象可以认为是一维数组的“类版本”。我们很快就可以看到,ArrayList 对象可以看做是一维数组的改良版本。类似于数组,ArrayList 对象支持元素的随机访问;也就是说,只要给出元素的索引,任何元素的访问时间都是常数。但是同数组不同的是,ArrayList 对象的大小在程序执行的过程中可以自动进行调整,并且ArrayList对象具有在任何索引位置插入和删除对象的方法,而数组如果想要插入和删除对象,则必须要编写代码增加和减少储存空间。
ArrayList方法简介:
首先我们先来看一下ArrayList的所有公共方法。
注:下面方法除构造函数方法外,其他方法皆按照字母顺序排序。
1 1、public Arraylist() 2 2、public ArrayList(int initalCapacity) 3 3、public ArrayList(Collection<? extends E> c) 4 4、public boolean add(E e) 5 5、public void add(int index, E element) 6 6、 public boolean addAll(int index, Collection<? extends E> c) 7 7、public boolean addAll(Collection<? extends E> c) 8 8、 public void clear() 9 9、 public Object clone() 10 10、 public boolean contains(Object o) 11 11、public boolean containsAll(Collection<?> c) 12 12、public void ensureCapacity(int minCapacity) 13 13、public boolean equals(Object o) 14 14、 public void forEach(Consumer<? super E> action) 15 15、public E get(int index) 16 16、public int hashCode() 17 17、 public int indexOf(Object o) 18 18、public boolean isEmpty() 19 19、public Iterator<E> iterator() 20 20、public int lastIndexOf(Object o) 21 21、public ListIterator<E> listIterator() 22 22、public ListIterator<E> listIterator(int index) 23 23、public E remove(int index) 24 24、public boolean remove(Object o) 25 25、public boolean removeAll(Collection<?> c) 26 26、public boolean removeIf(Predicate<? super E> filter) 27 27、 public void replaceAll(UnaryOperator<E> operator) 28 28、public boolean retainAll(Collection<?> c) 29 29、public E set(int index, E element) 30 30、public int size() 31 31、public void sort(Comparator<? super E> c) 32 32、public Spliterator<E> spliterator() 33 33、public List<E> subList(int fromIndex, int toIndex) 34 34、public Object[] toArray() 35 35、public <T> T[] toArray(T[] a) 36 36、public String toString() 37 37、public void trimToSize()
ArrayList方法解析:
ArrayList类结构关系:
在具体分析Arrayl类的方法之前,我们先看一下ArrayList类的继承关系。
从图中可以看出,ArrayList继承AbstractList类,并且实现了List、RandomAccess、Serializable接口。
其中RandomAccess接口为一个空接口,起到一个标识的作用,如果集合类是RandomAccess的实现,则尽量用for(int i = 0; i < size; i++) 来遍历而不要用Iterator迭代器来遍历,在效率上要差一些。反过来,如果List是Sequence List,则最好用迭代器来进行迭代。 其具体细节在我们在这里就不进行讨论了,感兴趣的同学可以自己去查一查。
Serializable是启用序列化功能的接口,这里也不进行讨论了。
AbstractList继承了AbstractCollection.两者都是抽象类,AbstractCollection提供了Collection接口的一些方法的默认实现,AbstractCollection提供了List接口
的默认实现(个别方法为抽象方法)。
ArrayList类的属性:
1 //默认容量的大小 2 private static final int DEFAULT_CAPACITY = 10; 3 4 //空数组常量 5 private static final Object[] EMPTY_ELEMENTDATA = {}; 6 7 //默认的空数组常量 8 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; 9 10 //存放元素的数组,从这可以发现ArrayList的底层实现就是一个Object数组 11 transient Object[] elementData; 12 13 //数组中包含的元素个数 14 private int size; 15 16 //数组的最大上限 17 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
ArrayList的属性非常少,一共就只有这五个。其中:DEFAULT_CAPACITY 代表默认容量的大小,在后面我们可以看到,在像ArrayList对象第一次添
加元素的时候,会将ArrayList的容量设置为默认的容量。EMPTY_ELEMENTDATE和DEFAULTCAPACITY_EMPTY_ELEMENTDATA区别不大。这个在后面
讲构造器的时候会详细讲到。其中最重要的莫过于elementData了,ArrayList所有的方法都是建立在elementData之上。
ArrayList构造函数:
ArrayList一共有三个构造函数,分别为:
1 public ArrayList(int initialCapacity) { 2 if (initialCapacity > 0) { 3 this.elementData = new Object[initialCapacity]; 4 } else if (initialCapacity == 0) { 5 this.elementData = EMPTY_ELEMENTDATA; 6 } else { 7 throw new IllegalArgumentException("Illegal Capacity: "+ 8 initialCapacity); 9 } 10 } 11 public ArrayList() { 12 this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; 13 } 14 public ArrayList(Collection<? extends E> c) { 15 elementData = c.toArray(); 16 if ((size = elementData.length) != 0) { 17 // c.toArray might (incorrectly) not return Object[] (see 6260652) 18 if (elementData.getClass() != Object[].class) 19 elementData = Arrays.copyOf(elementData, size, Object[].class); 20 } else { 21 // replace with empty array. 22 this.elementData = EMPTY_ELEMENTDATA; 23 } 24 }
从构造函数我们可以看出,elementDate默认是一个大小为0的的数组,即DEFAULTCAPACITY_EMPTY_ELEMENTDATA,而
当指定数组长度时,elementData的初始大小就变成了我们所指定的初始大小了。如果我们指定数组的默认长度为0时,
elementDate即为EMPTY_ELEMENTDATA,在这里我们还看不出两者的区别,在后面讲到add方法时,就可以看到两者的不同了。
ArrayList同样可以传入一个Collection,当Collection不为空时,复制该Collection。否则elementDate仍然为EMPTY_ELEMENTDATA。
ArrayList的公共方法:
ArrayList的添加方法:
ArrayList添加单个元素有两个方法,分别为:
1 public boolean add(E e) { 2 ensureCapacityInternal(size + 1); // Increments modCount!! 3 elementData[size++] = e; 4 return true; 5 } 6 public void add(int index, E element) { 7 rangeCheckForAdd(index); 8 9 ensureCapacityInternal(size + 1); // Increments modCount!! 10 System.arraycopy(elementData, index, elementData, index + 1, 11 size - index); 12 elementData[index] = element; 13 size++; 14 }
add(E e)方法是用来添加一个单个的元素,其调用了私有方法enesureCapacityInternal(size+1).
1 private void ensureCapacityInternal(int minCapacity) { 2 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { 3 minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); 4 } 5 6 ensureExplicitCapacity(minCapacity); 7 } 8 private void ensureExplicitCapacity(int minCapacity) { 9 modCount++;// 10 // overflow-conscious code 11 if (minCapacity - elementData.length > 0) 12 grow(minCapacity); 13 } 14 private void grow(int minCapacity) { 15 // overflow-conscious code 16 int oldCapacity = elementData.length; 17 int newCapacity = oldCapacity + (oldCapacity >> 1); 18 if (newCapacity - minCapacity < 0) 19 newCapacity = minCapacity; 20 if (newCapacity - MAX_ARRAY_SIZE > 0) 21 newCapacity = hugeCapacity(minCapacity); 22 // minCapacity is usually close to size, so this is a win: 23 elementData = Arrays.copyOf(elementData, newCapacity); 24 }
我们可以看到,在这个方法中,增加了一个判断,elementData ==DEFAULTCAPACITY_EMPTY_ELEMENTDATA,那么minCapacity=DEFAULT_CAPACITY,也就是等于10.这就区分出了前面所说的DEFAULTCAPACITY_EMPTY_ELEMENTDATA和EMPTY_ELEMENTDATA的区别。当elemetnDate=DEFAULT_CAPACITY时,第一次添加元素时,容量变为1,而当elementData =DEFAULTCAPACITY_EMPTY_ELEMENTDATA时,第一次添加元素时,容量变为10.
同时,我们也可以看出ArrayList在面对容量不足时,每次扩容都是在原容量的基础上,增加0.5倍。
而add(int index,E e)就比较好理解了,先检查index,然后判断是否需要扩容,需要就扩容,然后将原来的elemetnDate从index起始到最后复制到index+1的位置上,然后在原index位置添加元素。
ArrayList添加多个元素的方法一样有两个,分别为:
1 public boolean addAll(Collection<? extends E> c) { 2 Object[] a = c.toArray(); 3 int numNew = a.length; 4 ensureCapacityInternal(size + numNew); // Increments modCount 5 System.arraycopy(a, 0, elementData, size, numNew); 6 size += numNew; 7 return numNew != 0; 8 } 9 10 public boolean addAll(int index, Collection<? extends E> c) { 11 rangeCheckForAdd(index); 12 13 Object[] a = c.toArray(); 14 int numNew = a.length; 15 ensureCapacityInternal(size + numNew); // Increments modCount 16 17 int numMoved = size - index; 18 if (numMoved > 0) 19 System.arraycopy(elementData, index, elementData, index + numNew, 20 numMoved); 21 22 System.arraycopy(a, 0, elementData, index, numNew); 23 size += numNew; 24 return numNew != 0; 25 }
插入多个元素,与插入单个元素的逻辑是一样的,在这里就不再重复了。
ArrayList删除元素方法:
ArrayList删除元素的方法一共有五个,分别是
1 public E remove(int index) { 2 rangeCheck(index); 3 4 modCount++; 5 E oldValue = elementData(index); 6 7 int numMoved = size - index - 1; 8 if (numMoved > 0) 9 System.arraycopy(elementData, index+1, elementData, index, 10 numMoved); 11 elementData[--size] = null; // clear to let GC do its work 12 13 return oldValue; 14 }
remove(int index)的处理逻辑:先检查index是否大于elementDate的size,如果大于的话,则抛出异常;然后获取elementDate[index],留在方法结束后返回;然后复制素组,从index+1开始,复制size-index-1个元素,复制到elementDate,从index开始;最后设置elementDate[--size]=null;
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 }
remove(Object o)的逻辑:先判断Object 是否为null,如果为null,则遍历ArrayList,删除所有的null值,如果有值被删除,则返回true;如果Object不为null,同样遍历ArrayList,删除ArrayList中相同的元素,如果有元素被删除,则返回true.
而fastRemove(int index)的逻辑与remove(int index),完全一致,只是少了对index的验证。
1 public boolean removeAll(Collection<?> c) { 2 Objects.requireNonNull(c); 3 return batchRemove(c, false); 4 }
1 private boolean batchRemove(Collection<?> c, boolean complement) { 2 final Object[] elementData = this.elementData; 3 int r = 0, w = 0; 4 boolean modified = false; 5 try { 6 for (; r < size; r++) 7 if (c.contains(elementData[r]) == complement) 8 elementData[w++] = elementData[r]; 9 } finally { 10 // Preserve behavioral compatibility with AbstractCollection, 11 // even if c.contains() throws. 12 if (r != size) { 13 System.arraycopy(elementData, r, 14 elementData, w, 15 size - r); 16 w += size - r; 17 } 18 if (w != size) { 19 // clear to let GC do its work 20 for (int i = w; i < size; i++) 21 elementData[i] = null; 22 modCount += size - w; 23 size = w; 24 modified = true; 25 } 26 } 27 return modified; 28 }
从上面的代码我们可以看出,remove(Collection<?> c)的逻辑:先判断c是否为null,如果为null,则抛出异常。然后遍历elementDate,如果c包含数组elementDate中的元素
,则将该元素添加一次替换原elementDate数组中,最后,将数组的其余位置设为null,如果新数组比原来数组中的元素少,则已经删除了元素,返回true.
除此之外,还有一个清空ArrayList的方法
1 public void clear() { 2 modCount++; 3 4 // clear to let GC do its work 5 for (int i = 0; i < size; i++) 6 elementData[i] = null; 7 8 size = 0; 9 }
clear()方法的逻辑:遍历,删除所有元素。
除此之外,还有一个retainAll(collenction<T> c)方法,该方法愿意是取得两个集合得交集,在这里也可以理解为删除集合中不与collection c 重复得元素。
1 return batchRemove(c, true);
可以看到,它得处理逻辑和removeAll是同样得逻辑,这里就不再重复了。
ArrayList修改元素的方法
在JDK1.8之前,修改元素只有一个方法,就是set(int index,Object c).
1 public E set(int index, E element) { 2 rangeCheck(index); 3 4 E oldValue = elementData(index); 5 elementData[index] = element; 6 return oldValue; 7 }
逻辑很简单,就是先检查index,然后新元素替换旧元素,并返回旧元素。
在JDK1.8中,添加了一个新方法,批量修改replaceAll(UnaryOperator<E> operator):
1 public void replaceAll(UnaryOperator<E> operator) { 2 Objects.requireNonNull(operator); 3 final int expectedModCount = modCount; 4 final int size = this.size; 5 for (int i=0; modCount == expectedModCount && i < size; i++) { 6 elementData[i] = operator.apply((E) elementData[i]); 7 } 8 if (modCount != expectedModCount) { 9 throw new ConcurrentModificationException(); 10 } 11 modCount++; 12 }
UnaryOperator<T> extends Function<T, T>,而Function<T,R>方法中有一个抽象方法,R apply<T,t>,方法的原意应该是将一个T转换成R.而这里使用,UnaryOperator,则只能将T转换成T。我们在使用ReplaceAll方法是,必须重写apply方法,作为转换规则。例如:
1 public class ArrayListTest { 2 public static void main(String[] args) { 3 List<String> list = new ArrayList<String>(); 4 list.add("科比"); 5 list.add("詹姆斯"); 6 list.add("库里"); 7 list.replaceAll(new UnaryOperator<String>() { 8 @Override 9 public String apply(String t) { 10 // TODO Auto-generated method stub 11 return t+"牛逼"; 12 } 13 }); 14 System.out.println(list); 15 } 16 }
输出的结果为:
[科比牛逼, 詹姆斯牛逼, 库里牛逼]
ArrayList查找元素得方法:
1 public E get(int index) { 2 rangeCheck(index); 3 4 return elementData(index); 5 }
get(int index)方法,通过元素的下标来获取元素。其原理就是获取数组的当前下表的元素。
1 public int indexOf(Object o) { 2 if (o == null) { 3 for (int i = 0; i < size; i++) 4 if (elementData[i]==null) 5 return i; 6 } else { 7 for (int i = 0; i < size; i++) 8 if (o.equals(elementData[i])) 9 return i; 10 } 11 return -1; 12 }
indextOf(Object o),查找ArrayList是否含有某元素,且返回该元素在集合中的第一个位置的下标。通过遍历元素,获取该集合所含有的第一个该元素的下标。如果不含有该元素,则返回-1.
1 public int lastIndexOf(Object o) { 2 if (o == null) { 3 for (int i = size-1; i >= 0; i--) 4 if (elementData[i]==null) 5 return i; 6 } else { 7 for (int i = size-1; i >= 0; i--) 8 if (o.equals(elementData[i])) 9 return i; 10 } 11 return -1; 12 }
lastIndexOf(Object o),与indexOf方法想法,该方法查找该元素在集合中最后一个位置,并返回下标。其逻辑与indexOf基本一致,只不过是在遍历的时候选择从后往前遍历。
1 public boolean isEmpty() { 2 return size == 0; 3 }
isEmpth()方法用来查看集合是否为空集合。如果size=0,则为空集合,返回true。否则返回false。
public boolean contains(Object o) { return indexOf(o) >= 0; }
contains(Object o)查看集合中是否含有某元素。其调用indexOf(Object o),如果含有返回ture,否则返回false.
public int size() { return size; }
size()方法用来查看集合中含有多少个元素,返回元素个数。
ArrayList的一些其他常用方法:
1 public Object clone() { 2 try { 3 ArrayList<?> v = (ArrayList<?>) super.clone(); 4 v.elementData = Arrays.copyOf(elementData, size); 5 v.modCount = 0; 6 return v; 7 } catch (CloneNotSupportedException e) { 8 // this shouldn‘t happen, since we are Cloneable 9 throw new InternalError(e); 10 } 11 }
clone()方法,用来复制集合并返回一个新的集合。而查看copyOf源码,发现最底层是使用native方法进行的复制。我无法确定其到底是深复制还是浅复制。
private static native Object newArray(Class<?> componentType, int length) throws NegativeArraySizeException;
所以我写了一个简单的测试代码,代码如下:
1 public class ArrayListTest{ 2 public static void main(String[] args) { 3 ArrayList<User> list = new ArrayList<User>(); 4 list.add(new User("科比")); 5 list.add(new User("詹姆斯")); 6 list.add(new User("库里")); 7 ArrayList list1= (ArrayList) list.clone(); 8 User user = list.get(1); 9 user.name = "麦迪"; 10 System.out.println(list1); 11 } 12 } 13 14 class User{ 15 String name; 16 public User(String name){ 17 this.name = name; 18 } 19 @Override 20 public String toString() { 21 return name; 22 } 23 }
打印结果为:
[科比, 麦迪, 库里]
说明ArrayList的复制为潜复制,因为其数组中的元素,并没有进行值复制,而是直接复制了元素的引用。
在Java8中,ArrayList添加了一种新的遍历方法。foreach+lambda表达式遍历。代码如下:
1 public class ArrayListTest{ 2 public static void main(String[] args) { 3 ArrayList<String> list = new ArrayList<String>(); 4 list.add("科比"); 5 list.add("詹姆斯"); 6 list.add("库里"); 7 list.forEach((s)->System.out.println(s)); 8 } 9 }
因为不懂lambda表达式的实现原理,foreach的源码实在看不懂。等以后研究了lambda表达式的实现原理,在回来研究下foreach方法。
在Java8中,ArrayList同样新添加了一种排序方法。sort(Comparator<? super E> c).源码如下:
1 @Override 2 @SuppressWarnings("unchecked") 3 public void sort(Comparator<? super E> c) { 4 final int expectedModCount = modCount; 5 Arrays.sort((E[]) elementData, 0, size, c); 6 if (modCount != expectedModCount) { 7 throw new ConcurrentModificationException(); 8 } 9 modCount++; 10 }
发现,ArrayList的排序,其实就是调用了数组的排序。我们继续往下看,数组是如何排序的:
1 public static <T> void sort(T[] a, int fromIndex, int toIndex, 2 Comparator<? super T> c) { 3 if (c == null) { 4 sort(a, fromIndex, toIndex); 5 } else { 6 rangeCheck(a.length, fromIndex, toIndex); 7 if (LegacyMergeSort.userRequested) 8 legacyMergeSort(a, fromIndex, toIndex, c); 9 else 10 TimSort.sort(a, fromIndex, toIndex, c, null, 0, 0); 11 } 12 }
1 public static void sort(Object[] a, int fromIndex, int toIndex) { 2 rangeCheck(a.length, fromIndex, toIndex); 3 if (LegacyMergeSort.userRequested) 4 legacyMergeSort(a, fromIndex, toIndex); 5 else 6 ComparableTimSort.sort(a, fromIndex, toIndex, null, 0, 0); 7 }
1 private static void legacyMergeSort(Object[] a, 2 int fromIndex, int toIndex) { 3 Object[] aux = copyOfRange(a, fromIndex, toIndex); 4 mergeSort(aux, a, fromIndex, toIndex, -fromIndex); 5 }
1 @SuppressWarnings({"unchecked", "rawtypes"}) 2 private static void mergeSort(Object[] src, 3 Object[] dest, 4 int low, 5 int high, 6 int off) { 7 int length = high - low; 8 9 // Insertion sort on smallest arrays 10 if (length < INSERTIONSORT_THRESHOLD) { 11 for (int i=low; i<high; i++) 12 for (int j=i; j>low && 13 ((Comparable) dest[j-1]).compareTo(dest[j])>0; j--) 14 swap(dest, j, j-1); 15 return; 16 } 17 18 // Recursively sort halves of dest into src 19 int destLow = low; 20 int destHigh = high; 21 low += off; 22 high += off; 23 int mid = (low + high) >>> 1; 24 mergeSort(dest, src, low, mid, -off); 25 mergeSort(dest, src, mid, high, -off); 26 27 // If list is already sorted, just copy from src to dest. This is an 28 // optimization that results in faster sorts for nearly ordered lists. 29 if (((Comparable)src[mid-1]).compareTo(src[mid]) <= 0) { 30 System.arraycopy(src, low, dest, destLow, length); 31 return; 32 } 33 34 // Merge sorted halves (now in src) into dest 35 for(int i = destLow, p = low, q = mid; i < destHigh; i++) { 36 if (q >= high || p < mid && ((Comparable)src[p]).compareTo(src[q])<=0) 37 dest[i] = src[p++]; 38 else 39 dest[i] = src[q++]; 40 } 41 }
1 /** To be removed in a future release. */ 2 private static <T> void legacyMergeSort(T[] a, int fromIndex, int toIndex, 3 Comparator<? super T> c) { 4 T[] aux = copyOfRange(a, fromIndex, toIndex); 5 if (c==null) 6 mergeSort(aux, a, fromIndex, toIndex, -fromIndex); 7 else 8 mergeSort(aux, a, fromIndex, toIndex, -fromIndex, c); 9 }
1 @SuppressWarnings({"rawtypes", "unchecked"}) 2 private static void mergeSort(Object[] src, 3 Object[] dest, 4 int low, int high, int off, 5 Comparator c) { 6 int length = high - low; 7 8 // Insertion sort on smallest arrays 9 if (length < INSERTIONSORT_THRESHOLD) { 10 for (int i=low; i<high; i++) 11 for (int j=i; j>low && c.compare(dest[j-1], dest[j])>0; j--) 12 swap(dest, j, j-1); 13 return; 14 } 15 16 // Recursively sort halves of dest into src 17 int destLow = low; 18 int destHigh = high; 19 low += off; 20 high += off; 21 int mid = (low + high) >>> 1; 22 mergeSort(dest, src, low, mid, -off, c); 23 mergeSort(dest, src, mid, high, -off, c); 24 25 // If list is already sorted, just copy from src to dest. This is an 26 // optimization that results in faster sorts for nearly ordered lists. 27 if (c.compare(src[mid-1], src[mid]) <= 0) { 28 System.arraycopy(src, low, dest, destLow, length); 29 return; 30 } 31 32 // Merge sorted halves (now in src) into dest 33 for(int i = destLow, p = low, q = mid; i < destHigh; i++) { 34 if (q >= high || p < mid && c.compare(src[p], src[q]) <= 0) 35 dest[i] = src[p++]; 36 else 37 dest[i] = src[q++]; 38 } 39 }
可以看出,当比较器为空时,调用了mergeSort(Object[] src,Object[] dest,int low, int high, int off)方法,不为空时,调用了mergeSort(Object[] src,Object[] dest,int low, int high, int off,Comparator c)方法,两个方法基本一模一样,唯一的区别就是在有默认比较器的时候,两个元素的比较实用默认比较器的比较方法来比较。
仔细看两个方法,可以看出,ArrayList.sort方法 时间上是使用了一种优化过后的递归排序,在数组长度小于7的时候使用直接插入排序。数组长度大于7的时候,使用归并排序,直至子数组的长度小于7.
归并排序详见我的另一篇随笔经典排序算法--归并排序。
写在最后:
此篇随笔仅用来记录我的学习内容,如有错误,欢迎指正。谢谢!!!