一、再走读源码之前,先来复习一下简单的数据结构
1、数据结构:
- 线性结构:数据结构中的元素存在一对一的相互关系;
- 树形结构:数据结构中的元素存在一对多的相互关系;
- 图形结构:数据结构中的元素存在多对多的相互关系。
2、线性结构
线性结构有且仅有一个开始结点和一个结束结点,并且每个结点最多只有一个前驱和一个后继,常见的线性结构如下:
- 顺序表
- 链表(单向,双向,循环)
- 栈
以上3种数据的逻辑结构一致,指反映数据元素之间的逻辑关系的数据结构,其中的逻辑关系是指数据元素之间的前后间关系。
以上3种数据的物理结构(也叫存储结构)不一致,指数据的逻辑数据在计算机存储空间上的存放形式,具体实现的方法有顺序、链接、索引、散列等多种
二、先看ArrayList的几个成员变量
private static final int DEFAULT_CAPACITY = 10; // 集合默认长度
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData; // 用于存储元素的object数组
private int size; // 集合实际长度
可以看出,ArrayList底层使用的是object数组存储元素,也就是线性结构种的线性表,学过数据结构都知道,线性表可以根据下标直接访问元素,所以查询效率较高,但是再中间添加元素的时候,需要将index以后的所有元素后移一位,删除中间元素,需要将index以后的所有元素前移一位,所以增删效率较低。
三、ArrayList的构造函数
1、指定长度构造器
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);
}
}
当指定集合长度时,直接创建等长度的数组,为的是后续添加元素的时候减少数组的扩展和元素的复制。
2、无参构造器
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
空数组,长度为0
3、以一个集合作为参数的构造器
public ArrayList(Collection<? extends E> c) {
Object[] a = c.toArray();
if ((size = a.length) != 0) {
if (c.getClass() == ArrayList.class) {
elementData = a;
} else {
elementData = Arrays.copyOf(a, size, Object[].class);
}
} else {
// replace with empty array.
elementData = EMPTY_ELEMENTDATA;
}
}
先将入参集合转成object数组a,当数组a长度不为空时,先判断集合c是不是ArrayList类型,是的话就elementData直接指向c的堆地址了,不是的话就将a数组拷贝给elementData
四、ArrayList常用api
1、添加一个元素
这个方法看懂之后,后面的都是同样的逻辑了
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 确保数组长度够用,不够用就扩容
elementData[size++] = e; // 数组第size++位置赋值为e
return true;
}
private void ensureCapacityInternal(int minCapacity) { // minCapacity为添加元素之后的长度
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // 当集合为空数组时,返回默认值10和需要长度的 较大者
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0) // 当需要的长度,大于当前数组的长度时,扩容
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // 新数组长度为老数组的1.5倍
if (newCapacity - minCapacity < 0) // 扩充1.5倍还是不够,直接把需要的长度赋值给新数组长度
newCapacity = minCapacity;
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); // elementData指向copy之后的新数组
}
2、指定位置添加元素
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // 确保数组长度够用,不够用就扩容
System.arraycopy(elementData, index, elementData, index + 1, //将index以后的元素后移一位,空出来index位置
size - index);
elementData[index] = element; // index位置赋值element
size++;
}
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
// 它是一个静态本地方法,由虚拟机实现 它的含义是 从源数组src取元素,范围为下标srcPos到srcPos+length-1,取出共length个元素,存放到目标数组中,存放位置为下标destPos到destPos+length-1
3、addAll
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // 与上续add一样,确保数组长度够用,不够用就扩容(包括老数组元素的复制)
System.arraycopy(a, 0, elementData, size, numNew);// 将a数组复制到 elementData
size += numNew; // 数组长度增加
return numNew != 0;
}
4、移除元素remove
public E remove(int index) { // 按照坐标移除
rangeCheck(index); // 检查坐标是否越界
modCount++;
E oldValue = elementData(index); //根据坐标取出元素
int numMoved = size - index - 1; //上面讲过线性表删除元素,index以后的元素前移一位
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; //移动一位之后,最后一位置为null,然后长度减一 但是这时候数组elementData的长度是不变的,只不过是size减一了
return oldValue;
}
E elementData(int index) {
return (E) elementData[index];
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
public boolean remove(Object o) { // 按照元素删除,实现就是遍历elementData数组,找到相等的,就fastRemove删除
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
}
5、get/set
public E get(int index) {
rangeCheck(index); // 检查下标
return elementData(index); // 数组根据下标取元素
}
public E set(int index, E element) {
rangeCheck(index); // 检查下标
E oldValue = elementData(index // 数组根据下标取元素
elementData[index] = element; // 该下标设置新元素
return oldValue;
}
E elementData(int index) {
return (E) elementData[index];
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
6、indexOf/contains
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;
}
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
五、总结
本文只是列举了ArrayList一些常用的方法,更多的感兴趣的话可以自行阅读,感觉以前学数据结构的时候,总是知道有这么个东西,有这些特性。而ArrayList的实现方案其实就是线性表的一个很完美的实践。