最近又重新学习了一下集合框架,包括看书和看博客,故自己也作了一些总结,以自己的理解对集合做一些记录。
本次参考源码基于JDk1.8
一、基本层级
它继承自抽象类AbstractList,
实现了List接口,具备了一些集合的基本方法。
实现了RandomAccess接口 支持随机访问
实现了Serializable 接口, 支持序列化,
实现了Cloneable接口,可实现克隆。可看如下图
二、数据机构
ArrayList底层是基于数组实现的,但它是一个动态数组,意思是可自己动态扩容,
三、构造函数
ArrayList 总共有三个构造函数,
//1、构造一个空列表的初始容量10。
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
2、构造一个包含指定集合的元素的列表,将它copy到ArrayList。
* 此构造函数可延伸,可用于集合间的转换,如Set转换成List
*/
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;
}
}
//3、构造一个与指定初始容量的空列表。
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);
}
}
四、常用属性
/* 定义一个数组,使用transient修饰,该关键字声明数组默认不会被序列化。ArrayList
具有动态扩容特性,因此保存元素的数组不一定都会被使用,那么就没必要全部进行序列化。
ArrayList 重写了 writeObject() 和 readObject() 来控制只序列化数组中有元素填充
那部分内容。*/
transient Object[] elementData;
// size 标识它包含元素的个数
private int size;
五、常用方法
1) 添加方法 add(E e),看以下源码
public boolean add(E e) {
//ensureCapacityInternal 方法很关键,这个是加入元素之前,判断是否需要扩容,
ensureCapacityInternal(size + 1); // Increments modCount!!
// 将添加的元素 赋给elementData,然后size++
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0) // 如果添加的元素个数,大于数组的初始容量,则会使用grow() 方法扩容
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//设置新数组的容量扩展为原来数组的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 这个是再次判断新数组的容量够不够,如果不够的话,
//就把当前添加元素的长度,当作新数组的长度,
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//判断有没有超过最大值限制,
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
// 最后一步,将旧数组的元素,copy到新数组,
elementData = Arrays.copyOf(elementData, newCapacity);
}
因为ArrayList 要扩容,需要复制数组,所以代价很高,那怎么个代价高了呢?我们查看Arrays.copyOf()的源码可知,最终回到System 类下的一个native方法
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
它的功能是从指定源数组中复制一个数组,复制从指定的位置开始,到目标数组的指定位置结束。从src引用的源数组到dest引用的目标数组,数组组件的一个子序列被复制下来。被复制的组件的编号等于length参数。源数组中位置在srcPos到srcPos+length-1之间的组件被分别复制到目标数组中的destPos到destPos+length-1位置
2) 添加方法 add(int index, E element),与上个方法不同的是,这个是将元素插入到指定索引处,add(E e) 是直接在ArrayList 的末尾添加元素,
添加完元素,size+1,然后index 后的元素,需要整体往后移动一位,
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
//将elementData从index开始后面的元素往后移一位。
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
3) 删除 E remove(int index)
// 删除ArrayList指定位置的元素
public E remove(int index) {
// 范围检测,如果index比size 大,则会抛出
// new IndexOutOfBoundsException 异常
RangeCheck(index);
modCount++;
E oldValue = (E) elementData[index];
int numMoved = size - index - 1;
if (numMoved > 0)
// 把后面的元素 往前移
System.arraycopy(elementData, index + 1, elementData, index, numMoved);
//最后把最后一个元素置为 null
elementData[--size] = null; // Let gc do its work
return oldValue;
}
4) set(int index, E element)方法
将element元素 放到 指定index位置上,并返回 *被覆盖的元素*
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
5) 读取 get(int index)
指定具体的索引值读取,相当于数组中的查找,
// 返回此列表中指定位置上的元素
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
先列了一些常规用法的方法,API中还有很多其他的方法, 等后一步在慢慢完善!