集合-ArrayList和LinkedList常见源码及异同

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

集合-ArrayList和LinkedList常见源码及异同
1. ArrayList
1.1.继承关系
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable,
java.io.Serializable
1.2.内部结构和初始化
底层是可变数组
/**
* Default initial capacity.
* 初始化容量,这个没发现在初始化的时候使用,在扩容的时候有使用
*/
private static final int DEFAULT_CAPACITY = 10;
transient Object[] elementData;
transient关键字修饰表示不会被序列化
transient 的作用是说不希望 elementData 数组被序列化,重写了 writeObject 实现
private void writeObject(java.io.ObjectOutputStream s) throws
java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out array length
s.writeInt(elementData.length);
// Write out all elements in the proper order.
for (int i=0; i<size; i++)
s.writeObject(elementData[i]);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
每次序列化时,先调用 defaultWriteObject() 方法序列化 ArrayList 中的非 transient 元素,
然后遍历 elementData,只序列化已存入的元素,这样既加快了序列化的速度,又减小了序列化之后的文件大小。

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);
}
}
传参的构造方法,构造对应的长度的数组,如果小于0,则抛出错误。

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* Constructs an empty list with an initial capacity of ten. * 构造一个初始容量为10的空列表。

*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
这里无参构造器,初始化空的Object数组。

内部结构就是一个Object的数组
1.3.扩容
在添加元素时会涉及到扩容,
添加元素时会进行 1.增加数组长度,2.将元素添加到数组中
Java
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!! 1 增加长度
elementData[size++] = e; // 添加元素到数组
return true;
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity);
//DEFAULT_CAPACITY=10,如果是空数组,就返回10
}
return minCapacity;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++; //modCount默认为0
// overflow-conscious code
if (minCapacity - elementData.length > 0) //如果需要的长度
大于原来数组长度需要扩容
grow(minCapacity);
}
扩容方法
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); //扩容1.5倍
if (newCapacity - minCapacity < 0)
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);//进行复制
}
1.4.小结
1.ArrayList 可以存放null;
2.ArrayList线程不安全;
3.ArrayList内部实际上是一个Object的数组;
4.ArrayList每次扩容是1.5倍;
5.ArrayList本质是一个数组所以查询很快,新增和删除要慢些;
6.ArrayList底层数组存/取元素效率非常的高(get/set),查找,时间复杂度是O(1),插入和删除元素效率似乎不太高,时间复杂度为O(n)。

2.LinkedList
继承关系
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable 2.1.内部结构和初始化
底层是双向链表,
双向链表:是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。

链表的尾部元素的后一个节点是链表的头节点;而链表的头结点前一个节点则是则是链表的尾节点
transient int size = 0; //集合的长度
transient Node<E> first;//双向链表头部节点
transient Node<E> last;//双向链表尾部节点
无参构造方法
public LinkedList() {
}
传入一个list的构造方法
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
addAll的源码解析
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index);//越界检查
Object[] a = c.toArray();//集合转数组
int numNew = a.length;
if (numNew == 0)
return false;
Node<E> pred, succ;//尾插,得到插入位置的前继节点和后继节点 if (index == size) {//初始化尾插,就直接index=size
//从尾部添加的情况:前继节点是原来的last节点;后继节点是null
succ = null;
pred = last;
} else {
// 从指定位置(非尾部)添加的情况:前继节点就是index位置的节点,后继节点是index位置的节点的前一个节点
succ = node(index);
pred = succ.prev;
}
//循环插入
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null)
//空链插入情况
first = newNode;
else //不为空,将pred下一个指针指向当前的元素
pred.next = newNode;
pred = newNode;//最后将newNode赋值给pred,给下一次循环使用,目的是后移,(更新前置节点为最新插入的节点(的地址))
}
if (succ == null) {
//如果是从尾部开始插入的,则把last置为最后一个插入的元素 last = pred; //循环完后如果头元素为空,则将最后一个元素赋值为头,形成尾部跟头部的关联关系
} else {
// 如果不是从尾部插入的,则把尾部的数据和之前的节点连起来 pred.next = succ;
succ.prev = pred;
}
size += numNew;
modCount++;
return true;
}
双向链表是内部类定义了一个Node类的实体实现的
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;
}
}
LinkedList为双向链表结构,每个节点存放一个Node。

其中Node有三个属性:
item:实际存放的元素
next:下一个节点的引用,指向下一个节点
prev:前一个节点的引用,指向上一个节点
这样就构成了一个链表结构。

2.2.添加方法
LinkedList即实现了List接口,又实现了Deque接口,
所以LinkedList既可以添加将元素添加到尾部,也可以将元素添加到指定索引位置,还可以添加添加整个集合;
另外既可以在头部添加,又可以在尾部添加。

//将元素添加到尾部
public boolean add(E e) {
linkLast(e);
return true;
}
/**
* Links e as last element.
*/
void linkLast(E e) {
final Node<E> l = last;// 获取尾部元素
final Node<E> newNode = new Node<>(l, e, null);// 以尾部元素为前继节点创建一个新节点
last = newNode;// 更新尾部节点为需要插入的节点
if (l == null)
// 如果空链表的情况:同时更新first节点也为需要插入的节点。

(也就是说:该节点既是头节点first也是尾节点last)
first = newNode;
else
// 不是空链表的情况:将原来的尾部节点(现在是倒数第二个节点)的next指向需要插入的节点
l.next = newNode;
size++;// 更新链表大小和修改次数,插入完毕
modCount++;
}
private void linkFirst(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
在指定位置添加
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev; // 得到插入位置元素的前继节点
final Node<E> newNode = new Node<>(pred, e, succ);// 创建新节点,其前继节点是succ的前节点,后接点是succ节点
succ.prev = newNode;// 更新插入位置(succ)的前置节点为新节点 if (pred == null)
first = newNode;// 如果pred为null,说明该节点插入在头节点之前,要重置first头节点
else
pred.next = newNode;// 如果pred不为null,那么直接将pred的后继指针指向newNode即可
size++;
modCount++;
}
2.3.扩容
由于它的底层是用双向链表实现的,没有初始化大小,也没有扩容的机制。

2.4.小结
1.LinkedList 可以存放null;
2.LinkedList线程不安全;
3.LinkedList内部实际上是循环的双向链表;
4.LinkedList没有扩容机制也没有初始化;
5.LinkedList本质是双向链表,所以查询慢(需要遍历),新增和删除要快些;
6.LinkedList查找,时间复杂度是O(n),插入(尾插为为O(1),指定位置插为O(n)),删除时间复杂度为O(n)。

3.ArrayList和LinkedList比较
1.LinkedList和ArrayList的差别主要来自于Array和LinkedList数据结构的不同。

ArrayList是基于数组实现的,LinkedList是基于双链表实现的。

2.对于随机访问get和set,ArrayList优于LinkedList,因为ArrayList可以随机定位,而LinkedList要移动指针一步一步的移动到节点处。

4.使用场景
使用场景:
1.如果应用程序对数据有较多的随机访问,ArrayList对象要优于LinkedList对象;
2.如果应用程序有更多的插入或者删除操作,较少的数据读取,LinkedList对象要优于ArrayList对象;
3.注意ArrayList的插入,删除操作也不一定比LinkedList慢,如果在List靠近末尾的
地方插入,那么ArrayList只需要移动较少的数据,而LinkedList则需要一直查找到列
表尾部,
反而耗费较多时间,这时ArrayList就比LinkedList要快。

相关文档
最新文档