共同点:
两者都实现了List
public List<E> subList(int fromIndex, int toIndex) { return new SubList<E>(this, fromIndex, toIndex); }
不同点:
1.数据结构不同,ArrayList采用数组的形式存储,其有两个构造方法,开发者可以指定数组大小,默认构造函数大小为10
public ArrayList(int initialCapacity) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity]; }
public ArrayList() { this(10); }个人建议初始化时最好能预判判断存储的大小,使用第一种方法初始化,避免空间浪费和扩容造成效率低下,如果容量不够,新增数据时会进行扩容,新建一个数组存放数据;扩容大小为原大小的一半加1,具体实现如下:
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); } }
2. LinkedList采用链表存储数据,jdk1.6中采用双向循环链表存放,头结点为null,不存放数据,其节点为Entry
private transient Entry<E> header = new Entry<E>(null, null, null); private transient int size = 0; /** * Constructs an empty list. */ public LinkedList() { header.next = header.previous = header; }
jdk1.7采用不带头结点的普通链表存储,具体的实现感兴趣可以看一下源码
3.两者的数据结构不同导致了不同操作的效率不同,接下来基于常用方法分析一下两者效率:
a.对于基本的add(E e)操作,两者效率相同,前提是ArrayList不需要扩容情况下
b.对于指定位置添加add(int index,E e)、romove(int index)操作,ArrayList需要进行数组的内容移动,而LinkedList只需要在指定位置添加一下新节点,变换一下指针即可,具体实现如下:
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++; }
LinkedList源码
public boolean add(E e) { addBefore(e, header); return true; }
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; }
c.对于remove(Object o),二者都需要遍历进行查询,ArrayList查找到删除完节点后,还需要将其后的节点进行移动,LinkedList只需要改变一下指针指示即可,具体实现如下:
public boolean remove(Object o) { if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; }
private void fastRemove(int index) { modCount++; int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // Let gc do its work }
LikedList源码
public boolean remove(Object o) { if (o==null) { for (Entry<E> e = header.next; e != header; e = e.next) { if (e.element==null) { remove(e); return true; } } } else { for (Entry<E> e = header.next; e != header; e = e.next) { if (o.equals(e.element)) { remove(e); return true; } } } return false; }
private E remove(Entry<E> e) { if (e == header) throw new NoSuchElementException(); E result = e.element; e.previous.next = e.next; e.next.previous = e.previous; e.next = e.previous = null; e.element = null; size--; modCount++; return result; }
总结:
1. 对于顺序添加读取,建议使用ArrayList且提前初始化好大小
2. 对于指定位置添加和删除,建议使用LinkedList,特别是位置比较靠前的新增和删除
3. 二者都是线程不安全的
4. subList方法的使用注意序列化问题,此处有坑