前言
List:一个有序(元素存入集合的顺序和取出的顺序一致)容器,元素可以重复,可以插入多个null元素,元素都有索引,常见的实现类有ArrayLust、LinkedList、Vector(线程安全)。
ArrayList和LinkedList作为两款单线程环境下常用的List集合(线程不安全),有必要对它们进行较为深入的了解与区分。
一、底层数据结构
- ArrayList
ArrayList底层数据结构是动态数组
其源码如下:
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable {
private static final long serialVersionUID = 8683452581122892189L;
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = new Object[0];
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0];
transient Object[] elementData;
private int size;
private static final int MAX_ARRAY_SIZE = 2147483639;
- LinkedList
LinkedList底层数据结构是双向链表
其源码如下:
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, Serializable {
transient int size;
transient LinkedList.Node<E> first;
transient LinkedList.Node<E> last;
private static final long serialVersionUID = 876323262645176354L;
- 测试:
数组访问快,增删慢;链表访问慢,增删快。下面具体测试下
import java.util.ArrayList;
import java.util.LinkedList;
public class ListDemo {
public static void main(String[] args) {
ArrayList<Integer> arrayList=new ArrayList<Integer>();
LinkedList<Integer> linkedList=new LinkedList<Integer>();
Long ArrayAddStart=System.currentTimeMillis();
for (int i = 0; i <50000 ; i++) {
arrayList.add(i);
}
Long ArrayAddEnd=System.currentTimeMillis();
Long LinkedAddStart=System.currentTimeMillis();
for (int i = 0; i <50000 ; i++) {
linkedList.add(i);
}
Long LinkedAddEnd=System.currentTimeMillis();
System.out.println("ArrayList添加50000个数据时间:"+(ArrayAddEnd-ArrayAddStart)+"ms");
System.out.println("LinkedList添加50000个数据时间:"+(LinkedAddEnd-LinkedAddStart)+"ms");
Long ArrayGetStart=System.currentTimeMillis();
for (int i = 0; i <50000 ; i++) {
arrayList.get(i);
}
Long ArrayGetEnd=System.currentTimeMillis();
Long LinkedGetStart=System.currentTimeMillis();
for (int i = 0; i <50000 ; i++) {
linkedList.get(i);
}
Long LinkedGetEnd=System.currentTimeMillis();
System.out.println("ArrayList访问50000个数据时间:"+(ArrayGetEnd-ArrayGetStart)+"ms");
System.out.println("LinkedList访问50000个数据时间:"+(LinkedGetEnd-LinkedGetStart)+"ms");
}
}
/*
程序运行结果:
ArrayList添加50000个数据时间:10ms
LinkedList添加50000个数据时间:0ms
ArrayList访问50000个数据时间:10ms
LinkedList访问50000个数据时间:901ms
*/
二、扩容机制
- ArrayList
- 初始化
/* ArrayList有三个构造方法 第一个构造方法是规定其初始化数组长度,如果传参大于0,则构造传参大小的elementData数组;如果传参小于0,抛异常;如果传参等于0,将默认的EMPTY_ELEMENTDATA数组传给elementData数组 第二个构造方法是无参构造,将默认的DEFAULTCAPACITY_EMPTY_ELEMENTDATA数组传给elementData数组 第三个构造方法是传入另一个集合,如果传入集合长度大于0,则调用Arrays.copyOf()方法将传入集合浅拷贝给elementData数组;如果传入集合长度等于0,将默认的EMPTY_ELEMENTDATA数组传给elementData数组 */ public ArrayList(int initialCapacity) { if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else { if (initialCapacity != 0) { throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity); } this.elementData = EMPTY_ELEMENTDATA; } } public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } public ArrayList(Collection<? extends E> c) { this.elementData = c.toArray(); if ((this.size = this.elementData.length) != 0) { if (this.elementData.getClass() != Object[].class) { this.elementData = Arrays.copyOf(this.elementData, this.size, Object[].class); } } else { this.elementData = EMPTY_ELEMENTDATA; } }
- 扩容
/* 1:add(E e):调用this.add(e, this.elementData, this.size)方法 2:add(E e, Object[] elementData, int s):如果当前ArrayList的size(即实际ArrayList中元素个数)和elementData.length(即实际ArrayList中一共有多少位置可以放元素)相等,则需要调用this.grow()方法给elementData动态扩容,之后在进行赋值和size++操作;如果长度不相等则直接进行赋值和size++操作 3:private Object[] grow():调用重载方法this.grow(this.size + 1),将当前集合所需要的最小容量作为传参 4:private Object[] grow(int minCapacity):调用Arrays.copyOf(this.elementData, this.newCapacity(minCapacity))方法给elementData进行扩容,关键是this.newCapacity(minCapacity)方法获得新的长度。 5:private int newCapacity(int minCapacity): 首先newCapacity = oldCapacity + (oldCapacity >> 1),进行1.5倍的容量扩容; 接着进行判断: (1):newCapacity<=minCapacity:则判断当前elementData是否是DEFAULTCAPACITY_EMPTY_ELEMENTDATA(即是否是无参构造出来的),如果是则return Math.max(10, minCapacity)(这就是无参构造时ArrayList初始容量为10的原因),如果minCapacity<0,直接抛出错误,否则返回minCapacity。这里之所以会出现对minCapacity小于0的判断,是因为当this.size=2147483647时,this.size+1=-2147483648(溢出) (2):newCapacity>minCapacity:如果1.5倍扩容后的newCapacity<=2147483639,则返回newCapacity,否则返回2147483647(所以ArrayList实际上的最大容量为2147483639)。 最后依次返回到private Object[] grow()方法,elementData数组长度就扩容完成。 */ public boolean add(E e) { ++this.modCount; this.add(e, this.elementData, this.size); return true; } private void add(E e, Object[] elementData, int s) { if (s == elementData.length) { elementData = this.grow(); } elementData[s] = e; this.size = s + 1; } private Object[] grow(int minCapacity) { return this.elementData = Arrays.copyOf(this.elementData, this.newCapacity(minCapacity)); } private Object[] grow() { return this.grow(this.size + 1); } private int newCapacity(int minCapacity) { int oldCapacity = this.elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity <= 0) { if (this.elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(10, minCapacity); } else if (minCapacity < 0) { throw new OutOfMemoryError(); } else { return minCapacity; } } else { return newCapacity - 2147483639 <= 0 ? newCapacity : hugeCapacity(minCapacity); } } private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) { throw new OutOfMemoryError(); } else { return minCapacity > 2147483639 ? 2147483647 : 2147483639; } }
- LinkedList
- 初始化
/* LinkedList只有两个构造方法 第一个构造方法是无参构造,初始化当前 LinkedList大小为0 第二个构造方法是传入另外一个集合,将其赋给LinkedList 注意:linkedList=new LinkedList<Integer>();()内不能填数字,因为LinkedList没有含有初始大小的构造方法!!! */ public LinkedList() { this.size = 0; } public LinkedList(Collection<? extends E> c) { this(); this.addAll(c); }
- 扩容
因为LinkedList底层是链表不是数组,增加元素时直接在链表末尾添加就行,因此不需要扩容。
三、常用方法
- List(ArrayList和LinkedList共有)
- public int size()返:回此列表中的元素数。
- public boolean contains(Object o):如果此列表包含指定的元素,则返回true。
- public int indexOf(Object o):返回此列表中指定元素的第一次出现的索引,如果此列表不包含元素,则返回-1。
- public int lastIndexOf(Object o):返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1。
- public E get(int index):返回此列表中指定位置的元素。
- public E set(int index, E element):用指定的元素替换此列表中指定位置的元素。
- public boolean add(E e):将指定的元素追加到此列表的末尾。
- public void add(int index,E element):在此列表中的指定位置插入指定的元素。
- public E remove(int index):删除该列表中指定位置的元素。 将任何后续元素移动到左侧(从其索引中减去一个元素)。
- public boolean remove(Object o):从列表中删除指定元素的第一个出现(如果存在)。
- public void clear()从列表中删除所有元素。 此呼叫返回后,列表将为空。
- public Object[] toArray():以正确的顺序(从第一个到最后一个元素)返回一个包含此列表中所有元素的数组。注意该方法返回的数组可以强制转换为基本类型的包装类数组,但不可以转换成基本类型的数组。
- public Object clone():返回此ArrayList实例的浅拷贝。即拷贝的集合和原集合地址不同。
- public Iterator iterator()以正确的顺序返回该列表中的元素的迭代器
- public void sort(Comparator<? super E> c) : List使用提供的Comp-arator对此列表进行排序,以比较元素。 底层为优化的快速排序,效率高)。
- LinkedList(LinkedList独有)
- public boolean offer(E e): 将指定的元素添加为此列表的尾部(最后一个元素)。
- public E peek():检索但不删除此列表的头(第一个元素)。
- public E poll()检索并删除此列表的头(第一个元素)。
- public E remove():检索并删除此列表的头(第一个元素)。
- 因为LinkedList是由双向链表实现的,因此它的add方法有对应的public void addFirst(E e),public void addLast(E e)方法,同样地,offer,peek,poll,remove方法也有对应的方法。
- remove()方法和poll()方法的区别是,如果此列表为空,remove()抛NoSuchElementException异常,而poll()返回null。
- public E pop():从此列表表示的堆栈中弹出一个元素。 换句话说,删除并返回此列表的第一个元素。 此方法相当于removeFirst() 。
- public void push(E e)将元素推送到由此列表表示的堆栈上。 换句话说,在该列表的前面插入元素。 此方法相当于addFirst(E) 。
四、对比
- 数据结构: ArrayList是动态数组数据结构实现;LinkedList是双向链表数据结构实现
- 访问效率: ArrayList比LinkedList的访问效率要高
- 增删效率: 在非首尾的增加和删除操作中,LinkedList比ArrayList效率要高
- 内存空间:LinkedList比ArrayList效率要高,因为LinkedList的节点处理存储数据还存储了两个引用(指向前后元素)
五、小结
在需要频繁读取集合中的元素时,推荐使用ArrayList,而在插入和删除操作较多时,推荐使用LinkedList