ArrayList 与 LinkedList
1.List
- List 集合:元素有序、且可重复
- List是Collection的子接口,ArrayList 与 LinkedList是 List 的实现类
2.ArrayList
2.1 ArrayList 特点
// Arraylist 声明
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
- 线程不安全
- 底层使用 动态Object 数组存储
- ArrayList 实现 RandomAccess 接口,支持快速随机访问:不需要遍历就可以通过下标(索引)直接访问到内存地址。
- ArrayList 实现了 Cloneable 接口,重写了 Object 类的 clone() 方法。表明 ArrayList 是支持拷贝的。
- ArrayList 实现了 Serializable 接口,支持序列化
- 查询、更改速度快,增加、删除速度慢 (查改直接可以根据地址进行访问,增删会涉及到大量数据的移动)
2.2 ArrayList源码分析(JDK7)
- 构造器
//提供了3个构造器
1.public ArrayList() //构造一个默认初始容量为 10 的空列表
2.public ArrayList(int initialCapacity) //构造一个指定初始容量的空列表
3.public ArrayList(Collection<? extends E> c)//构造一个包含指定 collection 的元素的列表
- 数组扩容 (判断→扩容1.5倍→copy)
(1) 向数组中添加元素时,先检查当前元素的个数+1 是否会超出当前数组的长度
public boolean add(E e)
{
ensureCapacityInternal(size + 1);
// 此处的size是已经存在的元素的长度
// 因为add() 只能添加一个元素,所以使用size+1 判断当前容量是否够用
elementData[size++] = e;//将要添加的元素e放置在当前数组末尾
return true;
}
(2) 若当前元素的个数+1 会超出当前数组的长度则进行扩容
private void ensureCapacityInternal(int minCapacity)
{
modCount++;
// elementData.length是 底层数组的长度
if (minCapacity - elementData.length > 0) //若差值大于0 则表明底层数组长度不够,需要扩容
grow(minCapacity); //扩容函数
}
(3) 判断扩容1.5倍后是否满足要求。
若满足扩容要求则将旧数组copy到扩容后大小的新数组中
若不满足扩容要求则直接将新容量设置为要扩容的大小,其他情况再做相应的处理。最后进行copy
private void grow(int minCapacity)
{
int oldCapacity = elementData.length; // elementData.length 是当前底层数组个数
// >>表示右移运算符(实际相当于÷2),则新的容量是旧容量的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//单纯的add()函数并不会触发以下两个if,但是其他需要调用扩容方法的函数可能触发以下两个if
if (newCapacity - minCapacity < 0) //若扩容1.5倍后还是不够则直接将新容量设置为minCapacity
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)//若扩容1.5被后大于MAX_ARRAY_SIZE,就再进行其他处理
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);//将旧数组中的内容copy到新数组中
}
private static int hugeCapacity(int minCapacity)
{
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
注:为减少数组扩容次数,提升性能。当我们可预知要保存的元素的数量时,创建对象时就指定其容量
2.3 ArrayList源码分析(JDK8)
- JDK8与JDK7不同的是:JDK8在创建空参ArrayList时不会预先创建大小未10 的object 数组 ( 而是创建空参数组 )。新增元素时才初始化object数组大小为10,其余操作与JDK7一致
private static final int DEFAULT_CAPACITY = 10;
DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public ArrayList()
{
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
public boolean add(E e)
{
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity)
{
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity)
{
//如果数组元素等于空,则返回元素数组的默认初始化大小(即10)
//否则返回传入的数组大小
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
{
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
3 LinkedList
3.1 LinkedList特点
// LinkedList 声明
public class LinkedList<E> extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
- 底层使用双向链表存储
- LinkedList 是继承自 AbstractSequentialList 的双向链表,因此它也可以被当作堆栈、队列或双端队列进行操作
- LinkedList 实现了 Cloneable 接口,这表明 LinkedList 是支持拷贝的
- LinkedList 实现了 Serializable 接口,这表明 LinkedList 是支持序列化的
LinkedList 还实现了 Serializable 接口,这表明 LinkedList 是支持序列化的。
*查询、更改速度慢,增加、删除速度快 (查改某个节点需要先找到其前一个节点,增删只需要处理其相邻的节点)
3.2 LinkedList源码分析(JDK8)
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;
}
}
//在尾部插入一个节点: add
public boolean add(E e)
{
linkLast(e);
return true;
}
//生成新节点 并插入到 链表尾部, 更新 last/first 节点。
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++;//修改size
modCount++;//修改modCount
}
//get()方法
public E get(int index)
{
checkElementIndex(index);//判断是否越界 [0,size)
return node(index).item; //调用node()方法 取出 Node节点,
}
//根据index 查询出Node,
Node<E> node(int index)
{
//通过下标获取某个node 的时候,(增、查 ),会根据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;
}
}
4.总结
- ArrayList 底层是 动态object 数组,LinkedList底层是双向链表
- 对于随机访问( get( )、set( )方法 ) ArrayList要优于LinkedList。对于频繁增加、删除 LinkedList 优于ArrayList。
- ArrayList 数组扩容 (判断→扩容1.5倍→copy)
- 对于末尾添加元素,ArrayList与LinkedList 开销一致
- ArrayList 调用 get( )时直接返回index位置上的元素
LinkedList 调用 get( )时 需要通过for循环进行二分查找 - ArrayList 想要在指定位置插入或删除元素时,主要耗时的是System.arraycopy动作,会移动index后面所有的元素;LinkedList主耗时的是要先通过for循环找到index,然后直接插入或删除
- ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间
- ArrayList、LinkedList 都实现了 Cloneable、Serializable 接口
- ArrayList 继承于 AbstractList。LinkedList 继承于AbstractSequentialList
- ArrayList 实现 RandomAccess 接口,支持快速随机访问