Collection之List
List基本知识
ArrayList
ArrayList内部通过数组实现,当数组容量不够时,需要将原来的元素复制到新的扩容数组中。当要在ArrayList中插入元素时,需要进行数组元素的移动和复制,代价较高。所以ArrayList适用于随机查找和遍历元素,不适合插入和删除元素。
扩容机制
ArrayList初始长度为0(未指定长度情况下,指定长度的话初始长度就是指定长度);
调用一次add之后长度变为10,之后空间不够就会按1.5被进行扩容(如果扩容之后仍不够,新长度就是传入集合大小)
底层代码
-
DEFAULT_CAPACITY:default_capcity,默认的容量大小,也就是当你第一次创建数组并往里面添加第一个元素时,数组的默认容量大小,默认值为10
-
DEFAULTCAPACITY_EMPTY_ELEMENTDATA:defaultcapacity_empty_elementdata是默认的空数组,他的作用是当elementData为{},即空数组时,把它赋值给elementData
-
elementData:表示的就是当前存储元素的数组
-
size:他表示当前还没有添加新元素前的数组中有效的元素个数,比如说数组长度为10,只保存了5个元素,那有效长度就是5
-
MAX_ARRAY_SIZE:最大数组长度,它用来标识当前数组可保存元素的最大长度,值为Integer_MAX_VALUE -8,即2147483647 - 8 ,这里的 8 代表8字节用来保存数组本身的内存大小。
总结:
- 调用add方法时,如果数组为空,则minCapacity为10;若不为空,则minCapacity为数组有效元素个数加一
- 然后和当前数组长度比较,若大于当前数组长度则进行扩容
- 新数组长度为原数组长度的1.5倍,如果新数组长度比最小容量小,则新数组长度就是最小容量
public boolean add(E e) {
//(确保内部变量)该方法用来判断当前数组的容量是否足够,不足就扩容
//若无参构造,则size==0
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
//判断当前存储元素的数组是否为null
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//DEFAULT_CAPACITY == 10
//minCapacity == 1
//当前存储元素的数组是否为null,取出size+1和DEFAULT_CAPACITY中大的那一个,则minCapacity == 10
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//该方法判断是否需要扩容
//minCapacity == 10
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
//判断最小容量是否比当前数组容量(没有添加元素前)大
//第一次add, minCapacity == 10 ,elementData.length == 0(无参构造创建数组中无元素),满足
//第二次add, minCapacity == 2(size+1) ,elementData.length == 10,不满足,不用扩容
if (minCapacity - elementData.length > 0)
//扩容
grow(minCapacity);
}
//minCapacity == 10
private void grow(int minCapacity) {
// overflow-conscious code
//旧数组容量为当前数组长度(未添加数据前)
//oldCapacity == 0
int oldCapacity = elementData.length;
//新数组容量为老数组容量的1.5倍
//newCapacity = 0
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果新数组容量小于最小数组容量,直接将新数组容量赋值为最小数组容量
//0 - 10 < 0
if (newCapacity - minCapacity < 0)
//newCapacity = 10
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);
}
private static int hugeCapacity(int minCapacity) {
//若最小容量小于0的情况,抛出异常
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
若最小容量>最大预设值,返回Integer.Max_VALUE,否则是MAX_ARRAY_SIZE(Integer.Max_VALUE-8)
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
Vector
Vector底层也是通过数组实现,不同的是它是线程安全的,某一时刻只允许一个线程写Vector,访问速度比ArrayList慢。
LinkedList
底层使用双向链表存储数据,动态插入删除较快,随机访问和遍历较慢,同时还提供了List接口中没有定义的方法用于操作表头和表尾的数据。
LinkedList实现了Deque接口,而Deque又继承了queue接口,故LinkedList也是queue(队列)的实现类,可实现先进先出的队列功能,当也可实现后进先出的栈功能。
双向链表
add方法
add方法调用linkLast方法
/**
* 将e元素添加到链表作为最后一个节点
*/
void linkLast(E e) {
//last指向最后一个节点,让l指向last,即让l指向最后一个节点
final Node<E> l = last;
//创建新节点,让prev->l(last),element=e,next->null
final Node<E> newNode = new Node<>(l, e, null);
//把last指针指向新节点
last = newNode;
//如果l为null,说明该linkedlist为空
if (l == null)
//把first指向新节点
first = newNode;
else
//否则让最后一个节点的next指向新节点
l.next = newNode;
//集合元素数量增加1
size++;
//实际修改次数增加1
modCount++;
}
总结LinkedList的add方法(尾插法)
- 创建新节点并将新节点的前驱设置为之前的尾节点,后驱设置为null
- last指向新节点
- 判断之前的尾节点是否为空(linkedlist是否为空)
- 若是则将first指向新节点
- 若不是则将之前尾节点的后驱指向新节点
remove方法
调用removeFirst方法
public E removeFirst() {
//first指向第一个节点,f指向first,即f指向第一个节点
final Node<E> f = first;
//f指向为null,说明linkedList为空,没有元素可以remove
if (f == null)
throw new NoSuchElementException();
//调用unlinkFirst方法
return unlinkFirst(f);
}
removeFirst方法调用unlinkFirst方法
/**
* 取消链接第一个节点
* 传入的f指向第一个节点
*/
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
//获取第一个节点元素
final E element = f.item;
//next指向第一个节点的下一个节点
final Node<E> next = f.next;
//清空第一个节点
f.item = null;
f.next = null; // help GC
//把first指向next指向的节点(即原第一个节点的下一个节点)
first = next;
//如果next为null说明原linkedList只有一个元素,获取第一个节点之后linkeList变为空
if (next == null)
last = null;
else
//把next指向节点的prev置为null
next.prev = null;
//集合元素数量减少1
size--;
//实际修改次数增加1
modCount++;
//返回节点元素值
return element;
}
总结LinkedList的remove方法(获取头节点)
- 获取头节点的数据
- next指向头节点的后驱节点
- 清空头节点(helpGC)
- first指向next代表的节点
- 判断next代表的节点是否为空
- 若为空,则linkedlist也为空,last设为null
- 若不为空,则next代表的节点的后驱设置为null
List相关面试题
1. 说说你自己对 ArrayList 的理解?
- ArrayList底层基于数组实现,实现了RandomAccess接口,因此随机查找和遍历速度快,当需要对ArrayList插入删除时,涉及到数组元素的移动和复制,代价大,速度慢。
- ArrayList使用无参构造方法创建时,默认长度为0,第一次调用add方法时,长度扩容为10,之后每次扩容都按照原来数组长度的1.5倍进行扩容,(如果扩容之后仍不够,新长度就是传入集合大小)
2. 怎么解决List 的线程安全问题?
- 使用Collections类中的synchronizedList解决,
- 采用 CopyOnWriteArrayList 并发 List(读写分离思想)来解决
转载:https://blog.csdn.net/zlfing/article/details/109231117