Java集合--深度剖析Vector、Stack、ArrayList、LinkedList(二)
2018-03-02 23:14
591 查看
研究环境:jdk1.8
还是这张图,可以清晰地看到各个类或者接口之间的关系。我们知道集合可以分为两大类:由Collection接口延伸出的集合和由Map接口延伸出的集合。详细地分,集合可以分为4种类型:List类型、Set类型、Queue类型、Map类型,今天我们研究的是List类型的集合,Vector、Stack、ArrayList和LinkedList。
从它们最开始的地方说起,也就是Collection接口。这个接口无非定义一些集合通用的方法,比如添加、删除元素啊,size()啊等等。查看Collection源码可以发现,它是继承自Iterable接口的,Iterable接口中有一个方法返回一个迭代器:Iterator< T> iterator(); Collection继承了这个接口就可以使用迭代器了。这里有个疑问,为什么Collection不直接继承Iterator接口,而是要多定义一个Iterable接口,让Collection去继承Iterable,而不是Iterator呢?如果让Collection直接继承Iterator接口,这样Collection本身就变成了一个迭代器了,它有next方法,hasNext方法,也就是说Collection包含着当前迭代的位置(状态),先不说多线程的情况了,就是单线程,如果一个方法遍历Collection,遍历了一半突然终止了,那么Collection中迭代的位置就在中间。而另一个方法接着遍历它,却发现只能遍历后一半的内容,前一半遍历不到,因为Iterator没有提供重置迭代位置的方法。而继承Iterable就不同了,它有一个iterator()方法,它能保证每一次调用Collection,都能返回一个新的迭代器,不同的迭代器之间是互不影响的。随便看一个集合对于iterator方法的实现方式:
可以看到每一次调用iterator方法都会新new一个迭代器
图中从上往下看,说完了Collection,就到了它的子接口List和实现类AbstractCollection,里面的方法都没什么好讲的,再继续往下看就是AbstractList,它继承了AbstractCollection类,并且实现了List接口,它虽然还是一个抽象类,但是它从父类或者父接口中继承来的方法差不多已经有了实现方式,包括iterator方法。从图中的关系可以看出,Vector、Stack、ArrayList、LinkedList全是AbstractList的子孙类,AbstractList派生了Vector、ArrayList、AbstractSequentialList,接着由Vector继续派生出Stack,AbstractSequentialList派生出LinkedList。好了,主角终于登场了,弄清了相应的父子类关系,就可以来说说Vector、Stack、ArrayList、LinkedList这四种集合的内部实现了。
(by the way,看源码,我们会发现Vector、ArrayList、LinkedList都实现了List接口,但是AbstractList已经实现过List接口了啊,Vector、ArrayList、LinkedList作为AbstractList的子孙类,完全没有必要再显示的写implements List啊,即使有不同于AbstractList的实现方式,直接重写就行了啊。难道只是一种规范,增加代码可读性,或者防止以后AbstractList不实现List了,Vector、ArrayList、LinkedList根本不用去修改,降低维护成本)
Vector、ArrayList底层都是由object数组实现,Stack是Vector的子类,底层也是数组;LinkedList由双向链表实现,所以它的核心是组成链表的Node节点。
一、既然都是由数组实
f199
现的,那就先来比较Vector和ArrayList吧:
1. 类声明
这是这两个类的声明,是一样的,都继承了AbstractList,并实现了List、RandomAccess、 Cloneable、Serializable接口,说明了Vector和ArrayList都是可以快速随机访问、可克隆、可以被序列化和反序列化的,容量大小可变的集合。(网上有人说List是有序可重复的,我觉得这种说法还是有点问题的,你不能说一个接口具有怎么样的特性,如果能通过一些抽象方法就看出接口具有什么特点,也是厉害。那么为什么会说List接口是有序,可重复的呢?因为在Java里,实现了List接口的实现类,比如ArrayList、LinkedList都表现出了这个特点,数据是按录入的顺序存放的,并且可以重复)RandomAccess接口虽然只是一个标记接口,里面什么方法都没有定义,但它就是表明Vector和ArrayList具有随机访问性,毕竟底层是用数组实现的,可以根据数组下标直接找到元素。
上面我们说了Vector和ArrayList都是用数组实现的,看一下这两个类的成员变量
Vector的elementData是用protected修饰的,而ArrayList的elementData是用transient修饰的,那不就是说ArrayList中的数据元素不会被序列化?对象序列化确切地说有三种方式
只是实现Serializable接口,那么在序列化时,就会自动调用java.io.ObjectOutputStream的defaultWriteObject()方法进行序列化,这时用transient修饰的成员变量是不会被序列化的
实现Serializable接口,重写writeObject()方法,这时用transient修饰的成员变量(除静态变量)能不能被序列化,就取决于重写的writeObject()方法了
实现Externalizable接口,Externalizable继承了Serializable接口,并增加了两个方法:writeExternal(ObjectOutput out) 和readExternal(ObjectInput in) 。在writeExternal方法里定义了哪些属性可以序列化,哪些不可以被序列化
很明显,ArrayList采用的是第二种方式
它先调用defaultWriteObject方法,此时用transient修饰的成员变量还没有被序列化,但是它后来把数组里的元素一个一个遍历并序列化,也就是说ArrayList中用transient修饰的elementData数组是被序列化的。那ArrayList花这么大的功夫来“多此一举”,为什么不直接把elementData的修饰符transient去掉呢?仔细想想还是有点用的,因为elementData数组在初始化或者扩容时,允许有空元素,如果在序列化时不加控制,就会把空元素也序列化,ArrayList这样做,在序列化时,不会把空元素也序列化,它能保证序列化的元素都是有用的,也就是下标从0到size-1的元素。
2. 自动扩容
在构造Vector和ArrayList时,如果不显示给容量赋值,那么它们的默认容量都为10。当容量不够时,它们都会自动扩容,那么扩容的大小是怎么样的呢
在代码中可以看到,对于ArrayList,int newCapacity = oldCapacity + (oldCapacity >> 1); 也就是说,新容量=旧容量*1.5; 对于Vector, int newCapacity = oldCapacity + ((capacityIncrement > 0) ?capacityIncrement : oldCapacity); 这里涉及到一个成员变量capacityIncrement,它表示Vector每次扩容,增加的数值,在构造Vector时就可以显示给它赋值,如果没有给它赋值,默认为0。所以当capacityIncrement>0时,新容量 = 旧容量+capacityIncrement;当capacityIncrement<=0时,新容量 = 旧容量*2 。
Vector和ArrayList都有一个ensureCapacity(int minCapacity)方法,它最后都会调用grow(int minCapacity)方法,如果不看API或者源码,很可能会被它误导,以为它是按照minCapacity参数扩容。比如ArrayList,默认容量为10,当调用ensureCapacity(11)后,实际容量并不是11,而是15。在API中,这个方法表示:如果需要,增加此向量的容量,以确保它可以至少保存最小容量参数指定的组件数。至于调用后实际容量的值就根据源码计算吧
3. 同步
Vector使用synchronized来保证线程安全(同步),并且是读写都加了synchronized。ArrayList则是非线程安全的(不同步),所以在效率上ArrayList要优于Vector。
4. 遍历
ArrayList支持用迭代器Iterator和foreach/for去遍历,而Vector除了这两种方式,还支持Enumeration去遍历
5. 其他方面
Vector和ArrayList在其他方面都差不多,添加、删除元素等方法都很类似。但是,对于删除元素,虽然Vector的坑比ArrayList要少,但还是会一不小心就出错,具体可以看我的另一篇博客那些年,我们在Java ArrayList Remove方法遇到的坑
二、Stack与Vector
Stack和Vector是父子关系,Vector是父,Stack是子,所以Stack也是用数组实现的,确切地说,是一个用数组实现的栈,满足先进后出原则(FILO),入栈和出栈都是在数组下标为size-1的地方。
三、链表与数组
上面说了用数组实现的集合,现在可以来说说用链表实现的集合了,代表就是LinkedList了。
1. 底层数据结构
LinkedList的底层数据结构是节点Node
每个节点由三部分所组成,分别是前节点(引用地址)、后节点(引用地址)以及本节点所包含的实体数据。
我们先来看一下LinkedList到底是一个怎么样的链表,它的成员变量由三个:
回想一下,为LinkedList添加元素的语句:
List< String> list = new LinkedList<>();
list.add(“abc”);
好,我们就从add方法开始
代码很简单,就不详细说明了,到这里我们可以知道LinkedList是一个双向链表,大致如下图(原谅我又盗图了,其实在看linkLast代码的时候,让我想起了以前我学数据结构的时候,那时候还是用C++写的。时间过的很快,转眼大学四年就要过去了,马上也要实习了,还是比较伤感的)
2. 类声明
AbstractSequentialList是一个抽象类,它继承于AbstrctList。实现了链表中,根据index索引值操作链表的全部方法。
它没有实现RandomAccess接口,说明不具备快速随机访问
实现了Deque接口(通常可以被用来当作栈、队列使用)
3. LinkedList与ArrayList的区别
底层实现:ArrayList由数组实现;LinkedList由链表实现
随机访问:ArrayList随机访问速度快,LinkedList访问速度慢
可以看到,ArrayList可以直接根据下标从数组中返回相应的元素;而LinkedList就不同了,它要在链表中循环,一个节点一个节点地往下找,虽然已经做了优化,根据参数来判断是在左半边链表还是右半边链表,然而还是没有ArrayList速度快。
删除、插入元素:这个还真不好说
没看源码之前,我是非常相信网上所说的LinkedList在插入、删除元素方面,要优于ArrayList的,因为ArrayList要移动数据。但是看了源码之后,我就不这么认为了。每次插入(删除)元素,ArrayList的部分元素都要向前或向后移动,而LinkedList只要直接插入(删除)即可,但是在通过下标找节点时要花费一定时间,那就要看是遍历链表的代价大,还是移动数据的代价大了。所以这应该要看你集合中原本的数据量,以及插入(删除)元素的位置了,至于到底是怎么样的结果,因为懒就不做实验了,哈哈。网上有一个实验的结果是这样的:如果待插入、删除的元素是在数据结构的前半段尤其是非常靠前的位置的时候,LinkedList的效率将大大快过ArrayList,因为ArrayList将批量copy大量的元素;越往后,对于LinkedList来说,优势就不是很明显了,需要寻址的时间越来越长,ArrayList要批量copy的元素越来越少,操作速度必然追上乃至超过LinkedList。
还是看情况吧,比如都是remove(Object o)方法,就不止是LinkedList要遍历整个容器,找到相应的元素了,ArrayList也需要在整个容器中遍历,找到需要删除的元素,这样,就明显要选择LinkedList,而不是ArrayList了,因为LinkedList在删除后不需要移动数据,而且顺序遍历,链表是要快于数组的;还有就是,如果是顺序插入,并且数据量也知道,那肯定是选择ArrayList,因为ArrayList不存在移动数据和自动扩容造成的时间、空间浪费问题,反而是LinkedList每次插入数据,在定义节点时会浪费时间和内存。
有一种情况特别要注意,在插入元素的时候,ArrayList可能会扩容,这是一个耗费时间和空间的事情,因为数组在堆上需要一块很大的连续的空间,如果当时连续的空间不能满足扩容要求,就会发生gc,那代价就更高了,所以一般来说,在数据量不知道,需要经常插入操作的,还是选择LinkedList吧。
同步:都是不同步的(非线程安全)
顺序迭代遍历:LinkedList比ArrayList要快,毕竟是链表
空间浪费:ArrayList的空间浪费主要体现在自动扩容后,有一部分多余空间没有被利用(可以在ArrayList分配完空间后使用trimToSize来去掉浪费的空间),而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间
还是这张图,可以清晰地看到各个类或者接口之间的关系。我们知道集合可以分为两大类:由Collection接口延伸出的集合和由Map接口延伸出的集合。详细地分,集合可以分为4种类型:List类型、Set类型、Queue类型、Map类型,今天我们研究的是List类型的集合,Vector、Stack、ArrayList和LinkedList。
从它们最开始的地方说起,也就是Collection接口。这个接口无非定义一些集合通用的方法,比如添加、删除元素啊,size()啊等等。查看Collection源码可以发现,它是继承自Iterable接口的,Iterable接口中有一个方法返回一个迭代器:Iterator< T> iterator(); Collection继承了这个接口就可以使用迭代器了。这里有个疑问,为什么Collection不直接继承Iterator接口,而是要多定义一个Iterable接口,让Collection去继承Iterable,而不是Iterator呢?如果让Collection直接继承Iterator接口,这样Collection本身就变成了一个迭代器了,它有next方法,hasNext方法,也就是说Collection包含着当前迭代的位置(状态),先不说多线程的情况了,就是单线程,如果一个方法遍历Collection,遍历了一半突然终止了,那么Collection中迭代的位置就在中间。而另一个方法接着遍历它,却发现只能遍历后一半的内容,前一半遍历不到,因为Iterator没有提供重置迭代位置的方法。而继承Iterable就不同了,它有一个iterator()方法,它能保证每一次调用Collection,都能返回一个新的迭代器,不同的迭代器之间是互不影响的。随便看一个集合对于iterator方法的实现方式:
ArrayList: public Iterator<E> iterator() { return new Itr(); } private class Itr implements Iterator<E>{ //具体实现不看了 }
可以看到每一次调用iterator方法都会新new一个迭代器
图中从上往下看,说完了Collection,就到了它的子接口List和实现类AbstractCollection,里面的方法都没什么好讲的,再继续往下看就是AbstractList,它继承了AbstractCollection类,并且实现了List接口,它虽然还是一个抽象类,但是它从父类或者父接口中继承来的方法差不多已经有了实现方式,包括iterator方法。从图中的关系可以看出,Vector、Stack、ArrayList、LinkedList全是AbstractList的子孙类,AbstractList派生了Vector、ArrayList、AbstractSequentialList,接着由Vector继续派生出Stack,AbstractSequentialList派生出LinkedList。好了,主角终于登场了,弄清了相应的父子类关系,就可以来说说Vector、Stack、ArrayList、LinkedList这四种集合的内部实现了。
(by the way,看源码,我们会发现Vector、ArrayList、LinkedList都实现了List接口,但是AbstractList已经实现过List接口了啊,Vector、ArrayList、LinkedList作为AbstractList的子孙类,完全没有必要再显示的写implements List啊,即使有不同于AbstractList的实现方式,直接重写就行了啊。难道只是一种规范,增加代码可读性,或者防止以后AbstractList不实现List了,Vector、ArrayList、LinkedList根本不用去修改,降低维护成本)
Vector、ArrayList底层都是由object数组实现,Stack是Vector的子类,底层也是数组;LinkedList由双向链表实现,所以它的核心是组成链表的Node节点。
一、既然都是由数组实
f199
现的,那就先来比较Vector和ArrayList吧:
1. 类声明
public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
这是这两个类的声明,是一样的,都继承了AbstractList,并实现了List、RandomAccess、 Cloneable、Serializable接口,说明了Vector和ArrayList都是可以快速随机访问、可克隆、可以被序列化和反序列化的,容量大小可变的集合。(网上有人说List是有序可重复的,我觉得这种说法还是有点问题的,你不能说一个接口具有怎么样的特性,如果能通过一些抽象方法就看出接口具有什么特点,也是厉害。那么为什么会说List接口是有序,可重复的呢?因为在Java里,实现了List接口的实现类,比如ArrayList、LinkedList都表现出了这个特点,数据是按录入的顺序存放的,并且可以重复)RandomAccess接口虽然只是一个标记接口,里面什么方法都没有定义,但它就是表明Vector和ArrayList具有随机访问性,毕竟底层是用数组实现的,可以根据数组下标直接找到元素。
上面我们说了Vector和ArrayList都是用数组实现的,看一下这两个类的成员变量
Vector: protected Object[] elementData; ArrayList: transient Object[] elementData;
Vector的elementData是用protected修饰的,而ArrayList的elementData是用transient修饰的,那不就是说ArrayList中的数据元素不会被序列化?对象序列化确切地说有三种方式
只是实现Serializable接口,那么在序列化时,就会自动调用java.io.ObjectOutputStream的defaultWriteObject()方法进行序列化,这时用transient修饰的成员变量是不会被序列化的
实现Serializable接口,重写writeObject()方法,这时用transient修饰的成员变量(除静态变量)能不能被序列化,就取决于重写的writeObject()方法了
实现Externalizable接口,Externalizable继承了Serializable接口,并增加了两个方法:writeExternal(ObjectOutput out) 和readExternal(ObjectInput in) 。在writeExternal方法里定义了哪些属性可以序列化,哪些不可以被序列化
很明显,ArrayList采用的是第二种方式
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{ // Write out element count, and any hidden stuff int expectedModCount = modCount; s.defaultWriteObject(); // Write out size as capacity for behavioural compatibility with clone() s.writeInt(size); // Write out all elements in the proper order. for (int i=0; i<size; i++) { s.writeObject(elementData[i]); } if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } }
它先调用defaultWriteObject方法,此时用transient修饰的成员变量还没有被序列化,但是它后来把数组里的元素一个一个遍历并序列化,也就是说ArrayList中用transient修饰的elementData数组是被序列化的。那ArrayList花这么大的功夫来“多此一举”,为什么不直接把elementData的修饰符transient去掉呢?仔细想想还是有点用的,因为elementData数组在初始化或者扩容时,允许有空元素,如果在序列化时不加控制,就会把空元素也序列化,ArrayList这样做,在序列化时,不会把空元素也序列化,它能保证序列化的元素都是有用的,也就是下标从0到size-1的元素。
2. 自动扩容
在构造Vector和ArrayList时,如果不显示给容量赋值,那么它们的默认容量都为10。当容量不够时,它们都会自动扩容,那么扩容的大小是怎么样的呢
ArrayList: 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); } Vector: private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copyOf(elementData, newCapacity); }
在代码中可以看到,对于ArrayList,int newCapacity = oldCapacity + (oldCapacity >> 1); 也就是说,新容量=旧容量*1.5; 对于Vector, int newCapacity = oldCapacity + ((capacityIncrement > 0) ?capacityIncrement : oldCapacity); 这里涉及到一个成员变量capacityIncrement,它表示Vector每次扩容,增加的数值,在构造Vector时就可以显示给它赋值,如果没有给它赋值,默认为0。所以当capacityIncrement>0时,新容量 = 旧容量+capacityIncrement;当capacityIncrement<=0时,新容量 = 旧容量*2 。
Vector和ArrayList都有一个ensureCapacity(int minCapacity)方法,它最后都会调用grow(int minCapacity)方法,如果不看API或者源码,很可能会被它误导,以为它是按照minCapacity参数扩容。比如ArrayList,默认容量为10,当调用ensureCapacity(11)后,实际容量并不是11,而是15。在API中,这个方法表示:如果需要,增加此向量的容量,以确保它可以至少保存最小容量参数指定的组件数。至于调用后实际容量的值就根据源码计算吧
3. 同步
Vector使用synchronized来保证线程安全(同步),并且是读写都加了synchronized。ArrayList则是非线程安全的(不同步),所以在效率上ArrayList要优于Vector。
4. 遍历
ArrayList支持用迭代器Iterator和foreach/for去遍历,而Vector除了这两种方式,还支持Enumeration去遍历
Vector: public Enumeration<E> elements() { return new Enumeration<E>() { int count = 0; public boolean hasMoreElements() { return count < elementCount; } public E nextElement() { synchronized (Vector.this) { if (count < elementCount) { return elementData(count++); } } throw new NoSuchElementException("Vector Enumeration"); } }; }
5. 其他方面
Vector和ArrayList在其他方面都差不多,添加、删除元素等方法都很类似。但是,对于删除元素,虽然Vector的坑比ArrayList要少,但还是会一不小心就出错,具体可以看我的另一篇博客那些年,我们在Java ArrayList Remove方法遇到的坑
二、Stack与Vector
Stack和Vector是父子关系,Vector是父,Stack是子,所以Stack也是用数组实现的,确切地说,是一个用数组实现的栈,满足先进后出原则(FILO),入栈和出栈都是在数组下标为size-1的地方。
三、链表与数组
上面说了用数组实现的集合,现在可以来说说用链表实现的集合了,代表就是LinkedList了。
1. 底层数据结构
LinkedList的底层数据结构是节点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; } }
每个节点由三部分所组成,分别是前节点(引用地址)、后节点(引用地址)以及本节点所包含的实体数据。
我们先来看一下LinkedList到底是一个怎么样的链表,它的成员变量由三个:
transient int size = 0; //链表实际存有数据的节点个数(允许数据为空) transient Node<E> first; //指向链表第一个节点 transient Node<E> last; //指向链表最后一个节点
回想一下,为LinkedList添加元素的语句:
List< String> list = new LinkedList<>();
list.add(“abc”);
好,我们就从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++; }
代码很简单,就不详细说明了,到这里我们可以知道LinkedList是一个双向链表,大致如下图(原谅我又盗图了,其实在看linkLast代码的时候,让我想起了以前我学数据结构的时候,那时候还是用C++写的。时间过的很快,转眼大学四年就要过去了,马上也要实习了,还是比较伤感的)
2. 类声明
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable
AbstractSequentialList是一个抽象类,它继承于AbstrctList。实现了链表中,根据index索引值操作链表的全部方法。
它没有实现RandomAccess接口,说明不具备快速随机访问
实现了Deque接口(通常可以被用来当作栈、队列使用)
3. LinkedList与ArrayList的区别
底层实现:ArrayList由数组实现;LinkedList由链表实现
随机访问:ArrayList随机访问速度快,LinkedList访问速度慢
ArrayList: public E get(int index) { rangeCheck(index); return elementData(index); } E elementData(int index) { return (E) elementData[index]; } LinkedList public E get(int index) { checkElementIndex(index); return node(index).item; } Node<E> node(int 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速度快。
删除、插入元素:这个还真不好说
//只说明插入元素,删除元素原理相同 ArrayList: 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: public void add(int index, E element) { checkPositionIndex(index); if (index == size) linkLast(element); else linkBefore(element, node(index)); } Node<E> node(int 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在插入、删除元素方面,要优于ArrayList的,因为ArrayList要移动数据。但是看了源码之后,我就不这么认为了。每次插入(删除)元素,ArrayList的部分元素都要向前或向后移动,而LinkedList只要直接插入(删除)即可,但是在通过下标找节点时要花费一定时间,那就要看是遍历链表的代价大,还是移动数据的代价大了。所以这应该要看你集合中原本的数据量,以及插入(删除)元素的位置了,至于到底是怎么样的结果,因为懒就不做实验了,哈哈。网上有一个实验的结果是这样的:如果待插入、删除的元素是在数据结构的前半段尤其是非常靠前的位置的时候,LinkedList的效率将大大快过ArrayList,因为ArrayList将批量copy大量的元素;越往后,对于LinkedList来说,优势就不是很明显了,需要寻址的时间越来越长,ArrayList要批量copy的元素越来越少,操作速度必然追上乃至超过LinkedList。
还是看情况吧,比如都是remove(Object o)方法,就不止是LinkedList要遍历整个容器,找到相应的元素了,ArrayList也需要在整个容器中遍历,找到需要删除的元素,这样,就明显要选择LinkedList,而不是ArrayList了,因为LinkedList在删除后不需要移动数据,而且顺序遍历,链表是要快于数组的;还有就是,如果是顺序插入,并且数据量也知道,那肯定是选择ArrayList,因为ArrayList不存在移动数据和自动扩容造成的时间、空间浪费问题,反而是LinkedList每次插入数据,在定义节点时会浪费时间和内存。
有一种情况特别要注意,在插入元素的时候,ArrayList可能会扩容,这是一个耗费时间和空间的事情,因为数组在堆上需要一块很大的连续的空间,如果当时连续的空间不能满足扩容要求,就会发生gc,那代价就更高了,所以一般来说,在数据量不知道,需要经常插入操作的,还是选择LinkedList吧。
同步:都是不同步的(非线程安全)
顺序迭代遍历:LinkedList比ArrayList要快,毕竟是链表
空间浪费:ArrayList的空间浪费主要体现在自动扩容后,有一部分多余空间没有被利用(可以在ArrayList分配完空间后使用trimToSize来去掉浪费的空间),而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间
相关文章推荐
- 浅析Java中的集合包(ArrayList,LinkedList,Vector, Stack,HashSet,TreeSet,HashMap,TreeMap)
- Java 集合之List、ArrayList、LinkedList以及Vector详解
- Java 集合:Collection,List,ArrayList,Vector,LinkedList(实现方式,对比)
- java基础之集合List-ArrayList、LinkedList、Vector的差别
- Java--集合体系(ArrayList、Vector、LinkedList)
- Java基础 集合框架 共性方法 迭代器 ArrayList LinkedList Vector HashSet TreeSet
- Java基础知识_集合(ArrayList & LinkedList & Vector & 迭代器)
- JAVA集合之---LinkedList、ArrayList与Vector
- Java 集合:Collection,List,ArrayList,Vector,LinkedList(实现方式,对比)
- JAVA学习---集合系列---ArrayList、Vector和LinkedList等的差别
- Java 集合:Collection,List,ArrayList,Vector,LinkedList(实现方式,对比)
- Java基础之集合List-ArrayList、LinkedList、Vector的底层实现和区别
- Java集合之ArrayList,LinkedList,Vector
- Java 集合系列之 List总结(LinkedList, ArrayLis,vector,stack等使用场景和性能分析)
- Java 集合:Collection,List,ArrayList,Vector,LinkedList(实现方式,对比)
- Java 集合 1:Collection,List,ArrayList,Vector,LinkedList(实现方式,对比)
- JAVA中的数据结构——集合类(线性表:Vector、Stack、LinkedList、set接口;键值对:Hashtable、Map接口<HashMap类、TreeMap类>)
- Java 集合:Collection,List,ArrayList,Vector,LinkedList(实现方式,对比)
- java基础之集合List-ArrayList、LinkedList、Vector的区别
- Java 集合深入学习--ArrayList,LinkedList和Vector