前言:本文结合源码整体观察List集合的实现方式,学习交流使用,理解有误希望指正
List整体优缺点
ArrayList
优点: 底层数据结构是数组,查询快,增删慢。
缺点: 线程不安全,效率高
Vector
优点: 底层数据结构是数组,查询快,增删慢。
缺点: 线程安全,效率低。
LinkedList
优点: 底层数据结构是链表,查询慢,增删快。
缺点: 线程不安全,效率高
ArrayList
ArrayList在方法内部作为局部变量使用时,方法使用时在线程私有的虚拟机栈中创建栈帧,ArrayList就存储在栈帧的局部变量表中。方法执行完毕栈帧弹出,资源释放,因此多线程访问时ArrayList也安全。
ArrayList的扩容
ArrayList的扩容并没有这么难,由于ArrayList有数组实现。ArrayList扩容的实质就是创建新的数组将原数组元素进行复制。关键点就是判断是否需扩容(原数组长度不够),通过一定规则确定扩容的大小。
添加元素方法
//size为数组中已有元素的个数
public boolean add(E e) {
//调用扩容方法,进行判断是否进行扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
//添加集合
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;
}
ArrayList源码扩容片段
//minCapacity为添加元素之后所需要的的数组最小长度
private void ensureCapacityInternal(int minCapacity) {
//判断是否ArrayList中第一次添加元素
//是:将传入值与默认值长度进行比较,两者的最大值进行扩容。
//由此可见,ArrayList第一次添加需要扩容,添加长度最小值为10
//否:直接进行下一步(添加元素之后elementData数组重新指向新的数组,见下文)
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
//判断添加元素之后需要的长度,是否超出原数组长度
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//如果添加元素之后的长度大于数组长度,进行扩容
//如果添加之后长度小于数组长度,不需要扩容
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/**
* The maximum size of array to allocate.
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
//扩容函数,对ArrayList进行扩容
private void grow(int minCapacity) {
// overflow-conscious code
//原数组长度
int oldCapacity = elementData.length;
//新数组长度,为原数组长度的3/2.这里注意位运算的优先级小于+ - * /
//还需注意此时可能会发生溢出,两个正数相加结果为负数,下边有判断
int newCapacity = oldCapacity + (oldCapacity >> 1);
//取新长度与添加元素所需最小长度的最大值作为新长度的值
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//整形最大正数Integer.MAX_VALUE=2^31-1
//MAX_ARRAY_SIZE=2^31-1 -8
//如果扩容长度超过了此值,执行hugeCapacity调整容量
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
//通过返回的新扩容长度进行扩容,调用系统方法。
//实际就是新创建一个数组,把旧的复制进去。此时elementData对象改变(指向新定义的数组)。
elementData = Arrays.copyOf(elementData, newCapacity);
}
//解决溢出
private static int hugeCapacity(int minCapacity) {
//溢出判断,两正数相加结果为负数,抛出异常,
//此时的溢出判断判断的是扩容传入的参数是否溢出。
//由于minCapacity=size+添加长度,此时有可能传入数据为负数
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//长度不溢出的情况下且大于 MAX_ARRAY_SIZE返回Integer.MAX_VALUE
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
ArrayList总结:
1,添加元素首先会判断,是不是第一次添加,如果第一次添加,添加的最小值为10。
2,然后进行判断添加所需最小容量是否大于原本数组的长度(不区分第一次,或其他次都执行),如果添加长度大于原本长度则进行扩容,否则不扩容。
3,扩容的实质就是得到扩容的长度创建新的数组,将原来的复制进去。
4,其中包含位运算,判断是否溢出为重点
LinkedList
对于LinkedList底层由双向链表实现,并不是普通单向链表或者循环链表
节点数据类型:
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;
}
}
由此数据类型明显可以看到,节点中包含node类型的next和prev,prev就是此节点中的前驱节点,由此可见为双向链表。
再看添加节点方法
/**
* Links e as last element.
*/
void linkLast(E e) {
final Node<E> l = last;
//新建一个节点,传入前驱和值和后继
final Node<E> newNode = new Node<>(l, e, null);
//新的last
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
这是尾插发,同样有头插法。从尾插发中可以清晰的看到,将新的节点的next为null。因此不是循环链表。
对于链表,和普通的链表操作相同,没什么特殊的,链表的特点就是删除插入容易,但是查找慢需要遍历链表,采用双向链表占用空间大但是操作效率快。
Vector
这个实现也是通过数组实现的,观看源码会发现和ArrayList内部没什么区别同样的方式实现功能。之所以安全是由于内部的方法大部分为synchronized修饰,对方法加锁控制线程安全。