1.ArrayList底层原理:
原理:基于底数组实现的
2.ArrayList源码分析:
2.1 add方法(在尾部添加元素):
public boolean add(E e) {
//构造函数时候size已经初始化,size初始化=10,这里这个方法作用:确保elementData的容量可以放入新加的一个元素
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
//minCapacity==11,elemetDate==null
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//minCapacity==11,判断新添加一个元素数组的长度是否比在没有添加
//时候的长度的大小
if (minCapacity - elementData.length > 0)
//如果大于0,代表需要扩容了
grow(minCapacity);
}
//数组的扩容
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//例子:加入原先数组容量是10 现在是10 + 10/2 =15
int newCapacity = oldCapacity + (oldCapacity >> 1);
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);
}
2.2 add(int index,E e)在指定位置添加数据的过程:
//将元素插入到指定位置,其右边的数据右移1位
public void add(int index, E element) {
//检查索引,不能小于0,不能大于size
rangeCheckForAdd(index);
//确保容量
ensureCapacityInternal(size + 1);
//将index右边的数据右移
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//插入数据
elementData[index] = element;
//增加size
size++;
}
在指定位置添加元素的方法中分为5步:
- 检查索引,不能小于0,不能大于size
- 将容量+1,确保容量
- 将index右边的数据右移
- 在index位置插入数据
- 增加size
2.3 get()方法:
public E get(int index) {
//检查索引范围
rangeCheck(index);
//返回指定位置元素
return elementData(index);
}
3.LinkedList源码分析
3.1 基础变量:
LinkedList内部是一个双端链表结构,有两个变量,first指向链表头部,last指向链表尾部。
LinkedtList内部的成员变量如下:
transient int size = 0;
transient Node<E> first;//first指向链表头部
transient Node<E> last;//last指向链表尾部。
3.2添加操作:
add(E e)用于将元素添加到链表尾部,实现如下:
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
final Node<E> l = last;//指向链表的尾部
//以尾部为前驱节点创建一个新节点,新建节点的l
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;//将链表尾部指向新节点
if (l == null)//如果链表为空,那么该节点既是头节点也是尾节点
first = newNode;
else//链表不为空,那么将该结点作为原链表尾部的后继节点
l.next = newNode;
size++;//增加尺寸
modCount++;
}
从上面代码可以看到,linkLast方法中就是一个链表尾部添加一个双端节点的操作,但是需要注意对链表为空时头节点的处理。
add(int index,E e)
add(int index,E e)用于在指定位置添加元素。实现如下:
public void add(int index, E element) {
//检查索引是否处于[0-size]之间
checkPositionIndex(index);
if (index == size)//添加在链表尾部
linkLast(element);
else//添加在链表中间
linkBefore(element, node(index));
}
从上面代码可以看到,主要分为3步:
- 检查index的范围,否则抛出异常
- 如果插入位置是链表尾部,那么调用linkLast方法
- 如果插入位置是链表中间,那么调用linkBefore方法
linkLast方法前面已经讨论了,下面看一下linkBefore的实现。在看linkBefore之前,先看一下node(int index)方法,该方法返回指定位置的节点,实现如下:
Node<E> node(int index) {
// assert isElementIndex(index);
//如果索引位置靠链表前半部分,从头开始遍历
//size>>1 相当于size/2 整除2 计算链表
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;
}
}
添加操作总结
LinkedList由于实现了List和Deque接口,所以有多种添加方法,下面总结了一下。
- 将数据插入到链表尾部
- boolean add(E e):
- void addLast(E e)
- boolean offerLast(E e)
- 将数据插入到链表头部
- void addFirst(E e)
- boolean offerFirst(E e)
- 将数据插入到指定索引位置
- boolean add(int index,E e)
3.3 查找元素:get(E e)
public E get(int index) {
//检查边界
checkElementIndex(index);
return node(index).item;
}
3.4 获得位置为0的头节点数据
LinkedList中有多种方法可以获得头节点的数据,实现大同小异,区别在于对链表为空时的处理,是抛出异常还是返回null。主要方法有getFirst()、element()、peek()、peekFirst()、方法。其中getFirst()和element()方法将会在链表为空时,抛出异常,它们的实现如下:
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
public E element() {
return getFirst();
}
从代码可以看到,element()方法的内部就是使用getFirst()实现的。它们会在链表为空时,抛出NoSuchElementException
peek()和peekFirst()的实现:
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
public E peekFirst() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
当链表为空时,peek()和peekFirst()方法返回null
3.5 获得位置为size-1的尾节点数据
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
public E peekLast() {
final Node<E> l = last;
return (l == null) ? null : l.item;
}
getLast()方法在链表为空时,会抛出NoSuchElementException,而peekLast()则不会,只是会返回null
检索操作总结
检索操作分为按照位置得到对象以及按照对象得到位置两种方式,其中按照对象得到位置的方法有indexOf()和lastIndexOf();按照位置得到对象有如下方法:
- 根据任意位置得到数据的get(int index)方法,当index越界会抛出异常
- 获得头节点数据
- getFirst()和element()方法在链表为空时会抛出NoSuchElementException
- peek()和peekFirst()方法在链表为空时会返回null
- 获得尾节点数据
- getLast()在链表为空时会抛出NoSuchElementException
- peekLast()在链表为空时会返回null
3.6 删除指定对象
当删除指定对象时,只需调用remove(Object o)即可,不过该方法一次只会删除一个匹配的对象,如果删除了匹配对象,返回true,否则false
public boolean remove(Object o) {
//如果删除对象为null
if (o == null) {
//从前向后遍历
for (Node<E> x = first; x != null; x = x.next) {
//一旦匹配,调用unlink()方法和返回true
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
//从前向后遍历
for (Node<E> x = first; x != null; x = x.next) {
//一旦匹配,调用unlink()方法和返回true
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
由于LinkedList可以存储null元素,所以对删除对象以是否为null做区分。然后从链表头开始遍历,一旦匹配,就会调用unlink()方法将该节点从链表中移除。下面是unlink()方法的实现:
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;
}