java集合系列-ArrayList,简单介绍ArrayList的底层结构,及ArrayList扩容技术。
概念 ArrayList的底层是数组队列,相当于动态数组,与Java中的数组相比,它的容量能动态增长,在添加大量元素前,应用程序可以使用ensureCapacity来增加ArrayList实例的容量。
ArrayList继承AbstractList,实现了List、RandomAccess、Cloneable、java.io.Serializable这些接口。
构造函数 ArrayList有三种方式来初始化,源代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 private static final int DEFAULT_CAPACITY = 10 ; private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; public ArrayList () { this .elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } 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); } } public ArrayList (Collection<? extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0 ) { if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { this .elementData = EMPTY_ELEMENTDATA; } }
无参数构造方法构建ArrayList时,实际上初始化赋值的是一个空数组,当真正对数组进行添加元素操作时,才真正分配容量。即向数组添加第一个元素时,数组扩容为10。
扩容 添加元素时,使用ensureCapacityInternal()方法来保证容量足够,如果不够,需要使用grow()方法,进行扩容。新容量的大小为oldCapacity+(oldCapacity >> 1),也就是旧容量的1.5倍。
扩容操作需要调用Arrays.copyOf(),把原来的数组整个复制到新的数组,这个操作代价很高,因此最好在创建ArrayList对象时就制定大概的容量大小,减少扩容操作的次数。
源代码 ensureCapacityInternal() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public void ensureCapacity (int minCapacity) { int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) ? 0 : DEFAULT_CAPACITY; if (minCapacity > minExpand) { ensureExplicitCapacity(minCapacity); } } private void ensureCapacityInternal (int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity (int minCapacity) { modCount++; if (minCapacity - elementData.length > 0 ) grow(minCapacity); }
grow() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8 ; private void grow (int minCapacity) { int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1 ); if (newCapacity - minCapacity < 0 ) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0 ) newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copyOf(elementData, newCapacity); }
扩容过程中应用了 移位运算符 ,移位运算符就是在二进制的基础上对数字进行平移。分为三种:<<(左移),>>(带符号右移),>>>(无符号右移)。对于大数据的2进制运算,位移运算比普通的运算要快的多,oldCapacity >> 1,右移一位,相当于除以2。
另外:
java 中的 length
属性是针对数组说的,比如说你声明了一个数组,想知道这个数组的长度则用到了 length 这个属性.
java 中的 length()
方法是针对字符串说的,如果想看这个字符串的长度则用到 length()
这个方法.
java 中的 size()
方法是针对泛型集合说的,如果想看这个泛型有多少个元素,就调用此方法来查看!
hugeCapacity() 从上面 grow()
方法源码我们知道: 如果新容量大于 MAX_ARRAY_SIZE,进入(执行) hugeCapacity()
方法来比较 minCapacity 和 MAX_ARRAY_SIZE,如果minCapacity大于最大容量,则新容量则为Integer.MAX_VALUE
,否则,新容量大小则为 MAX_ARRAY_SIZE 即为 Integer.MAX_VALUE - 8
。
1 2 3 4 5 6 7 8 9 10 11 private static int hugeCapacity (int minCapacity) { if (minCapacity < 0 ) throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
ArrayList源码中大量调用了System.arraycopy()和Arrays.copyOf(),比如扩容操作以及add、toArray等方法中都用到了该方法
System.arraycopy()和Arrays.copyOf()区别:
联系:Arrays.copyOf()内部调用了System.arraycopy()方法
区别:System.arraycopy() 需要目标数组,将原数组拷贝到你自己定义的数组里或者原数组,而且可以选择拷贝的起点和长度以及放入新数组中的位置 Arrays.copyOf() 是系统自动在内部新建一个数组,并返回该数组。
新增元素 新增元素调用add(E e)方法时,ArrayList会默认将插入的元素追加到链表末尾,这种情况时间的复杂度是O(1)。
1 2 3 4 5 6 7 8 9 10 public boolean add (E e) { ensureCapacityInternal(size + 1 ); elementData[size++] = e; return true ; }
但是如果在指定位置i插入数据时,时间复杂度为O(n)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public void add (int index, E element) { rangeCheckForAdd(index); ensureCapacityInternal(size + 1 ); System.arraycopy(elementData, index, elementData, index + 1 , size - index); elementData[index] = element; size++; }
删除元素 需要调用System.arraycopy()将index+1后面的元素都复制到index位置上,该操作的时间复杂度为O(n),可以看出删除元素的代价是非常高的。
源代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 public E remove (int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1 ; if (numMoved > 0 ) System.arraycopy(elementData, index+1 , elementData, index, numMoved); elementData[--size] = null ; return oldValue; } 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 ; } 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 ; } public void clear () { modCount++; for (int i = 0 ; i < size; i++) elementData[i] = null ; size = 0 ; }
快速随机访问 ArrayList实现了RandomAccess接口,ArrayList底层时数组,数组天然支持随机访问,时间复杂度为O(1),所以称为快速随机访问。
遍历list时注意:
实现了RandomAccess接口的list,优先选择普通for,其次是foreach
未实现RandomAccess接口的list,优先选择iterator(foreach底层是iterator实现的),大量数据时,千万不要使用for循环
常用的调用ArrayList代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 public class ArrayListDemo { public static void main (String[] srgs) { ArrayList<Integer> arrayList = new ArrayList<Integer>(); System.out.printf("Before add:arrayList.size() = %d\n" ,arrayList.size()); arrayList.add(1 ); arrayList.add(3 ); arrayList.add(5 ); arrayList.add(7 ); arrayList.add(9 ); System.out.printf("After add:arrayList.size() = %d\n" ,arrayList.size()); System.out.println("Printing elements of arrayList" ); System.out.print("通过迭代器遍历:" ); Iterator<Integer> it = arrayList.iterator(); while (it.hasNext()){ System.out.print(it.next() + " " ); } System.out.println(); System.out.print("通过索引值遍历:" ); for (int i = 0 ; i < arrayList.size(); i++){ System.out.print(arrayList.get(i) + " " ); } System.out.println(); System.out.print("for循环遍历:" ); for (Integer number : arrayList){ System.out.print(number + " " ); } Integer[] integer = arrayList.toArray(new Integer[0 ]); Integer[] integer1 = new Integer[arrayList.size()]; arrayList.toArray(integer1); System.out.println(); arrayList.add(2 ,2 ); arrayList.remove(2 ); arrayList.remove((Object)3 ); System.out.println("ArrayList contains 5 is: " + arrayList.contains(5 )); arrayList.clear(); System.out.println("ArrayList is empty: " + arrayList.isEmpty()); } }