ArrayList
Arraylist是基于数组实现的
transient Object[] elementData;
elementData是ArrayList真正用来保存数据的Object数组。
构造器
一边我们使用时都是:
List<Object> arrayList = new ArrayList<>();
默认使用空构造器
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
所以当我们新建一个ArrayList时都是一个空的Object数组。
这里出现两个疑问:
1,那么初始化一个空数组,那么怎么添加元素?
其实在我们可以使用其他构造器指定初始值的大小,比如:
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,既然是object数组那么泛型的是干什么呢?
其实这个地方我理解,泛型在arraylist集合中只是起到一个校验元素的作用。泛型我们知道并不是一个真正类型,当编译时还是会编译成具体类型。所以在集合中使用泛型其实对程序运行没有任何效率上的帮助,但是他会让我们可以一个集合只存储一种固定的类型。
add
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) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(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;
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);
}
其实很简单,简单一看就懂了。
添加一个元素时,默认添加到数组最后一个。
比较size+1和默认的10取大;如果容量不够了,调用grow方法。然后使用copy方法对其进行扩容。
再看一下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++;
}
指定位置添加。我们这里看到他会对数组进行一次copy,所以这里出现了一个arraylist的缺点;
arraylist因为基于数组,所以如果是顺序添加按位查找或按顺序遍历,那么他的性能非常好。但是如果对其某一个位置的内容进行修改或者使用迭代器遍历,那么他的性能就会大打折扣,因为他要是进行数组的copy,并且操作的数据越靠前,他copy的数组越大,性能就越差。
但arrayLIst如果是按索引值查找,时间复杂度O(1),效率高。所以引申出来,假设数据量足够大:
当对ArrayList进行迭代时,应该使用fori循环,因为forI循环是取的数组索引下标。
而foreach循环采用的是迭代器循环,当取下一个元素时都是从头遍历一次。
remove方法也类似,这里就不多赘述了。
LinkedList
我们先看一下他的内部定义的几个成员变量
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;
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;
}
}
很明显能看出来linkedlist内部实现是使用node内部类进行存储数据的。他有两个node,一个是first,一个是last,linkedlist保存这两个node,其他的node之间的关系,要看node内部去维护。
node有3个属性:
item:要存的值
prev:前一个
next:下一个
所以数据结构就很明白了。他是一个链表时的结构。
add方法
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 = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
他先讲last备份,然后将要存的值创建一个node对象进行保存,并且将之前的last值的关系存入node,这个新的node也就是最后一个了。然后判断如果之前的last为空,说明这是第一个元素。如果不是那么将之前备份的那个元素的next指针指向新的node对象。
由这个方法我们看出来linkedlist的特性:
linkedlist也是自动保存到最后一位。他的值是以node对象的形式存储的,相对于arraylist来说更占一些内存。但是他无需扩容,并且不用copy数组,所以性能相对较高。他适合于迭代器来遍历,当按位添加或者删除指定位数据时由于只需要维护被操作node的前后元素,而没有copy的性能消耗。所以按位添加相对于arrayList性能更好。
这里说明一下我看的源码的JDK版本是1.8,每个版本的jdk的实现都不太一样。略有区别。但暴露的API实现功能还是一致的。