前言:Java程序员开发程序时,必定会使用JDK中提供的集合类来完成功能模块的开发,而JDK是Java规范的实现,不同厂商提供的JDK也多少会存在一些差异,那么,如何选用合适的集合类实现应用中的具体需求,是每个Java程序员在实际开发中必须解决的一个问题;解决这一问题就需要我们对JDK中集合类的相关实现有一个清晰的认识!
本文首先从全局角度对JDK中的集合包进行一个分析,接着对JDK中常用的集合类的关键实现进行源码分析和比较,最后对本文的内容进行归纳总结,希望能够能够帮助读者从整体与局部两方面认识和理解JDK中的集合以及对应的选择使用!
注意:上述UML图仅仅给出了本文主要讨论的集合的接口以及实现类的关系图,对于一些其他的类图关系并没有给出,目的在于将接口与实现类之间的关系清晰化;
Collection接口存放一个个的单个对象,Collection接口下有两个子接口:List接口以及Set接口;
Map接口存放Key-Value形式的键值对,Key不可重复,Value可重复;
首先,看一下List接口的实现类:ArrayList、LinkedList以及Vector;主要从底层存储结构、实例构造、添加元素、删除元素、查找元素以及遍历元素这些方面进行分析;
/**
* 默认初始化容量
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* ArrayList用于存放元素的数组
* ArrayList的容量为数组的长度
*/
transient Object[] elementData; // 非私有属性,便于内部类访问
/**
* ArrayList实例中包含的元素个数
*/
private int size;
/**
*共享的空数组对象,当含参构造器的初始化容量为0时,使用的底层数组
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 共享的空数组对象,用于无参构造函器使用,区别于EMPTY_ELEMENTDATA数组的是,
* 当第一次添加元素时,会初始化底层数组为长度为10的数组
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 根据指定参数实例化对象
* @param initialCapacity ArrayList实例的初始化容量
* @throws IllegalArgumentException 如果指定参数为负数,抛出异常
*/
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);
}
}
/**
* 构造一个空数组,当第一次添加元素时,会将底层数组替换为长度为10的数组
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
注意:该构造函数的设计是JDK1.8新添的懒初始化特性
protected transient int modCount = 0;
/**
* 向数组尾部添加指定元素
* @param e 待添加的元素
* @return true 添加成功返回true
*/
public boolean add(E e) {
//判断当前数组的长度是否足够容纳新元素,如果长度不够,需要进行扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
//插入元素
elementData[size++] = e;
return true;
}
/**
* 判断当前数组长度是否足够
* @param minCapacity
*/
private void ensureCapacityInternal(int minCapacity) {
//判断是否第一次添加,第一次添加元素需要初始化数组
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//再次判断数组长度是否足够
ensureExplicitCapacity(minCapacity);
}
//纯判断数组长度是否足够
private void ensureExplicitCapacity(int minCapacity) {
modCount++;//添加元素属于实例结构变更,需要记录
// 判断是否需要扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/**
* 数组可以分配的最大长度
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* 扩容以保证能够存放个数为minCapacity的元素
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// 扩容代码
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);
// 拷贝原有数组的元素到新数组中
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // 溢出
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
过程梳理:/**
* 删除第一个出现的与指定参数相等的元素(注意指定参数可以为null)
* @param o 待删除的元素(如果存在的话)
* @return true 如果存在指定元素,并删除成功返回true
*/
public boolean remove(Object o) {
//如果待删除元素为null,使用==判断是否相等
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
//找到元素位置,调用具体的删除方法
fastRemove(index);
return true;
}
} else {
//如果待删除元素不为null,使用equals判断是否相等
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; // 帮助GC回收待删除的元素
}
过程梳理:private class Itr implements Iterator<E>{
//省略具体实现
}
public Iterator<E> iterator() {
return listIterator();
}
元素的遍历可以通过调用iterator()方法返回的Iterator对象进行遍历,注意返回的Iterator对象是ArrayList内部实现的Itr内部类的对象;遍历的过程不难,需要注意的是,ArrayList对象的遍历是快速失败的,即在迭代的过程中,如果发现modCout与开始遍历之前的modCount值不同,将会停止遍历,抛出ConcurrentModificationException异常;ArrayList底层基于数组实现,采用1.5倍扩容机制,懒初始化以及非线程安全;
/**
* 指向第一个节点的指针,如果为null,则指向最后一个节点的指针也为null
*/
transient LinkedList.Node<E> first;
/**
* 指向最后一个节点的指针,如果为null,则指向第一个节点的指针也为null
*/
transient LinkedList.Node<E> last;
/**
* 静态内部类,存放元素的节点类
* @param
*/
private static class Node<E> {
E item;//链表中存放的元素
Node<E> next;//指向后继节点的指针
Node<E> prev;//指向上一节点的指针
//构造函数
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
/**
* 构造一个空链表
*/
public LinkedList() {
}
注意:LinkedList是基于链表结构实现的,且链表是双向链表;元素存放到LinkedList,需要先包装成一个节点,然后在插入链表;
//底层结构调整次数
protected transient int modCount = 0;
/**
* 添加指定元素到线性表尾部
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
//在尾部插入元素
linkLast(e);
return true;
}
/**
* 将待插入元素插入线性表尾部
*/
void linkLast(E e) {
final Node<E> l = last;
//以指定元素构造节点对象
final Node<E> newNode = new Node<>(l, e, null);
//将新节点作尾节点
last = newNode;
if (l == null)
first = newNode;//头结点
else
l.next = newNode;//连接到尾部结点
size++;
modCount++;
}
小结:链表实现的线性表的插入只需要对前后节点进行操作即可,不需要移动元素,故效率相比数组实现的线性表要高
/**
* 删除链表中第一个出现的与待删除元素相同的元素(如果存在的话)
* 同样需要对待删除元素是否为null进行分情况处理
* @param o 待删除元素
* @return {@code true} 如果包含待删除元素则返回true
*/
public boolean remove(Object o) {
//待删除元素为null
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
//实际的删除方法
unlink(x);
return true;
}
}
} else {
//待删除元素不为null
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
//实际的删除方法
unlink(x);
return true;
}
}
}
return false;
}
/**
* 删除指定元素所在的节点
*/
E unlink(Node<E> x) {
//先保存前后向指针以及节点中的元素
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
//处理前驱指针
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
//处理后继指针
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
//释放元素的引用,帮助GC
x.item = null;
size--;
modCount++;
return element;
}
小结:双向链表中元素的删除,需要先遍历链表找到待删除的节点,接着需要先处理前向指针,再处理后继指针;查找节点的过程是与数组链表相同的,但是删除操作却是更简单、高效的,因为不需要移动元素!
LinkedList是基于链表实现的,插入元素时,需要先将元素包装成一个节点对象,LinkedList是非线性安全的!
Vector与ArrayList类似,都是基于数组实现的,也是支持扩容的;下面给出两者之间不同的地方:
两者的初始容量都是10,但是Vector引进变量capacityIncrement,对扩容的方式进行控制;当capacityIncrement > 0时,扩容后的数组大小为原数组的大小加上capacityIncrement;当capacityIncrement <= 0时,扩容后的数组大小为原数组大小的两倍;可见,相比ArrayList,Vector的扩容机制更为可控!
Vector是线程安全(采用synchronized进行同步)的,ArrayList是非线程安全的
Stack是继承Vector类的,在Vector的基础上实现了Stack所要求的后进先出的操作,提供了push、pop以及peek等方法
注意:
Stack是基于Vector实现的,支持先进后出特性!
篇幅所限,本文先给出了集合包的整体框架,之后,又对其中的List家族进行了源码分析;在后续的文章中,将会对Set家族成员以及Map家族成员进行分析,如有描述不合适的地方还请指出,相互交流,共同成长!