前言
我思故我在
ArrayList的简介
1,ArrayList 的底层是数组队列,相当于动态数组。与 Java 中的数组相比,它的容量能动态增长。在添加大量元素前,应用程序可以使用ensureCapacityXXX 方法来操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量。
2,它继承了AbstractList,实现了 List, RandomAccess, Cloneable, java.io.Serializable 这些接口。
3,插入删除元素的时间复杂度为O(n),求表长以及增加元素,取第 i 元素的时间复杂度为O(1)
4,继承AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。
5,实现RandomAccess 接口, RandomAccess 是一个标志接口,表明实现这个这个接口的 List 集合是支持快速随机访问的。在 ArrayList 中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。对于实现了RandomAccess 接口的集合在使用Collections.binarySearch方法,的执行策略有所不同。
6,实现Cloneable 接口,即覆盖了函数 clone(),能被克隆。
7,实现java.io.Serializable 接口,这意味着ArrayList支持序列化,能通过序列化去传输。
8,和 Vector 不同,ArrayList 中的操作不是线程安全的!所以,建议在单线程中才使用 ArrayList,而在多线程中可以选择 Vector 或者 CopyOnWriteArrayList。
9,ArrayList的图解如下。
源码分析
首先我们先来看看ArrayList集合的相关属性和构造方法
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { private static final long serialVersionUID = 8683452581122892189L; /** * 设置默认容量为10 */ private static final int DEFAULT_CAPACITY = 10; /** * 如果我们使用的是ArrayList(int initialCapacity)来创建ArrayList数组时, * 如果我们initialCapacity等于0那么直接用EMPTY_ELEMENTDATA, * 如果initialCapacity大于0那么this.elementData = new Object[initialCapacity]; * 空数组(用于空实例)。 */ private static final Object[] EMPTY_ELEMENTDATA = {};//来初始化我们的elementData /** * 如果我们使用的是ArrayList() 的无参构造方法,那么将使用 * DEFAULTCAPACITY_EMPTY_ELEMENTDATA来初始化我们的elementData */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** * 用来存放集合中的元素 */ transient Object[] elementData;// non-private to simplify nested class access /** * 集合中容纳元素的个数 */ private int size; /** * 有参构造方法,我们可以在初始化的时候自定义elementData数组的长度 * @param initialCapacity 表示初始容量 */ 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() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } /** * 有参构造方法,参数是集合 */ public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0) { // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // replace with empty array. this.elementData = EMPTY_ELEMENTDATA; } } //。。。。。。 }
Add方法:①在末尾添加②指定位置添加
/** * 在末尾添加 */ public boolean add(E e) { //判断是否需要扩容 ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } /** * 在指定位置上添加元素 */ public void add(int index, E element) { rangeCheckForAdd(index);//判断这个坐标会不会发生集合越界 //判断是否需要扩容 ensureCapacityInternal(size + 1); // Increments modCount!! System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; }
ArrayList的扩容机制
我们要知道,ArrayList的扩容不是每次扩容只扩容一次,而是会扩容很多的空间。
通过add方法我们可以知道会调用了ensureCapacityInternal方法,这个就是在调用扩容机制。
ensureCapacityInternal(int minCapacity)的作用:得到最小扩容量
首先我们要知道ArrayList的扩容发生在添加元素阶段,比如我们要在x位置上添加元素,这个位置可能是集合尾部,集合中部或者集合前面,不管添加元素的位置在哪里,肯定是扩容数组的尾部。那么我们就要判断一下,需要扩容多少。
所以add方法调用ensureCapacityInternal(int minCapacity)方法,并传入参数size+1。
执行if语句,如果我们的的判断的结果是true,那么证明这是我们用无参构造方法ArrayList()生成的,所以得到最小的扩容量是DEFAULT_CAPACITY(默认值是10),则minCapacity=10。
如果返回的是false,证明elementData不是空数组,则minCapacity=size+1。
/** * 得到最小扩容量 */ private void ensureCapacityInternal(int minCapacity) {//minCapacity,为最小扩容量 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity);//传入最小扩容量 }
然后我们可以看到调用ensureExplicitCapacity(minCapacity)方法。
ensureExplicitCapacity(minCapacity)的作用:明确扩容量
首先,我们看到这个方法中执行了modCount++操作,这个东西很重要,我们在后面进行讲解。
执行if语句,如果我们判断结果是true,就证明我们真的需要扩容了。
如果返回false,证明不需要进行扩容,现在的数组长度,可以容纳这次添加元素
private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code //检查数组的容量是否足够 if (minCapacity - elementData.length > 0) grow(minCapacity);//真正扩容的方法 }
执行真正的扩容方法grow(minCapacity)
/* * 要分配的最大数组大小 */ private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; private void grow(int minCapacity) { //旧容量 int oldCapacity = elementData.length; /* 新容量,在这个用的是位运算符,在这里呢,顺便提一提位运算符 << : 左移运算符,num << 1,相当于num乘以2 >> : 右移运算符,num >> 1,相当于num除以2 >>> : 无符号右移,忽略符号位,空位都以0补齐 A >>> B -- B 指定要移位值A 移动的位数。 无符号右移的规则只记住一点:忽略了符号位扩展,0补最高位 位运算的执行效率是比普通的乘除要高的 所以说在这里呢,新容量更新为旧容量的1.5倍, */ int newCapacity = oldCapacity + (oldCapacity >> 1); //然后检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量当作数组的新容量 //这个时候,有的人可能会想,不是已经得到了新容量,为什么还有与minCapacity最小容量进行比较呢? //这是因为,在ArrayList的方法中我们其实可以直接自己任命最小容量的值, //调用ensureCapacity()方法,这个方法的具体细节,现在不讲。 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; //再检查新容量是否超出了ArrayList所定义的最大容量, //若超出了,则调用hugeCapacity()来比较minCapacity和 MAX_ARRAY_SIZE, //如果minCapacity大于MAX_ARRAY_SIZE,则新容量则为Interger.MAX_VALUE, //否则,新容量大小则为 MAX_ARRAY_SIZE。 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: //执行Arrays.copyOf方法,数组的拷贝复制 elementData = Arrays.copyOf(elementData, newCapacity); } //比较minCapacity和 MAX_ARRAY_SIZE private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
总结一下add(E e) 方法的扩容
1,检查数组的容量是否足够。
2,够,直接添加元素,不够,扩容到1.5倍。
3,第一次扩容后,如果还是小于minCapacity,就将容量扩充为minCapacity。
4,然后添加元素。
接着我们看一看add(int index, E element)方法
public void add(int index, E element) { rangeCheckForAdd(index); ensureCapacityInternal(size + 1); // Increments modCount!! System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; } //判断角标是否合法 private void rangeCheckForAdd(int index) { if (index > size || index < 0) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }
自定义扩容大小ensureCapacity方法
//这个方法我们可以直接调用,可以自定义扩容大小 public void ensureCapacity(int minCapacity) { int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) // any size if not default element table ? 0 // larger than default for default empty table. It's already // supposed to be at default size. : DEFAULT_CAPACITY; if (minCapacity > minExpand) { ensureExplicitCapacity(minCapacity); } }
get方法
public E get(int index) { rangeCheck(index);//判断角标是否合法 return elementData(index);//直接返回结果 } private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }
set方法
public E set(int index, E element) { rangeCheck(index);//判断角标是否合法 E oldValue = elementData(index); elementData[index] = element; return oldValue; } private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }
remove方法:①按角标删除②按元素删除
public E remove(int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; //elementData:源数组 //index + 1:源数组中的起始位置 //elementData:目标数组 //index:目标数组中的起始位置 //numMoved:要复制的数组元素的数量; if (numMoved > 0) System.arraycopy(elementData, index + 1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work 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; // clear to let GC do its work }
ArrayList中modCount的作用
在ArrayList中有个成员变量modCount,继承于AbstractList。
这个成员变量记录着集合的修改次数,也就每次add或者remove它的值都会加1。这到底有什么用呢?
import java.util.ArrayList; import java.util.Iterator; public class Demo { public static void main(String args[]){ ArrayList list = new ArrayList(); list.add(1); list.add(2); list.add(3); list.add(4); list.add(5); Iterator iterator = list.iterator(); while(iterator.hasNext()){ Integer number = (Integer) iterator.next(); if(number == 3){ list.remove(number); } System.out.println(number); } } }
我可以告诉大家这个代码会抛异常,ConcurrentModificationException
为什么,因为ArrayList被设计成非同步的,不能在遍历的时候进行修改和删除,这个就与modCount有关。
//执行下面的代码 Iterator iterator = list.iterator(); public Iterator<E> iterator() { return new Itr(); } //接着就会调用ArrayList内部实现的Iterator接口的类Itr private class Itr implements Iterator<E> { int cursor; // index of next element to return int lastRet = -1; // index of last element returned; -1 if no such //会将自己的ArrayList里面的modCount复制给内部类中的expectedModCount int expectedModCount = modCount; //调用遍历函数 public E next() { checkForComodification(); int i = cursor; if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1; return (E) elementData[lastRet = i]; } //在这里面我们可以看到一个checkForComodification(); //在这个方法中,if语句里面modCount != expectedModCount如果不等会抛异常。 //我们可以知道如果我们在遍历的时候调用ArrayList的add或者remove方法, //会导致执行modCount++操作,从而导致异常 final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
ArrayList中RandomAccess 接口的作用
查看源码我们发现实际上 RandomAccess 接口中什么都没有定义。所以,在我看来 RandomAccess 接口不过是一个标识罢了。标识什么? 标识实现这个接口的类具有随机访问功能
这个接口的作用要在调用Collections.binarySearch() 才有用。
在这里我们可以看到ArrayList 实现了 RandomAccess 接口,就表明了他具有快速随机访问功能
public static <T> int binarySearch(List<? extends Comparable<? super T>> list, T key) { if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD) return Collections.indexedBinarySearch(list, key); else return Collections.iteratorBinarySearch(list, key); } //实现了RandomAccess接口 private static <T> int indexedBinarySearch(List<? extends Comparable<? super T>> list, T key) { int low = 0; int high = list.size()-1; while (low <= high) { int mid = (low + high) >>> 1; Comparable<? super T> midVal = list.get(mid); int cmp = midVal.compareTo(key); if (cmp < 0) low = mid + 1; else if (cmp > 0) high = mid - 1; else return mid; // key found } return -(low + 1); // key not found } //没有实现 private static <T> int iteratorBinarySearch(List<? extends Comparable<? super T>> list, T key) { int low = 0; int high = list.size()-1; ListIterator<? extends Comparable<? super T>> i = list.listIterator(); while (low <= high) { int mid = (low + high) >>> 1; Comparable<? super T> midVal = get(i, mid); int cmp = midVal.compareTo(key); if (cmp < 0) low = mid + 1; else if (cmp > 0) high = mid - 1; else return mid; // key found } return -(low + 1); // key not found }
list 的遍历方式选择:
实现RandomAccess 接口的list,其实可以看做是使用了for循环。
没有实现 RandomAccess 接口的list,其实调用的是iterator遍历。
来源:https://www.cnblogs.com/phoniex22/p/12370884.html