ArrayList
基础属性:
transient Object[] elementData; 存储数据的数组
private static final int DEFAULT_CAPACITY = 10; 默认长度
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; default sized empty instances
1. new ArrayList<>()
/**
* Constructs an empty list with an initial capacity of ten.默认的数组长度是10
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
2. add方法 - 增
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 void ensureExplicitCapacity(int minCapacity) {
//并发标识位
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
//扩容!!
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//扩容为原始长度的1.5倍。 >>右移位符号,移动一位相当于除以2。
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);
}
//使用Arrays工具类的copyOf方法,底层使用的是System.arraycopy的native方法。可能会造成Heap space OOM
3. remove方法 - 删(有多个方法)
public E remove(int index) {
//检查下标,防止越界
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
//index之后的数据全部复制到index的位置。这一步较为费时,频繁的删除时不应该使用ArrayList!
System.arraycopy(elementData, index+1, elementData, index,numMoved);
//将数组指向null,方便GC
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
4. set方法 - 改
public E set(int index, E element) {
//检查下标后直接更新
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
5. get方法 -查
//1、知道下标时
public E get(int index) {
//大于等于size时,抛出IndexOutOfBoundsException
rangeCheck(index);
//返回对应下标下的数据
return elementData(index);
}
LinkedList
基础属性:
transient Node first; 头结点
transient Node last; 尾节点
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;
}
}
1. new LinkedList<>();
/**
* Constructs an empty list.
*/
public LinkedList() {
}
2. 增 - add
public boolean add(E e) {
linkLast(e);
return true;
}
//采用尾插法,将插入的节点作为last节点
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++;
}
3. 删 - remove
//remove指定index的
public E remove(int index) {
//检查下标,越界则抛出异常
checkElementIndex(index);
return unlink(node(index));
}
//获取指定index下的Node
Node<E> node(int index) {
// assert isElementIndex(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;
}
}
//释放指定的Node
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
final Node<E> next = x.next; //Node的前置节点next
final Node<E> prev = x.prev; //Node的后置节点prev
//Node是初始节点,将next置为first节点
if (prev == null) {
first = next;
} else {
//Node不是初始节点
prev.next = next;
x.prev = null;
}
//Node是尾节点,将前置节点作为尾节点
if (next == null) {
last = prev;
} else {
//Node不是尾节点,设置其前置节点
next.prev = prev;
x.next = null;
}
//Node变为了null,方便GC回收
x.item = null;
size--;
modCount++;
return element;
}
可以看到删除时仅仅移动了链表,不像ArrayList一样对数组进行了Copy。
4. 改 - set
改特定位置的Node的数据
public E set(int index, E element) {
//下面这两个方法很眼熟吧 :)
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
5. 查 - get
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
- 查找时ArrayList比LinkedList效率高,因为数组结构的遍历只需将下标加一,而链表结构则从头查找;
- 对元素进行增删时LinedList会比ArrayList好用,链表结构可以很轻松的在链表中间去增删元素,而不需要改变插入节点后面元素的任何数据。
遍历
-
for循环遍历,实际上是使用get(int index)方法进行查找元素,最基础的
-
迭代器,ArrayList和LinkedList均有内部类实现了ListIterator接口,如LinkedList:
private class ListItr implements ListIterator<E> { private Node<E> lastReturned; private Node<E> next; private int nextIndex; private int expectedModCount = modCount; ...省略部分代码 public boolean hasNext() { return nextIndex < size; } public E next() { checkForComodification(); if (!hasNext()) throw new NoSuchElementException(); lastReturned = next; next = next.next; nextIndex++; return lastReturned.item; } ...省略 public void remove() { checkForComodification(); if (lastReturned == null) throw new IllegalStateException(); Node<E> lastNext = lastReturned.next; unlink(lastReturned); if (next == lastReturned) next = lastNext; else nextIndex--; lastReturned = null; expectedModCount++; } public void set(E e) { if (lastReturned == null) throw new IllegalStateException(); checkForComodification(); lastReturned.item = e; } public void add(E e) { checkForComodification(); lastReturned = null; if (next == null) linkLast(e); else linkBefore(e, next); nextIndex++; expectedModCount++; }
如果想一边迭代一边用集合自带的方法进行删除或者新增操作,都会抛出ConcurrentModificationException异常(在增加和删除元素的时候,都会进行自增操作 modCount)。所以在遍历时需要增删list元素的,则需要使用迭代器。
- forEach,ArrayList使用的是数组下标:
@Override
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
final int expectedModCount = modCount;
@SuppressWarnings("unchecked")
final E[] elementData = (E[]) this.elementData;
final int size = this.size;
for (int i=0; modCount == expectedModCount && i < size; i++) {
action.accept(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
Best Practice:
- 实现了RadmoAcces接口的list(如ArrayList),优先选择普通for循环(使用下标直接随机读取) ,其次list.foreach方法或增强型的for循环
- 未实现RadmoAcces接口的list(如LinkedList), 优先选择iterator遍历,移动指针(foreach遍历底层也是通过iterator实现的),大size的数据,千万不要使用普通for循环(每次查找都要移动大量数据)