ArrayList与linkedlist插入效率分析
2017-05-23 22:20
197 查看
网上有很多比较ArrayList和LinkedList的文章,基本上都认为ArrayList是用数组实现,而LinkedList用链表实现,所以ArrayList查询效率高,LinkedList插入效率高,但是事实真的是这样吗?
比较他们的使用效率之前,先看看ArrayList与LinkeLlist的底层数据结构。
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer.
*/
private transient Object[] elementData;
/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;
ArrayList内部用数组来保存元素,size记录当前数组的大小。但是数组的大小是确定的,那么他是怎么实现ArrayList这种大小可变的集合的呢?
public boolean add(E e) {
ensureCapacity(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
/**
* Increases the capacity of this <tt>ArrayList</tt> instance, if
* necessary, to ensure that it can hold at least the number of elements
* specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
public void ensureCapacity(int minCapacity) {
modCount++;
int oldCapacity = elementData.length;
if (minCapacity > oldCapacity) {
Object oldData[] = elementData;
int newCapacity = (oldCapacity * 3)/2 + 1;
if (newCapacity < minCapacity)
newCapacity = minCapacity;
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
每次ArrayList在添加元素的时候都会调用ensureCapacity方法,这个方法会检查数组容量是否够大,如果不够则新创建一个容量为1.5倍的数组,
并把数据复制到这个新数组上。
所以不能简答的说ArrayList底层数据结构是一个数组,其实它是一个动态表。
接下来看看LinkedList的实现,它是一个双向循环链表。
//链表大小,transient代表不可序列化
transient int size = 0;
/**
* 指向头节点的指针
*/
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;
}
}
值得注意的是,这里的链表节点是linkedlist的私有内部类,也就是说其他类是不可能拿到节点的引用的。所以在链表中间插入的时候,通过修改几个指针就能插入一个节点这样的操作是不可能的。
了解底层实现之后,我们开始比较ArrayList与linkedlist的插入效率。
1.尾部插入
public boolean add(E e) {
ensureCapacity(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
ArrayList最常用的add方法就是尾部插入,在不需要扩容的情况下,只需要将size递增即可,时间复杂度O(1)
需要扩容的情况下,时间复杂度也不会受到影响。理由如下:
假设它每次扩容两倍,那么从开始的16个元素扩容到n,共需要复制16+32+。。。+n/4+n/2+n<2n个元素
所以平均到每一次添加操作,扩容只需要复制常数个元素
/**
*默认的添加动作,可以看到这个方法是把新元素添加 到表尾
*/
public boolean add(E e) {
addBefore(e, header); //加到头结点之前 ,即表尾
return true;
}
/**
*将元素e添加到entry结点之前
*/
private Entry<E> addBefore(E e, Entry<E> entry) {
Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
newEntry.previous.next = newEntry; //将新结点与前后结点相连接
newEntry.next.previous = newEntry;
size++;
modCount++;
return newEntry;
}
可以看出linkedlist的最常用的add方法也是插入尾部,只需要改变几个指针,时间复杂度O(1)
综上,在两个集合最常用的尾部插入时间复杂度都是O(1),不存在ArrayList插入比linkedlist慢的说法
2.中间插入
public void add(int index, E element) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(
"Index: "+index+", Size: "+size);
ensureCapacity(size+1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
ArrayList的中间插入效率比较低,因为需要将index之后的元素往后移动,时间复杂度O(n),但是不代表ArrayList就比linkedlist慢
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
/**
* Returns the (non-null) Node at the specified element 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;
}
}
linkedlist虽然插入的时间复杂度为O(1),但是在每次插入前需要找到插入的位置。前面已经说过linkedlist的node节点是私有内部类,外部不可能拿到node的引用,所以linkedlist只能进行顺序寻址找到插入的位置,总时间复杂度也是O(n)
综上,ArrayList与linkedlist在中间插入的时间复杂度也是一样
3.迭代器插入
linkedlist和ArrayList内部都内置了一个迭代器用来遍历他们里面的元素,在遍历的时候也可以插入元素。ArrayList的迭代器插入用的是中间插入的方法,所以时间复杂度还是一样。但是linkedlist在迭代过程中不需要定位插入的位置,插入的时间复杂度变成O(1)。所以在需要迭代器插入O(n)个元素的时候,ArrayList时间复杂度为O(n^2),而linkedlist的时间复杂度为O(n),这时候才体现了链表插入的优势。但是如果只需要插入常数个元素的时候,迭代器遍历的开销大于插入的开销,此时两个集合的时间复杂度还是一样。
所以,不能简单的说ArrayList插入速度比linkedlist慢,附上别人做的性能测试,仅供参考 http://blog.csdn.net/dlutbrucezhang/article/details/9931025
比较他们的使用效率之前,先看看ArrayList与LinkeLlist的底层数据结构。
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer.
*/
private transient Object[] elementData;
/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;
ArrayList内部用数组来保存元素,size记录当前数组的大小。但是数组的大小是确定的,那么他是怎么实现ArrayList这种大小可变的集合的呢?
public boolean add(E e) {
ensureCapacity(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
/**
* Increases the capacity of this <tt>ArrayList</tt> instance, if
* necessary, to ensure that it can hold at least the number of elements
* specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
public void ensureCapacity(int minCapacity) {
modCount++;
int oldCapacity = elementData.length;
if (minCapacity > oldCapacity) {
Object oldData[] = elementData;
int newCapacity = (oldCapacity * 3)/2 + 1;
if (newCapacity < minCapacity)
newCapacity = minCapacity;
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
每次ArrayList在添加元素的时候都会调用ensureCapacity方法,这个方法会检查数组容量是否够大,如果不够则新创建一个容量为1.5倍的数组,
并把数据复制到这个新数组上。
所以不能简答的说ArrayList底层数据结构是一个数组,其实它是一个动态表。
接下来看看LinkedList的实现,它是一个双向循环链表。
//链表大小,transient代表不可序列化
transient int size = 0;
/**
* 指向头节点的指针
*/
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;
}
}
值得注意的是,这里的链表节点是linkedlist的私有内部类,也就是说其他类是不可能拿到节点的引用的。所以在链表中间插入的时候,通过修改几个指针就能插入一个节点这样的操作是不可能的。
了解底层实现之后,我们开始比较ArrayList与linkedlist的插入效率。
1.尾部插入
public boolean add(E e) {
ensureCapacity(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
ArrayList最常用的add方法就是尾部插入,在不需要扩容的情况下,只需要将size递增即可,时间复杂度O(1)
需要扩容的情况下,时间复杂度也不会受到影响。理由如下:
假设它每次扩容两倍,那么从开始的16个元素扩容到n,共需要复制16+32+。。。+n/4+n/2+n<2n个元素
所以平均到每一次添加操作,扩容只需要复制常数个元素
/**
*默认的添加动作,可以看到这个方法是把新元素添加 到表尾
*/
public boolean add(E e) {
addBefore(e, header); //加到头结点之前 ,即表尾
return true;
}
/**
*将元素e添加到entry结点之前
*/
private Entry<E> addBefore(E e, Entry<E> entry) {
Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
newEntry.previous.next = newEntry; //将新结点与前后结点相连接
newEntry.next.previous = newEntry;
size++;
modCount++;
return newEntry;
}
可以看出linkedlist的最常用的add方法也是插入尾部,只需要改变几个指针,时间复杂度O(1)
综上,在两个集合最常用的尾部插入时间复杂度都是O(1),不存在ArrayList插入比linkedlist慢的说法
2.中间插入
public void add(int index, E element) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(
"Index: "+index+", Size: "+size);
ensureCapacity(size+1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
ArrayList的中间插入效率比较低,因为需要将index之后的元素往后移动,时间复杂度O(n),但是不代表ArrayList就比linkedlist慢
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
/**
* Returns the (non-null) Node at the specified element 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;
}
}
linkedlist虽然插入的时间复杂度为O(1),但是在每次插入前需要找到插入的位置。前面已经说过linkedlist的node节点是私有内部类,外部不可能拿到node的引用,所以linkedlist只能进行顺序寻址找到插入的位置,总时间复杂度也是O(n)
综上,ArrayList与linkedlist在中间插入的时间复杂度也是一样
3.迭代器插入
linkedlist和ArrayList内部都内置了一个迭代器用来遍历他们里面的元素,在遍历的时候也可以插入元素。ArrayList的迭代器插入用的是中间插入的方法,所以时间复杂度还是一样。但是linkedlist在迭代过程中不需要定位插入的位置,插入的时间复杂度变成O(1)。所以在需要迭代器插入O(n)个元素的时候,ArrayList时间复杂度为O(n^2),而linkedlist的时间复杂度为O(n),这时候才体现了链表插入的优势。但是如果只需要插入常数个元素的时候,迭代器遍历的开销大于插入的开销,此时两个集合的时间复杂度还是一样。
所以,不能简单的说ArrayList插入速度比linkedlist慢,附上别人做的性能测试,仅供参考 http://blog.csdn.net/dlutbrucezhang/article/details/9931025
相关文章推荐
- Java数据结构之LinkedList、ArrayList的效率分析
- ArrayList和LinkedList存取效率分析
- LinkedList,ArrayList末尾插入谁效率高?
- Java数据结构之LinkedList、ArrayList的效率分析
- Java使用Arrays、ArrayList、LinkedList、Vector实现插入查询性能分析
- LinkedList,ArrayList末尾插入谁效率高?
- Java数据结构之LinkedList、ArrayList的效率分析
- LinkedList,ArrayList末尾插入谁效率高?
- 常用集合ArrayList,LinkedList,HashMap,HashSet源码分析
- 【Java集合】ArrayList、LinkedList、Vector分析
- ArrayList和LinkedList 内部结构分析(一)
- java ArrayList与LinkedList 使用for,forearch,Iterator的遍历效率
- ArrayList和LinkedList效率解说
- ArrayList和LinkedList源码分析总结
- java的list几种实现方式的效率(ArrayList、LinkedList、Vector、Stack),以及 java时间戳的三种获取方式比较
- 分析ArrayList和LinkedList的区别
- 【转】LinkedList、ArrayList各自的使用场景分析
- Java 集合系列08之 List总结(LinkedList, ArrayList等使用场景和性能分析)
- ArrayList 与 LinkedList的插入效率实践分析
- Java 集合系列08之 List总结(LinkedList, ArrayList等使用场景和性能分析)