这篇主要讲List集合的三个子类:
- ArrayList:底层数据结构是数组。线程不安全
- LinkedList:底层数据结构是链表。线程不安全
- Vector:底层数据结构是数组。线程安全
ArrayList解析
ArrayList底层其实就是一个数组,ArrayList中有扩容这么一个概念,正因为它扩容,所以它能够实现“动态”增长
Add方法
add(E e)步骤:
- 检查是否需要扩容
- 插入元素插入元素
扩容方法总结如下:
1.进行空间检查,决定是否进行扩容,以及确定最少需要的容量
2.如果确定扩容,就执行grow(int minCapacity),minCapacity为最少需要的容量
3.第一次扩容,逻辑为newCapacity = oldCapacity + (oldCapacity >> 1);即在原有的容量基础上增加一半。
4.第一次扩容后,如果容量还是小于minCapacity,就将容量扩充为minCapacity。
5.对扩容后的容量进行判断,如果大于允许的最大容量MAX_ARRAY_SIZE,则将容量再次调整为MAX_ARRAY_SIZE。至此扩容操作结束。
与扩容相关ArrayList的add方法底层其实都是arraycopy()来实现的,看到arraycopy(),我们可以发现:该方法是由C/C++来编写的,并不是由Java实现(native方法)
看到arraycopy(),我们可以发现:该方法是由C/C++来编写的,并不是由Java实现
add( int index, E element)步骤:
- 越界检查
- 空间检查,如果有需要进行扩容
- 插入元素
需要先对元素进行移动,然后完成插入操作,线性的时间复杂度,即O(n)。
Get方法
get(int index):
- 检查角标,是否越界
- 返回元素
Set方法
set( int index, E element):
- 检查角标,是否越界
- 替代元素
- 返回旧值
Remove方法
remove(int index):
- 检查角标,是否越界
- 删除元素
- 计算出需要移动的个数,并移动
- 设置为null,让Gc回收
重点
1.ArrayList是基于动态数组实现的,在增删时候,需要数组的拷贝复制。
2.ArrayList的默认初始化容量是10,每次扩容时候增加原先容量的一半,也就是变为原来的1.5倍
3.删除元素时不会减少容量,若希望减少容量则调用trimToSize()
4.它不是线程安全的。它能存放null值。
–
Vector与ArrayList区别
- Vector底层也是数组,与ArrayList最大的区别就是:同步(线程安全),使用synchronized实现同步
- 如果想要ArrayList实现同步,可以使用Collections的方法:List list = Collections.synchronizedList(new ArrayList(…));,就可以实现同步了~
- ArrayList在底层数组不够用时在原来的基础上扩展0.5倍,Vector是扩展1倍。
- 要被淘汰
–
LinkedList解析
LinkedList底层是双向链表,LinkedList实现了Deque接口,因此,我们可以操作LinkedList像操作队列和栈一样~
1.构造方法
LinkedList的构造方法有两个:
public LinkedList() {
}
public LinkedList(Collection<? extend E> c) {
this();
addAll(c);
}
2.add方法
add方法实际上就是往链表最后添加元素
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++;
}
3.remove方法
public boolean remove(Object o) {
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
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) {
// assert x != null;
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;
}
x.item = null;
size--;
modCount++;
return element;
}
4.get方法
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) { //如果下表小于长度的一半,那就从头遍历
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else { //否则从尾向前遍历
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
5.set方法
set方法和get方法其实差不多,根据下标来判断是从头遍历还是从尾遍历
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
总结
ArrayList:
底层实现是数组
ArrayList的默认初始化容量是10,每次扩容时候增加原先容量的一半,也就是变为原来的1.5倍
在增删时候,需要数组的拷贝复制(navite 方法由C/C++实现)
LinkedList:
底层实现是双向链表[双向链表方便实现往前遍历]
Vector:
底层是数组,现在已少用,被ArrayList替代,原因有两个:
Vector所有方法都是同步,有性能损失。
Vector初始length是10 超过length时 以100%比率增长,相比于ArrayList更多消耗内存
总的来说:查询多用ArrayList,增删多用LinkedList。
ArrayList增删慢不是绝对的(在数量大的情况下,已测试):
如果增加元素一直是使用add()(增加到末尾)的话,那是ArrayList要快
一直删除末尾的元素也是ArrayList要快【不用复制移动位置】 至于如果删除的是中间的位置的话,还是ArrayList要快!
但一般来说:增删多还是用LinkedList,因为上面的情况是极端的~