您的位置:首页 > 编程语言 > Java开发

深入学习Java之ArrayList

2017-07-26 21:27 197 查看

深入学习Java之ArrayList

前言

对于Java的容器类,虽然之前一直有在使用,不过使用归使用,很多的原理本质上还是不懂,比如说什么时候进行扩容等,乘着最近有空,每天抽出点时间来重新学习下容器类,主要包括了(Collection、Map),其中Collection主要包括List、Set、Queue等,本小节主要来学习List中的ArrayList

ArrayList的继承结构

首先从宏观上来了解ArrayList的结构,然后再从源码的角度来学习。

ArrayList的继承结构如下所示


从上图中可以看到,ArrayList继承自AbstractList,并且实现了RandomAccess、Clonable、Serializable接口,而其中的AbstractList又实现了AbstractCollection接口,并且继承了List接口,AbstractCollection实现了Collection接口,List继承了Collection接口,Collection接口又继承了Iterable接口。

接下来我们根据上图,从上到下来逐个类、接口来学习

首先是Iterable接口



从上图中可以看到,Iterable接口中主要提供了iterator方法,用于返回一个Iterator对象,主要用于遍历容器(foreach)

接下来是Collection接口



从上图中可以看到,Collection接口基本上定义了操作容器的基本方法,包括了获取容器大小的size方法,判断容器是否为空的isEmpty方法,判断容器是否包含某个元素的contains方法,将容器的内容装换为数组的toArray方法,添加元素的add方法,移除元素的remove方法等,这些方法都是操作容器的最基本的方法,在对应的实现类中,不同的容器实现类将根据自身的特性,对不同的方法进行相应的实现。

接下来是AbstractCollection接口,AbstractCollection是一个抽象类,主要实现了Collection接口中的部分通用方法,具体如下代码所示

public boolean contains(Object o) {
// 获得迭代器对象
Iterator<E> it = iterator();
// 判断对象是否是NULL,如果是NUll,则查看容器中是否
// 有对象也是NULL
if (o==null) {
while (it.hasNext())
if (it.next()==null)
return true;
} else {
// 如果不是NULL,则查看容器中是否包含该方法
while (it.hasNext())
if (o.equals(it.next()))
return true;
}
return false;
}


// 原理同上
public boolean remove(Object o) {
Iterator<E> it = iterator();
if (o==null) {
while (it.hasNext()) {
if (it.next()==null) {
it.remove();
return true;
}
}
} else {
while (it.hasNext()) {
if (o.equals(it.next())) {
it.remove();
return true;
}
}
}
return false;
}


// 将容器转化为数组
public Object[] toArray() {
// 默认大小为容器中元素的个数
Object[] r = new Object[size()];
Iterator<E> it = iterator();
for (int i = 0; i < r.length; i++) {
// 当发现容器中的元素个数没有那么多时,将数组进行缩减
if (! it.hasNext()) // fewer elements than expected
return Arrays.copyOf(r, i);
r[i] = it.next();
}
// 当发现容器中个数多余size时,进行扩容,并将剩余元素添加到数组中
return it.hasNext() ? finishToArray(r, it) : r;
}

// 将容器中剩余的元素加入到数组中
private static <T> T[] finishToArray(T[] r, Iterator<?> it) {
int i = r.length;
while (it.hasNext()) {
int cap = r.length;
if (i == cap) {
// 当发现数组容量不足时,进行扩容,扩容后大小为原来的大小+原来大小的1/2 + 1
int newCap = cap + (cap >> 1) + 1;
// 对扩容后的大小进行判断,防止容量过大
// 其中的 MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
// 也就是如果扩容后的大小大于Integer.MAX_VALUE - 8,则进行
// 容量判断,防止其整数溢出
if (newCap - MAX_ARRAY_SIZE > 0)
newCap = hugeCapacity(cap + 1);
r = Arrays.copyOf(r, newCap);
}
r[i++] = (T)it.next();
}
// 如果数组容量由空余,则进行缩减至数组容量大小
return (i == r.length) ? r : Arrays.copyOf(r, i);
}
// 限制数组容量
private static int hugeCapacity(int minCapacity) {
// 如果新容量太大,造成整数溢出,则抛出异常
if (minCapacity < 0) // overflow
throw new OutOfMemoryError
("Required array size too large");
// 如果大于允许的最大容量,则将其限制在Integer.MAX_VALUE
// 否则则将其限制在MAX_ARRAY_SIZE
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}


当然,AbstractCollection中还有其他一些方法,不过,由于在具体的实现类中,可能对应的实现不同,所以这里就不进行其源码的研究,等到具体的实现类中再进行研究

接下来是List接口

由于List接口继承自Collection接口,所以Collection中有的方法List也自然有,不过,除此之外,List还增加了几个新的方法,主要用于针对List的操作


从上图中可以看到,主要是获取指定位置的get方法,将某个位置的元素设置为新的值的set方法,获取某个元素在List中的索引的indexOf、LastIndexOf方法,以及List增加的用于获取新的迭代器的ListIterator方法

接下来是AbstractList,正如AbstractCollection之于Collection,AbstractList是List的实现,主要实现了几个常用的方法,这里同样不进行展开

从ArrayList的结构图中可以看到,ArrayList除了AbstractList和实现了List接口之外,还实现了RandomAccess、Cloneable、Serializable接口,其中RandomAccess、Cloneable和Serializable接口均是标记接口,RandomAccess用于标记ArrayList支持随机访问,Cloneable接口用于标记ArrayList支持克隆,Serializable接口用于标记ArrayList支持序列化

ArrayList的源码剖析

上面从宏观上了解了ArrayList的继承结构,总体把握了ArrayList的方法之后,接下来我们来详细地学习ArrayList的源码

ArrayList的组成

transient Object[] elementData;


可以看到,ArrayList的底层实现其实就是一个Object数组,这也是为什么ArrayList称之为ArrayList的原因了

接下来来学习ArrayList的构造方法

// 带初始容量的初始化方法
public ArrayList(int initialCapacity) {
// 如果初始容量大于0,则使用该容量进行创建数组
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
// 如果容量是0,则使用默认的的空数组对象,其中的EMPTY_ELEMENTDATA定义如下
// private static final Object[] EMPTY_ELEMENTDATA = {};
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
// 如果容量小于0,则抛出异常
} else {
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
}
}
// 无参构造方法,也是默认的构造方法
public ArrayList() {
// 默认也是初始化为空数组
// private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

// 带一个容器对象的构造方法,主要用于将容器中的所有对象来初始化ArrayList对象
public ArrayList(Collection<? extends E> c) {
// 将Collection转化为数组对象
elementData = c.toArray();
// 如果传进来的Collection中有元素,则将元素添加到数组中
if ((size = elementData.length) != 0) {
// 如果转化后的数组不是Object类型,则初始化为实际类型
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
// 否则将其初始化为空容量
} else {
this.elementData = EMPTY_ELEMENTDATA;
}
}


获取某个元素

public E get(int index) {
// 对index的范围进行检查
rangeCheck(index);

return elementData(index);
}

// 对index的范围进行检查,由于对数组进行访问时,负数索引会抛出异常
// 所以这里不需要进行额外检查,但是由于此时数组的容量并不代表所有的
// 位置都是有效元素,所以需要进行检查
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}


将指定位置的元素替换为指定元素

public E set(int index, E element) {
rangeCheck(index);
// 替换旧值并且返回旧值
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}


添加某个元素

// 默认添加在最后面
public boolean add(E e) {
// 进行容量检测
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
// 容量检测
private void ensureCapacityInternal(int minCapacity) {
// 如果是此时的数组的默认的空数组,则将其容量初始化为
// max(默认容量(10), 此时所需要的最小容量)
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
// 判断此时容量是否足够
ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) {
// 记录对ArrayList结构进行操作
modCount++;

// 如果所需要的容量大于数组此时的长度,则增长数组
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}

// 动态增长,注意这里比较耗费资源
private void grow(int minCapacity) {

int oldCapacity = elementData.length;
// 大小为旧容量大小+ 1/2 旧容量大小
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);
}

// 检查容量大小是否溢出
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}


在指定位置插入元素

public void add(int index, E element) {
rangeCheckForAdd(index);

// 同上,进行容量检测并在需要时进行动态增长
ensureCapacityInternal(size + 1);
// 将要插入的位置的元素后面所有元素后移
// 这里可以看出,ArrayList在中间插入元素是比较消耗资源的
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}


添加一个容器中的所有元素

// 基本原理同上,这里不进行过多的解析
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew);  // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}


移除指定位置元素

public E remove(int index) {
rangeCheck(index);

modCount++;
E oldValue = elementData(index);

int numMoved = size - index - 1;
// 如果要移出的元素不是最后一个元素,则进行紧凑
// 从这里也可以看到,ArrayList在中间移除元素是比较消耗资源的
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work

return oldValue;
}


在指定位置添加所有的元素

// 基本原理同上,不做过多解析
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);

Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew);  // Increments modCount

int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);

System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}


移除指定值的元素

// 移除第一个匹配的值
public boolean remove(Object o) {
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
}


移除所有元素

public void clear() {
modCount++;

// 这里需要注意,由于数组中的元素是通过引用关系跟
// 具体的对象关联的,所有如果此时只是简单的移动索引
// 那么这些元素所占用的内存依旧不会被GC,因为依旧保持
// 跟数组的管理,GC回收是不会对这种对象进行回收的
// 这里写得非常棒,非常值得学习
for (int i = 0; i < size; i++)
elementData[i] = null;

size = 0;
}


查找某个元素的索引以及查找从后往前找某个元素的索引基本同AbstractCollection,这里就不进行展开了

克隆数组

public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}


手动触发容量检测

public void ensureCapacity(int minCapacity) {
// 如果是默认的空数组,则容量为0,否则容量为10
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) ? 0 : DEFAULT_CAPACITY;
// 对最小容量与需要的容量进行判断,并根据需要进行容量扩展
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}


手动将ArrayList中数组的大小进行确定,限制为实际的大小

public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size);
}
}


对与ArrayList中的Iterator以及ListIterator的源码,目前还没看懂,所以暂时无法进行研究,等以后研究懂了再进行补充

总结

本小节主要从宏观上学习了ArrayList的结构,以及从ArrayList的源码中研究了ArrayList的底层实现,以及ArrayList的扩容,紧凑原理(数组的拷贝,移动),从本质上明白了为何ArrayList在中间进行插入,删除会带来严重性能的原因(数组元素的移动),加深了对ArrayList的认识。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息