ArrayList在我们平时工作当中,已经是熟悉不能在熟悉的一个集合类了,但是我们有思考过为什么要用ArrayList吗?我们都知道ArrayList底层数据结构是数组,那我们为什么直接用数组?
好的,那我们带着现在的问题一起探索一下ArrayList的奥妙之处吧;
第一说说我们为什么要用ArrayList?而不用数组?
数组我们知道,我们初始化的时候需要指定大小对吧?比如这样:
int[] arr = new int[3];
并且只能放入对应数组大小容量的数据,如果超过了数组大小就会报错,比如这样:
arr[0] = 1;
arr[1] = 2;
arr[2] = 3;
arr[4] = 4; // 数组下标越界 报错
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 4
at enums.Test.main(Test.java:41)
并且类型单一对吧,我们声明什么基础数据类型就只能放什么类型的数据;
而ArrayList呢?让我们看看ArrayList如何优雅实现自动扩容;
我们从ArrayList构造函数开始了解
1、默认构造函数
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
DEFAULTCAPACITY_EMPTY_ELEMENTDATA 默认一个空数组
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
2、带参数构造函数
2.1 数组初始化大小 initialCapacity 参数
public ArrayList(int initialCapacity) {
如果是大于0 初始化当前数组大小
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
等于0 默认一个空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
小于0抛异常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
2.2 数组初始化 Collection<? extends E> c 数组参数
public ArrayList(Collection<? extends E> c) {
Object[] a = c.toArray();
判断大小 为空的话直接默认一个空数组
if ((size = a.length) != 0) {
不为空的话判断数组类型是否是 ArrayList ,是的话这直接赋值数组
if (c.getClass() == ArrayList.class) {
elementData = a;
} else {
不是 ArrayList 类型 直接copy元素
elementData = Arrays.copyOf(a, size, Object[].class);
}
} else {
// replace with empty array.
elementData = EMPTY_ELEMENTDATA;
}
}
那我们通过源码知道了原来构造ArrayList有三种方式,如下:
List<Object> default_list = new ArrayList<>();
List<Object> default_list_size = new ArrayList<>(10);
List<Object> default_list_by_collection = new ArrayList<>(new HashSet<>(10));
ArrayList 创建我们了解完毕,我们现在来了解一下他的扩容机制以及add方法的过程:
当我们点进去add方法的时候就看到了如下代码:
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @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;
}
这个代码阅读很容易主要是进行一个数据扩容之后,数组大小赋值,添加成功返回true;
我们重点讲一下
ensureCapacityInternal 方法中的 【calculateCapacity(Object[] elementData, int minCapacity)】以及
ensureExplicitCapacity 方法中调用的grow(扩容机制) 方法
初始化数组大小
private static int calculateCapacity(Object[] elementData, int minCapacity) {
/如果数组等于空的话设置默认大小 默认为10
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity > 默认大小 返回自定义大小
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
原数组追加一个元素后的大小 > 原数组大小的话才进行扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
重点来了,我们看下grow 是如何实现的
/**
* The maximum size of array to allocate.
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {
获取当前数组的大小
int oldCapacity = elementData.length;
newCapacity = 当前数组大小 + 当前数组大小 / 2; 也就是大概当前数组的1.5倍,也是就是扩容大小
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
判断扩容后的大小 - 如果当前数组+1大小 如果小于0 返回 minCapacity
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
进行原数组copay 到扩容大小的新数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
如果minCapacity > MAX_ARRAY_SIZE 返回 int 最大值,否则返回 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;
}
好啦,目前为止是不是对ArrayList的认知又提升一步了呢?
留下一个小问题ArrayList 为什么要添加一个 modCount 参数记录每一次集合的修改呢? 小伙伴可以评论区回答讨论哦;