Java容器学习之ArrayList
一、概述
ArrayList是java中十分常用的集合类,继承于AbstractList,并实现了List、RandomAccess、Cloneable和Serializable接口。ArrayList底层是使用数组来实现的,是一个动态的数组队列,它具有以下特点。
- 可以动态扩容、缩容
- ArrayList的操作不是线程安全的
- 允许元素重复,允许元素为空
ArrayList初始默认大小是10,每次扩容时是原大小的1.5倍,如果一开始就知道需要的Lsit长度,可以指定ArrayList的长度,减少扩容操作。
二、源码分析(JDK 1.8)
2.1 ArrayList属性
// 默认的容量 private static final int DEFAULT_CAPACITY = 10; // 保存ArrayList中数据使用的数组 transient Object[] elementData // ArrayList的元素个数 private int size;
此外还有一个成员变量modCount,这个变量用来记录修改次数,增删改的都会改变modCount的值。ArrayList是线程不安全的,如果在使用迭代器的过程中有其他线程修改了List,那么将抛出ConcurrentModificationException,是Fail-Fast策略的应用。
2.2 构造函数
1 // 构造一个初始容量为10的空列表 2 public ArrayList() { 3 this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; 4 } 5 // 构造具有指定容量的空列表 6 public ArrayList(int initialCapacity) { 7 if (initialCapacity > 0) { 8 this.elementData = new Object[initialCapacity]; 9 } else if (initialCapacity == 0) { 10 this.elementData = EMPTY_ELEMENTDATA; 11 } else { 12 throw new IllegalArgumentException("Illegal Capacity: "+ 13 initialCapacity); 14 } 15 } 16 17 // 构造一个包含指定容器元素的列表,按照容器迭代器返回的顺序排列 18 public ArrayList(Collection<? extends E> c) { 19 elementData = c.toArray(); 20 if ((size = elementData.length) != 0) { 21 // c.toArray might (incorrectly) not return Object[] (see 6260652) 22 if (elementData.getClass() != Object[].class) 23 elementData = Arrays.copyOf(elementData, size, Object[].class); 24 } else { 25 // replace with empty array. 26 this.elementData = EMPTY_ELEMENTDATA; 27 } 28 }
ArrayList有三个构造函数,如上代码所示。
2.3 trimToSize()
public void trimToSize() { modCount++; if (size < elementData.length) { elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size); } }
trimToSize()方法可以将ArrayList的底层数组缩容到当前ArrayList中元素的个数(ArrayList的size值),使用这个方法可以最小化ArrayList使用的存储空间。
2.4 添加
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 // 在列表尾部添加元素 2 public boolean add(E e) { 3 ensureCapacityInternal(size + 1); // Increments modCount!! 4 elementData[size++] = e; 5 return true; 6 } 7 // 在指定位置添加指定元素 8 public void add(int index, E element) { 9 rangeCheckForAdd(index); 10 11 ensureCapacityInternal(size + 1); // Increments modCount!! 12 System.arraycopy(elementData, index, elementData, index + 1, 13 size - index); 14 elementData[index] = element; 15 size++; 16 } 17 // 将集合c中元素添加到列表尾部 18 public boolean addAll(Collection<? extends E> c) { 19 Object[] a = c.toArray(); 20 int numNew = a.length; 21 ensureCapacityInternal(size + numNew); // Increments modCount 22 System.arraycopy(a, 0, elementData, size, numNew); 23 size += numNew; 24 return numNew != 0; 25 }
add方法中都用到了ensureCapacityInternal方法,这个方法用于对ArrayList进行扩容。
2.5 扩容
1 // 扩容操作 2 private void ensureCapacityInternal(int minCapacity) { 3 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { 4 minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); 5 } 6 7 ensureExplicitCapacity(minCapacity); 8 } 9 10 private void ensureExplicitCapacity(int minCapacity) { 11 modCount++; 12 13 // overflow-conscious code 14 if (minCapacity - elementData.length > 0) 15 grow(minCapacity); 16 } 17 // 增加容量,使列表可以的容量至少满足minCapacity个元素 18 private void grow(int minCapacity) { 19 // overflow-conscious code 20 int oldCapacity = elementData.length; 21 int newCapacity = oldCapacity + (oldCapacity >> 1); 22 if (newCapacity - minCapacity < 0) 23 newCapacity = minCapacity; 24 if (newCapacity - MAX_ARRAY_SIZE > 0) 25 newCapacity = hugeCapacity(minCapacity); 26 // minCapacity is usually close to size, so this is a win: 27 elementData = Arrays.copyOf(elementData, newCapacity); 28 }
ensureExplicitCapacity()方法先判断当前数组的容量是否可以满足需求,如果不满足就使用grow进行扩容操作,每次扩容为原来的1.5倍。
2.6 删除
1 // 删除指定位置的元素,并返回被删除的元素 2 public E remove(int index) { 3 // 范围检查 4 rangeCheck(index); 5 6 modCount++; 7 E oldValue = elementData(index); 8 // 需要移动的元素数 9 int numMoved = size - index - 1; 10 if (numMoved > 0) 11 // 等于0表示删除的是末尾的元素,不需要移动 12 System.arraycopy(elementData, index+1, elementData, index, 13 numMoved); 14 elementData[--size] = null; // clear to let GC do its work 15 16 return oldValue; 17 }
E remove(int index) 删除指定位置的元素
// 范围检查 private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }
删除前进行边界检查
1 // 删除列表中首次出现的指定元素,如果有多个只删除第一个 2 public boolean remove(Object o) { 3 if (o == null) { 4 for (int index = 0; index < size; index++) 5 if (elementData[index] == null) { 6 fastRemove(index); 7 return true; 8 } 9 } else { 10 for (int index = 0; index < size; index++) 11 if (o.equals(elementData[index])) { 12 fastRemove(index); 13 return true; 14 } 15 } 16 return false; 17 } 18 // 私有的跳过边界检查快速删除指定位置元素的方法 19 private void fastRemove(int index) { 20 modCount++; 21 int numMoved = size - index - 1; 22 if (numMoved > 0) 23 System.arraycopy(elementData, index+1, elementData, index, 24 numMoved); 25 elementData[--size] = null; // clear to let GC do its work 26 }
public boolean remove(Object o) 删除首次出现的指定元素,若有多个只删除第一个
1 // 删除指定范围的元素 2 protected void removeRange(int fromIndex, int toIndex) { 3 modCount++; 4 int numMoved = size - toIndex; 5 System.arraycopy(elementData, toIndex, elementData, fromIndex, 6 numMoved); 7 8 // clear to let GC do its work 9 int newSize = size - (toIndex-fromIndex); 10 for (int i = newSize; i < size; i++) { 11 elementData[i] = null; 12 } 13 size = newSize; 14 }
void removeRange(int fromIndex, int toIndex) 删除[fromIndex, toIndex)范围的元素
1 // 从列表中删除指定集合c中的元素 2 public boolean removeAll(Collection<?> c) { 3 Objects.requireNonNull(c); 4 return batchRemove(c, false); 5 } 6 // 列表中保留指定集合c中的元素 7 public boolean retainAll(Collection<?> c) { 8 Objects.requireNonNull(c); 9 return batchRemove(c, true); 10 } 11 // 从List的底层数组中删除或者保留指定集合中元素的值,complement为true是保留指定集合元素,为false是删除指定集合元素 12 private boolean batchRemove(Collection<?> c, boolean complement) { 13 final Object[] elementData = this.elementData; 14 int r = 0, w = 0; 15 boolean modified = false; 16 try { 17 18 for (; r < size; r++) 19 if (c.contains(elementData[r]) == complement) 20 elementData[w++] = elementData[r]; 21 } finally { 22 // Preserve behavioral compatibility with AbstractCollection, 23 // even if c.contains() throws. 24 if (r != size) { 25 System.arraycopy(elementData, r, 26 elementData, w, 27 size - r); 28 w += size - r; 29 } 30 // w==size表示全部元素保留,没有删除操作发生,否则返回true,并更改数组 31 if (w != size) { 32 // clear to let GC do its work 33 for (int i = w; i < size; i++) 34 elementData[i] = null; 35 modCount += size - w; 36 size = w; //新的大小为剩下元素的个数 37 modified = true; 38 } 39 } 40 return modified; 41 }
在try块中首先遍历数组,如果要保留集合c中的元素就把相同元素移到数组前段,如果要删除集合c中的数据就把不同元素移到数组前段,将剩下的数据保存在0到w之间,
第一个if语句是为了处理c.contains()抛出异常时执行;第二个if语句是处理w之后的空间。