ArrayList源码详解
ArrayList作为开发最最常用的容器,了解并掌握其底层原理是每个开发人员必备的技能。
一、关键属性
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData;
了解了以上属性,我们就可以回答面试官最喜欢问的面试问题了:
ArrayList的底层数据结构是什么?
显然嘛,数组。
初始化容量大小?
很多人说是10,这其实是错误的,看过源码就会知道,初始化容量是0。在新增第一个元素的时候进行扩容,容量扩容为10。这也是这个属性DEFAULTCAPACITY_EMPTY_ELEMENTDATA 存在的意义。
二、容器初始化
ArrayList 底层数据结构是数组,这个想必大家都知道。 初始化方式:
List<Integer> list = new ArrayList<>();
1. 无参构造
new ArrayList<>(); 源码很简单。
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
2. 指定容量大小
也不是很复杂
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;
}
}
三、核心add方法
整体流程图:
add()有几个重载方法,这里只对核心的add(E e)方法进行详细的介绍。 源码很简单三步:
- 确保容量
- 添加值
- 返回true
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
1. ensureCapacityInternal() 方法
作用:确保容量满足要求,不满足扩容
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
2. grow(int minCapacity) 扩容方法
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);
}
添加元素的流程结束了,是不是很简单。
四、核心get方法
get方法很简单,不用看源码应该就可以猜出来。get(下标) 不就是 elementData[下标] 嘛
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
五、核心remove 方法
remove 方法有不少重载方法。常用的是根据下标移除和根据元素移除。根据元素溢出,本质上还是根据下标移除,需要先定位到下标,再根据下标移除。这里只针对根据下标移除进行详细介绍。
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;
}
System.arraycopy()方法
此方法常用于数组copy,数组扩容,数组根据指定下标删除。
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
六、 contains() 方法
判断是否包含指定元素,主要是遍历数组,对一个元素一个元素进行判断。
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
七、总结
ArrayList 容器算是比较简单的容器,理解起来不是很困难,很适合源码阅读入门。
|