JAVA中ArrayList和LinkedList的区别
ArrayList和LinkedList的大致区别如下:
1.ArrayList是基于动态数组的数据结构实现,LinkedList是基于双向链表实现。
2.对于随机访问的get和set方法,ArrayList要优于LinkedList,因为ArrayList基于数组,获取任意元素的位置时间复杂度为O(1),LinkedList基于链表,时间复杂度则为O(n)。
3.对于新增和删除操作add和remove,LinkedList比较占优势,因为如果ArrayList不是在尾部进行新增和删除时数组会进行前移或后移。
具体说明
构造函数
ArrayList
在不指定容量时,构造一个初始容量为 10 的空数组,不过在第一次插入时才会分配初始容量为 10 的空数组。
transient Object[] elementData;
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
LinkedList
因为LinkedList是基于双向链表,链表不需要分配连续的内存空间,所以无参构造中无任何操作。
/**
* Constructs an empty list
* */
public LinkedList() {}
插入操作
ArrayList
可以看到通过下面的源码看到,ArrayList在尾部插入时,先去检查内部容量,若为DEFAULTCAPACITY_EMPTY_ELEMENTDATA(不指定容量生产ArrayList对象时)则分配初始容量为 10 的空数组,若容量不足则进行扩容,扩容为原数组大小右移一位加上原数组大小,约为1.5倍,为什么是1.5倍呢,需要考虑如下情况:
1.扩容容量不能太小,防止频繁扩容,频繁申请内存空间 + 数组频繁复制
2.扩容容量不能太大,需要充分利用空间,避免浪费过多空间
为了能充分使用之前分配的内存空间,最好把增长因子设为 1<k<2,并且充分利用移位操作(右移一位),减少浮点数或者运算时间和运算次数。
不在尾部插入时,则是使用了System.arraycopy()方法进行了数组的后移。
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
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;
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);
}
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
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++;
}
LinkedList
LinkedList的插入则是修改Node节点中的前驱节点(prev)与后驱节点(next)进行插入,若是尾部插入则next为null。
public boolean add(E e) {
linkLast(e);
return true;
}
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
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++;
}
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++;
}
删除操作
ArrayList删除时,也是使用了System.arraycopy()方法进行了数组的前移,LinkedList则是通过修改prev和next进行remove操作,故删除时LinkedList效率比ArrayList高。
获取数据
ArrayList直接通过数组下标获取,LinkedList则是通过遍历的方式进行获取,不过并不是重头节点开始进行便利,为了提高效率,元素下标小于长度的一半则从头部开始遍历,否则从尾部开始遍历,这样便提高了遍历的效率。
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
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;
}
}
ArrayList和LinkedList的缺点如下:
- 对ArrayList和LinkedList而言,在末尾增加一个元素所花的开销都是固定的。对ArrayList而言,主要是在内部数组中增加一项,偶尔可能会导致对数组重新进行分配;而对LinkedList而言,这个开销是统一的,分配一个内部对象。
- 在ArrayList集合中添加或者删除一个元素时,当前的数组移动元素后面的所有元素都会被移动。而在LinkedList中添加或者删除一个元素的开销是固定的。
- LinkedList集合不支持高效的随机随机访问, ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间。在ArrayList数组预留空间不足时,要进行数组的扩容,而LinkedList则不需要。