## 前言

1. 介绍线性表的概念，详细介绍线性表中数组链表的数据结构。
2. 进行 ArrayList 的源码分析，比如存储结构、扩容机制、数据新增、数据获取等。
4. 进行 ArrayList 和 LinkedList 的总结。

## ArratList

### ArrayList 存储结构

``````/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access

/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;``````

### ArrayList 扩容机制

``````/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
*/
ensureCapacityInternal(size + 1);  // Increments modCount!!
elementData[size++] = e;
return true;
}
// 开始检查当前插入位置时数组容量是否足够
private void ensureCapacityInternal(int minCapacity) {
// ArrayList 是否未初始化，未初始化是则初始化 ArrayList ，容量给 10.
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
// 比较插入 index 是否大于当前数组长度，大于就 grow 进行扩容
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}

/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// 扩容规则是当前容量 + 当前容量右移1位。也就是1.5倍。
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 是否大于 Int 最大值，也就是容量最大值
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
// 拷贝元素到扩充后的新的 ArrayList
elementData = Arrays.copyOf(elementData, newCapacity);
}``````

1. 开始检查当前插入位置时数组容量是否足够
2. ArrayList 是否未初始化，未初始化是则初始化 ArrayList ，容量给 10.
3. 判断当前要插入的下标是否大于容量

1. 不大于，插入新增元素，新增流程完毕。
4. 如果所需的容量大于当前容量，开始扩充。

1. 扩容规则是当前容量 + 当前容量右移1位。也就是1.5倍。

`int newCapacity = oldCapacity + (oldCapacity >> 1);`

2. 如果扩充之后还是小于需要的最小容量，则把所需最小容量作为容量。
3. 如果容量大于默认最大容量，则使用 最大值 Integer 作为容量。
4. 拷贝老数组元素到扩充后的新数组
5. 插入新增元素，新增流程完毕。

### ArrayList 数据新增

``````public boolean add(E e) {
ensureCapacityInternal(size + 1);  // Increments modCount!!
elementData[size++] = e; // 直接赋值
return true;
}``````

``````/**
* Inserts the specified element at the specified position in this
* list. Shifts the element currently at that position (if any) and
* any subsequent elements to the right (adds one to their indices).
*
* @param index index at which the specified element is to be inserted
* @param element element to be inserted
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public void add(int index, E element) {
ensureCapacityInternal(size + 1);  // Increments modCount!!
// 指定下标开始所有元素后移一位
System.arraycopy(elementData, index, elementData, index + 1,size - index);
elementData[index] = element;
size++;
}``````

### ArrayList 数据获取

``````public E get(int index) {
rangeCheck(index);
return elementData(index);
}
E elementData(int index) {
return (E) elementData[index];
}``````

``````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;

/**
* Constructs an empty list.
*/
}``````

``````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;
}
}``````

``````public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
/**
* Returns the (non-null) Node at the specified element index.
*/
// 遍历查找 index 位置的节点信息
Node<E> node(int index) {
// assert isElementIndex(index);
// 这里判断 index 是在当前链表的前半部分还是后半部分，然后决定是从
// first 向后查找还是从 last 向前查找。
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;
}
}``````

1. 尾部插入

``````public boolean add(E e) {
return true;
}
final Node<E> l = last;
// 新节点，prev 为当前尾部节点，e为元素值，next 为 null，
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
// 目前的尾部节点 next 指向新的节点
l.next = newNode;
size++;
modCount++;
}``````

默认的 add 方式就是尾部新增了，尾部新增的逻辑很简单，只需要创建一个新的节点，新节点的 prev 设置现有的末尾节点，现有的末尾 Node 指向新节点 Node，新节点的 next 设为 null 即可。

2. 中间新增

下面是在指定位置新增元素，涉及到的源码部分。

``````public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
// 如果位置就是当前链表尾部，直接尾插
else
// 获取 index 位置的节点，插入新的元素
}

/**
* Inserts element e before non-null Node succ.
*/
// 在指定节点处新增元素，修改指定元素的下一个节点为新增元素，新增元素的下一个节点是查找到得 node 的next节点指向，
// 新增元素的上一个节点为查找到的 node 节点，查找到的 node 节点的 next 指向 node 的 prev 修改为新 Node
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++;
}
``````

第二个部分是在查找到得 node 对象后插入元素。主要就是修改 node 的 next 指向为新节点，新节点的 prev 指向为查找到的 node 节点，新节点的 next 指向为查找到的 node 节点的 next 指向。查找到的 node 节点的 next 指向的 node 节点的 prev 修改为新节点。

``````public E remove(int index) {
checkElementIndex(index);
}
/**
*/
// 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;
}``````

node(index) 方法依旧是二分查找目标位置，然后进行删除操作。比如要删除的节点叫做 X，删除操作主要是修改 X 节点的 prev 节点的 next 指向为 X 节点的 next 指向，修改 X 节点的 next 节点的 prev 指向为 X 节点的 prev 指向，最后把 X 节点的 prev 和 next 指向清空。如果理解起来有点费劲，可以看下面这个图，可能会比较明白。

### 扩展

``````public class LinkedList<E> extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable``````

``````Queue<String> queue = new LinkedList<>();
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.poll());
// result：
// a
// b
// c
// d``````

## 总结

1. 遍历，ArrayList 每次都是直接定位，LinkedList 通过 next 节点定位，不相上下。这里要注意的是 LinkedList 只有使用迭代器的方式遍历才会使用 next 节点。如果使用 `get` ，则因为遍历查找效率低下。
3. 删除，同 2.

<完>

Hello world : ) 我是阿朗，一线技术工具人，认认真真写文章。

Hello world :)

362 声望
1.8k 粉丝
0 条评论