我们为什么需要ArrayList实现类?
ArrayList是List接口的实现类,其底层的关键点用的其实是《数组》。
ArrayList主要特点为:查询效率高,增删效率低,线程不安全。因为日常使用,都不涉及多线程,并且以查询为主,所以ArrayList在日常开发中使用最多。
下面通过源码,分析一下ArrayList类。
基本实现
上面说到ArrayList其实是靠数组实现的,那具体是怎么样实现呢?请看源码:
private static final int DEFAULT_CAPACITY = 10; //默认的数组大小为10,及容器可以存10个元素
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
private int size; // size用来存储容器现在已有的元素个数,注意,并不代表数组大小
transient Object[] elementData; //elementData,就是一个数组,后面的操作主要都是操作elementData
//不带参数构造器
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//带参数构造器
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);
}
}
通过构造器,可以看出底层就是操作一个Object类型的数组:elementData ,默认初始化大小为10。
增、刪、改、查
容器嘛,用来存储数据的,那就离不开”增、刪、改、查“这里四个字。那就来逐一看ArrayList是如何实现实现的。
增
有两种新增方法,第一种为不带索引的,只传入元素值,则会加在最后一个,源码如下:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!! 保证没有超过数组界限,即时扩容
elementData[size++] = e; // 加上在数组最后一位,并且用于计数的变量size加1
return true;
}
另一种为在指定索引新增,就是先将指定索引及以后的元素后移,再为指定索引的数组元素赋值:
public void add(int index, E element) {
rangeCheckForAdd(index); // 检查传入索引数值的有效性,避免索引为负数等异常情况
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index); //使用arraycopy,进行后移
elementData[index] = element; // 在指定索引位置赋值
size++; //用于计数的变量size加1
}
删
删也有两种方法,第一种为传入具体索引的:
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; // clear to let GC do its work
//用于计数的变量size减1,并且数组最后一个元素置为null
return oldValue;
}
另一种为删除指定元素内容,其实就是先找到元素内容匹配的索引,然后根据索引来删除:
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) { //找到元素内容为null的索引
fastRemove(index); //调用根据索引删除的方法
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) { //找到与元素内容匹配的索引
fastRemove(index); //调用根据索引删除的方法
return true;
}
}
return false;
}
//fastRemove与remove一样,无返回值,private的,外界调用不到
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
}
改
修改ArrayList,其实就是修改数组里指定索引的元素:
public E set(int index, E element) {
rangeCheck(index); // 检查传入索引数值的有效性,避免索引为负数等异常情况
E oldValue = elementData(index);
elementData[index] = element; //修改指定位置的元素内容
return oldValue;
}
查
查询ArrayList,其实就是返回数组里指定索引的元素:
public E get(int index) {
rangeCheck(index);// 检查传入索引数值的有效性,避免索引为负数等异常情况
return elementData(index);
}
扩容
Java中的数组,一旦声明,长度是不可以改变的。为什么ArrayList可以无限增加呢?是怎样进行扩容的呢?请看源码:
//当调用add,新增时,检查到需要扩容时调用
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length; //获取目前的长度
int newCapacity = oldCapacity + (oldCapacity >> 1); //新长度为:目前 长度 + (目前长度/2),即扩容1.5倍
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:
elementData = Arrays.copyOf(elementData, newCapacity); //把旧数组里的值copy到新数组
}
扩容,其实就是新建了一个新数组,新数组声明的元素个数为旧数组的1.5被,然后将旧数组的内容copy到新数组里,再把新数组赋值给elementData。旧数组没用了,就等着被JVM回收咯。
toString()
在父类:AbstractCollection中,重写了toString()方法:
public String toString() {
Iterator<E> it = iterator();
if (! it.hasNext()) // 为空
return "[]";
StringBuilder sb = new StringBuilder();
sb.append('[');
for (;;) {
E e = it.next();
sb.append(e == this ? "(this Collection)" : e);
if (! it.hasNext())
return sb.append(']').toString();
sb.append(',').append(' ');
}
}
是通过使用迭代器遍历,迭代器的next方法重写如下:
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i]; //读取elementData数组
}
简单来说,就是通过StringBuilder,拼接数组遍历出来的元素实现的,如下:
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("[");
for(int i=0;i<size;i++){
sb.append(elementData[i]+",");
}
sb.setCharAt(sb.length()-1, ']');
return sb.toString();
}