ArrayList
一、简介
ArrayList
的底层是数组队列,相当于动态数组。与 Java 中的数组相比,它的容量能动态增长。在添加大量元素前,应用程序可以使用ensureCapacity
操作来增加 ArrayList
实例的容量。这可以减少递增式再分配的数量。
二、ArraList核心源码
private static final long serialVersionUID = 8683452581122892189L; /** * 默认初始化容量 DEFAULT_CAPACITY 为10 */ private static final int DEFAULT_CAPACITY = 10; /** * 指定该ArrayList容量为0时,返回该空数组 */ private static final Object[] EMPTY_ELEMENTDATA = {}; /** * 当调用无参构造方法,返回的是该数组。刚创建一个ArrayList 时,其内数据量为0。 * 它与EMPTY_ELEMENTDATA的区别就是:该数组是默认返回的,而后者是在用户指定容量为0时返回。 */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** * The array buffer into which the elements of the ArrayList are stored. * The capacity of the ArrayList is the length of this array buffer. Any * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA * will be expanded to DEFAULT_CAPACITY when the first element is added. */ // 底层数组 transient Object[] elementData; // non-private to simplify nested class access /** * 数组的长度 */ private int size;
ArrayList的构造函数
在JDK8
中ArrayList有三种方式来初始化:
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); } } /** * 默认无参构造函数 * DEFAULTCAPACITY_EMPTY_ELEMENTDATA 为0.初始化为10,也就是说初始其实是空数组 当添加第一个元素的时候数组容量才变成10 */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } /** * Constructs a list containing the elements of the specified * collection, in the order they are returned by the collection's * iterator. * * @param c the collection whose elements are to be placed into this list * @throws NullPointerException if the specified collection is null */ 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; } }
在无参构造函数中、初始化赋值的是一个空数组,当添加元素时,才真正分配容量。即向数组添加第一个元素时,容量为10。
ArrayList的扩容机制
add方法
/** * 将指定的元素追加到此列表的末尾. * * @param e 添加到列表末尾的元素 * @return <tt>true</tt> (as specified by {@link Collection#add}) */ public boolean add(E e) { // 扩容判断 ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
add()方法调用了ensureCapacityInternal()方法
private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); }
calculateCapacity()方法
private static int calculateCapacity(Object[] elementData, int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; }
-
当添加第一个元素后,minCapacity经过Math.max()方法比较后,大小变为DEFAULT_CAPACITY即10.
ensureExplicitCapacity()方法
// 判断是否需要扩容 private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) // 调用grow方法进行扩容 grow(minCapacity); }
分析:
-
当add进第一个元素时,elementData.length为0。minCapacity经过Math.max()方法比较后,大小变为10。 此时调用grow()方法进行扩容。
-
当add进第二个元素时,minCapacity为2,但是elementData.length在第一次扩容后变为10。 此时不进入grow()方法。
-
当添加第11个元素时 minCapacity > elementData.length 。再进入grow()方法扩容。
grow()方法
/** * ArrayList扩容的核心方法 */ private void grow(int minCapacity) { // overflow-conscious code // oldCapacity为旧容量,newCapacity为新容量 int oldCapacity = elementData.length; // 将oldCapacity为旧容量右移一位,其效果相当于oldCapacity / 2 // 我们知道位运算的速度远远快于整除运算,整句运算式的结果就是将新容量更新为旧容量的1.5倍 int newCapacity = oldCapacity + (oldCapacity >> 1); // 检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量作为新容量 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; // 如果新容量大于MAX_ARRAY_SIZE,执行 `hugeCapacity()` 方法来比较minCapacity和MAX_ARRAY_SIZE /* 如果minCapacity大于最大容量,则新容量为 `Intergar.MAX_VALUE` ,否则,新容量大小为 MAX_ARRAY_SIZE * 即为 `Integer.MAX_VALUE - 8` */ if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); }
hugeCapacity()方法
// 比较minCapacity 和 MAX_ARRAY_SIZE private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); // 对 minCapacity 和 MAX_ARRAY_SIZE进行比较 // 若 minCapacity大,将 Integer.MAX_VALUE作为新数组大小 // 若 MAX_ARRAY_SIZE大,将 MAX_ARRAY_SIZE 作为新数组大小 // MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
三、代码测试
import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; public class AListTest { public static Integer getCapacity(ArrayList list){ Integer length = null; Class c = list.getClass(); Field f; try { f = c.getDeclaredField("elementData"); f.setAccessible(true); Object[] o = (Object[]) f.get(list); length = o.length; }catch (NoSuchFieldException | IllegalAccessException e) { e.printStackTrace(); } return length; } public static void main(String[] args) { // ArrayList常用方法及机制测试 /** * ArrayList扩容机制测试 */ ArrayList<Integer> a = new ArrayList<>(); System.out.println("空数组大小和容量分别是"+a.size()+" "+getCapacity(a)); a.add(1); System.out.println("空数组添加1个数据后,大小和容量分别是"+a.size()+" "+getCapacity(a)); a.add(2); System.out.println("空数组添加2个数据后,大小和容量分别是"+a.size()+" "+getCapacity(a)); for (int i = 1; i <= 8; i++) { a.add( i); } System.out.println("空数组添加到了10个数据后,大小和容量分别是"+a.size()+" "+getCapacity(a)); a.add(2); System.out.println("空数组添加11个数据后,大小和容量分别是"+a.size()+" "+getCapacity(a)); } }
四、高并发测试
我们知道ArrayList是线程不安全的,请编写一个不安全的案例并给出解决方案
1、线程不安全
List<String> list = new ArrayList<>(); for (int i = 1; i <= 30; i++) { new Thread(() -> { list.add(UUID.randomUUID().toString().substring(0,8)); System.out.println(list); },String.valueOf(i)).start(); }
报错:
Exception in thread "Thread 10" java.util.ConcurrentModificationException
2.导致原因
并发非正常修改导致
一个人正在写入,另一个同学来抢夺,导致数据不一致,并发修改异常
3、解决方法:CopyOnWriteArrayList
List<String> list = new Vector<>();//Vector线程安全 List<String> list = Collections.synchronizedList(new ArrayList<>());//使用辅助类 List<String> list = new CopyOnWriteArrayList<>();//写时复制,读写分离 Map<String, String> map = new ConcurrentHashMap<>(); Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
CopyOnWriteArrayList.add方法:
CopyOnWrite容器即写时复制,往一个元素添加容器的时候,不直接往当前容器Object[]添加,而是先将当前容器Object[]进行copy,复制出一个新的容器Object[] newElements,让后新的容器添加元素,添加完元素之后,再将原容器的引用指向新的容器setArray(newElements),这样做可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素,所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器
public boolean add(E e) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1); newElements[len] = e; setArray(newElements); return true; } finally { lock.unlock(); } }