1、ArrayList源码
arraylist底层实际上是一个数组结构transient Object[] elementData;,所以arraylist集合拥有数组的特性,可以方便的通过索引进行访问。
- 1、初始化构造函数
//默认大小的数组,空的
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
//先查看空参的构造函数,在创建的时候会赋值为一个空的数组
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_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);
}
}
- 2、add、addAll
//默认初始化大小10
private static final int DEFAULT_CAPACITY = 10;
//添加方法
public boolean add(E e) {
//1、判断内部容量
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
//1、容量判断是否扩容,传入当前size+1
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
//1.1、计算容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//如果是空数组,获取DEFAULT_CAPACITY和minCapacity的最大值
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
//否则返回size + 1大小
return minCapacity;
}
//1.2、是否需要扩容
private void ensureExplicitCapacity(int minCapacity) {
modCount++; //修改的次数,迭代器使用
// size+1 > 数组的长度的时候
if (minCapacity - elementData.length > 0)
//增长
grow(minCapacity);
}
//1.3、扩容函数
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//新数组长度 = 元素数组长度的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果还不够,则新数组长度 = minCapacity
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、add方法调用的时候,会进行扩容。构造函数创建的时候,会创建空的数组,在进行添加的时候,才会创建指定大小的数组 2、默认数组大小为10,这是出于性能损失与空间损失之间的最佳匹配考量。而扩容因子是1.5:因为扩容不太太小,太小容易频繁进行扩容,从而进行数组的频繁复制;而也不能太大,当扩容因子大于2的时候,新的容量刚好大于之前废弃的容量,容易造成空间的浪费。而且1.5可以充分的利用移位的操作
- add方法,添加到指定位置
public void add(int index, E element) {
//范围检查
rangeCheckForAdd(index);
//扩容检查
ensureCapacityInternal(size + 1); // Increments modCount!!
//数组移动
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
- addAll方法
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
//判断是否扩容
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
- get方法
public E get(int index) {
//范围检查
rangeCheck(index);
//返回数组指定的索引
return elementData(index);
}
- set方法
public E set(int index, E element) {
//范围检查
rangeCheck(index);
//获取索引位置的元素
E oldValue = elementData(index);
//赋值
elementData[index] = element;
return oldValue;
}
- remove方法
public E remove(int index) {
//检查索引
rangeCheck(index);
//记录修改
modCount++;
E oldValue = elementData(index);
//删除移动的元素数量
int numMoved = size - index - 1;
if (numMoved > 0)
//移动
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
public boolean remove(Object o) {
//删除null
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
- subList方法
public List<E> subList(int fromIndex, int toIndex) {
//检查索引
subListRangeCheck(fromIndex, toIndex, size);
//创建内部类SubList
return new SubList(this, 0, fromIndex, toIndex);
}
//内部类
private class SubList extends AbstractList<E> implements RandomAccess {
private final AbstractList<E> parent;
private final int parentOffset;
private final int offset;
int size;
//构造方法
SubList(AbstractList<E> parent,
int offset, int fromIndex, int toIndex) {
this.parent = parent; //指向原集合
this.parentOffset = fromIndex; //原集合开始索引
this.offset = offset + fromIndex; //原集合结束索引
this.size = toIndex - fromIndex;
this.modCount = ArrayList.this.modCount;
}
public E set(int index, E e) {
rangeCheck(index);
checkForComodification();
E oldValue = ArrayList.this.elementData(offset + index);
ArrayList.this.elementData[offset + index] = e;
return oldValue;
//....
}
1、可以看出subList实际上还是指向的原始集合,当原始集合改变或者sublist进行改变,它们的本质是相通的,都是改变的数组
ArrayList<Object> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
List<Object> subList = list.subList(0, 2);
subList.set(0,"a1"); //sublist改变原集合也改变
list.set(1,"b1"); //原集合改变,sublist也进行改变
subList.stream().forEach(System.out::println);
list.stream().forEach(System.out::println);
/**
a1
b1
a1
b1
c
*/
2、LinkedList源码
LinkedList内部维护双向链表的数据结构,他分别有一个节点类Node,以及两个引用,一个头引用,一个尾引用
- node类
transient Node<E> first; //头引用
transient Node<E> last; //尾引用
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 LinkedList() {
}
//添加集合
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
- add、addAll
public boolean add(E e) {
//获取最后一个元素
linkLast(e);
return true;
}
void linkLast(E e) {
final Node<E> l = last; //获取最后一个元素
final Node<E> newNode = new Node<>(l, e, null); //创建新节点,新节点的上一个元素只想last
last = newNode; //把尾指针指向新节点
if (l == null) //如果尾节点为null,即现在为空链,头指针也指向新建的节点
first = newNode;
else
l.next = newNode; //令老尾节点的下一个节点指向新的尾节点
size++;
modCount++;
}
//指定索引进行添加
public void add(int index, E element) {
//检查位置索引
checkPositionIndex(index);
//如果插入的位置是最后一个元素,采用尾插法
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
//根据索引,查找该节点
Node<E> node(int index) {
// assert isElementIndex(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;
}
}
//succ:要插入的位置
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); //创建新节点,节点前索引指向pred,后索引指向插入位置的元素
succ.prev = newNode; //插入位置的元素的前节点指向新建的元素
if (pred == null) //如果前节点为null
first = newNode; //把头节点指向新建的几点
else
pred.next = newNode;
size++;
modCount++;
}
//指定索引位置添加元素
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index); //检查索引
Object[] a = c.toArray();
int numNew = a.length; //插入元素的长度
if (numNew == 0)
return false;
//succ:要插入的节点,pred:要插入节点的前一个
Node<E> pred, succ;
if (index == size) { //要插入为最后一个元素
succ = null;
pred = last;
} else {
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.next = newNode;
pred = newNode;
}
if (succ == null) {
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
}
size += numNew;
modCount++;
return true;
}
- get、set方法
public E get(int index) {
//检查索引
checkElementIndex(index);
//node方法遍历获取指定索引位置的元素
return node(index).item;
}
public E set(int index, E element) {
//检查索引
checkElementIndex(index);
//获取指定索引的节点
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
- remove方法
public E remove(int index) {
//检查索引
checkElementIndex(index);
//先node方法根据索引查找节点
return unlink(node(index));
}
//删除方法
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;
}
- subList方法
//linkedlist 的subList方法是继承abstractList方法的subList
public List<E> subList(int fromIndex, int toIndex) {
return (this instanceof RandomAccess ? //判断是否继承RandomAccess接口,RandomAccess接口是标机接口,标记支持随机访问的集合
new RandomAccessSubList<>(this, fromIndex, toIndex) :
new SubList<>(this, fromIndex, toIndex)); //
}
//AbstractList内部类
class SubList<E> extends AbstractList<E> {
private final AbstractList<E> l;
private final int offset;
private int size;
SubList(AbstractList<E> list, int fromIndex, int toIndex) {
if (fromIndex < 0)
throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
if (toIndex > list.size())
throw new IndexOutOfBoundsException("toIndex = " + toIndex);
if (fromIndex > toIndex)
throw new IllegalArgumentException("fromIndex(" + fromIndex +
") > toIndex(" + toIndex + ")");
l = list; //还是指向原集合
offset = fromIndex;
size = toIndex - fromIndex;
this.modCount = l.modCount;
}
//添加方法
public void add(int index, E element) {
rangeCheckForAdd(index);
checkForComodification();
l.add(index+offset, element); //调用linkedlist的add方法,对原数组进行更改
this.modCount = l.modCount;
size++;
}
3、对比ArrayList和Linked List
- ArrayList内部维护的数组而LinkedList内部采用的链表结构
- ArrayList的结构使得它支持随机访问,但对于插入元素而言,linkedList要快
- 头部插入时:LinkedList有绝对的优势,它不需要移动元素和进行扩容,而且查找要插入的位置的时候可以很快的找到;ArrayList头部进行插入的时候,可能需要进行扩容,而且移动会牵扯到数组的移动
- 中间部分插入:由于LinkedList需要循环遍历查找要插入的位置,所以也会消耗大量时间,具体业务具体分析
- 尾部进行插入:再不考虑扩容的情况下,arrayList尾部插入有绝对的优势,它不需要创建节点等操作
- 两者都是按照存入顺序进行存取的,而且可以存入重复的元素