本文只要是简单介绍一下
ArrayList以及LinkedList。
ArrayList
ArrayList 的底层是数组。与 Java 中的数组相比,它的容量能动态增长。在添加大量元素前,应用程序可以使用 ensureCapacity 操作来增加 ArrayList 实例的容量,这可以减少递增式再分配的数量。
ArrayList 继承于 AbstractList ,实现了 List, RandomAccess, Cloneable, java.io.Serializable 这些接口。
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
}
数据结构
我们首先看一下 ArrayList 的数据结构。
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneabl{
...
transient Object[] elementData;
...
}
从代码中可以看出数据都存储在 elementData 这个数组中,所以后面的初始化以及增删改查都会围绕着这个数组展开。
初始化
我们看一下 ArrayList 无参构造函数。
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneabl{
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
...
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
...
}
通过代码我们可以看到无参构造函数中直接将空数组(DEFAULTCAPACITY_EMPTY_ELEMENTDATA)赋值给 elementData,也就是说这时 ArrayList 大小是 0。
查
ArrayList 的查询其实很简单,根据 index 直接从 elementData 里面查找。代码如下:
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneabl{
public E get(int index) {
// 校验index是否合法
rangeCheck(index);
return elementData(index);
}
E elementData(int index) {
return (E) elementData[index];
}
}
改
ArrayList 的更改操作最主要的是 elementData[index] = element;,代码如下:
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneabl{
public E set(int index, E element) {
rangeCheck(index);
// 获取历史数据
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
E elementData(int index) {
return (E) elementData[index];
}
}
增
我们首先来看一下增加数据的代码:
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneabl{
private static final int DEFAULT_CAPACITY = 10;
public boolean add(E e) {
// 选择合适的容量
ensureCapacityInternal(size + 1); // Increments modCount!!
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;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 如果当前的容量小于最小容量则需要扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
// 扩容操作
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// 扩容是当前数组长度的 1.5 倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 数组拷贝
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
通过代码分析我们就很清楚的了解到 ArrayList 是怎样新增数据的。
- 判断是否需要扩容
- 当数组为空数组时,新增时会将容量调整到
DEFAULT_CAPACITY也就是 10。如果需要扩容,扩容后的容量是之前的 1.5 倍。 - 最后使用
elementData[size++] = e进行新增。
删
删除操作会判断删除的数是不是最后一个,如果不是则会将 index 后面的数据向前挪一格,让后让最后一个数据指向 null。
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneabl{
public E remove(int index) {
// 检验index是否合法
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
// 如果不是最后一个数则会将index 后面的数据向前移动一格
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
}
LinkedList
数据结构
LinkeList 最重要的数据结构就是 Node,它代表了链表的一个节点。从 Node 中可以看出这个链表是双向链表,它既有指向下一个节点的 next 指针,也有指向上一个节点的 prev 指针。
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;
}
}
我们再看一下从链表的结构中我们可以看出它既有指向首节点的 first 指针,也有指向尾结点的 last 指针,说明它可以从链尾向前遍历。
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
transient int size = 0;
/**
* Pointer to first node.
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
// 首节点
transient Node<E> first;
/**
* Pointer to last node.
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
// 尾结点
transient Node<E> last;
...
}
查
在查询的时候实现了一个小优化,如果要查询的 index 小于当前链表 size 的一半,就会从前往后查,否则就会从后向前查,这样来提高查询效率。
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
Node<E> node(int index) {
// 如果index小于size的一半,就会从前往后查,否则就会从后向前查
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;
}
}
}
改
修改操作比较简单,首先查找到 index 节点然后更新这个节点的值。
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
}
增
新增操作主要分为三种,分别是从头结点向前插入,从尾结点向后插入,从指定的节点向后插入。从头结点向前插入可以在实现栈时使用,而从尾结点向后插入可以在实现队列时使用。
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
/**
* 从头结点向前插入
*/
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++;
}
/**
* 从尾结点向后插入
*/
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++;
}
/**
* 从指定的节点向后插入
*/
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.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
}
删
跟新增对应的,删除也有三种方式,这里就不再赘述了。
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
/**
* 删除第一个节点
*/
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
/**
* 删除最后一个节点
*/
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
final E element = l.item;
final Node<E> prev = l.prev;
l.item = null;
l.prev = null; // help GC
last = prev;
if (prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}
/**
* 删除指定节点
*/
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;
}
}