ArrayList和LinkList是經常使用的存儲結構,不看源碼先分析字面意思,Array意思是數組,可知其底層是用數組實現的,Link意思是連接,可知是以鏈表實現,這兩種數據結構各有什麼特色呢?在實際開發中,咱們要如何選擇?java
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{ private static final long serialVersionUID = 8683452581122892189L; /** * 默認初始容量10 */ private static final int DEFAULT_CAPACITY = 10; /** * 空實例的空數組 */ private static final Object[] EMPTY_ELEMENTDATA = {}; /** * 默認空實例的空數組 */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** * 存儲元素的數組,它的大小就是ArrayList的容量,不用序列化 */ transient Object[] elementData; // non-private to simplify nested class access /** * ArrayList元素的大小 */ private int size; }
/** * 構造空的list,並指定初始容量 * * @param initialCapacity 初始容量 * @throws IllegalArgumentException 若是初始容量爲負數 */ public ArrayList(int initialCapacity) { if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } /** * 構造空的list,並指定初始容量10 */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } /** * 構造list,包含指定集合的全部元素 * * @param c 指定的集合 * @throws NullPointerException 若是指定集合爲null */ public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0) { // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // replace with empty array. this.elementData = EMPTY_ELEMENTDATA; } }
ArrayList list = new ArrayList(),默認容量爲10,若是添加超過10個元素,就會形成list的擴容,內部會建立一個新的數組,對效率會有影響,若是有大量的元素添加,那麼list就會頻繁擴容,效率低下.所以在開發中能夠指定初始容量,細節之中能夠看出一我的的基本功.數組
/** * 返回list元素的數量 */ public int size() { return size; } /** * 判斷list是否包含元素,返回true或false */ public boolean isEmpty() { return size == 0; } /** * 判斷是夠包含指定的元素,返回true或false */ public boolean contains(Object o) { return indexOf(o) >= 0; } /** * 返回元素第一次出現的索引index * 若是list沒有這個元素,返回-1,不然返回第一次出現的index * 注意:也能夠查詢null的索引 */ public int indexOf(Object o) { if (o == null) { for (int i = 0; i < size; i++) if (elementData[i]==null) return i; } else { for (int i = 0; i < size; i++) if (o.equals(elementData[i])) return i; } return -1; } /** * 返回元素最後一次出現的索引index * 若是list沒有這個元素,返回-1,不然返回最後一次出現的index * 注意:也能夠查詢null的索引 */ public int lastIndexOf(Object o) { if (o == null) { for (int i = size-1; i >= 0; i--) if (elementData[i]==null) return i; } else { for (int i = size-1; i >= 0; i--) if (o.equals(elementData[i])) return i; } return -1; } /** * 返回指定索引的元素 * @param index 索引 * @return 元素 * @throws IndexOutOfBoundsException, 若是index超出數組的範圍 */ public E get(int index) { rangeCheck(index); return elementData(index); } /** * 替換指定索引的元素 * * @param index 索引 * @param element 新元素 * @return 老元素 * @throws IndexOutOfBoundsException */ public E set(int index, E element) { rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; return oldValue; }
下面重點介紹add()和remove()方法,元素的新增和刪除內部是如何實現的呢?安全
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
ArrayList新增元素時,也就是在list的尾部添加一個元素,首先修改元素的數量+1數據結構
ensureCapacityInternal(size+1),即list的size+1,元素數量加1,詳細實現以下:dom
private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); }元素的數量+1後,判斷minCapacity有沒有超出當前的容量,若是超出了,就要進行擴容操做grow()源碼分析
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); }其邏輯以下:新容量 = 老容量 + 老容量右移1位(即除以2),也就是大約1.5倍的老容量,爲何說大約呢?由於若是老容量是偶數,那麼新容量正好等於1.5倍老容量,若是老容量是奇數11,那麼新容量是15.
容量在大,也是有限制的,最大MAX_ARRAYSIZE = Integer.MAXVALUE - 8,有21億,估計沒有人會放這麼多的數據吧. 性能elementData[size++] = e, 而後把新元素e放在新索引的位置,也就是數組的尾部. this
ArrayList刪除元素有兩種,一種是根據索引刪除,二是直接刪除對象 線程
根據索引刪除對象 3d
public E remove(int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work return oldValue; }
numMoved = 4 - 2- 1 = 1;
System.arraycopy(elementData, 3, elementData, 2,1);
流程圖以下:
本質上是把要刪除的元素替換爲它後面的元素,而後把最後一個元素賦值爲null,手動GC,返回老的元素.
直接刪除對象
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; }
就是循環遍歷數組,當元素第一次出現的時候,刪除這個元素,同時返回true,若是元素不存在,那麼數組不改變,同時返回false.
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable{ transient int size = 0; /** * 第一個節點,不用序列化 */ transient Node<E> first; /** * 最後一個節點,不用序列化 */ transient Node<E> last; }
/** * 構造空的list */ public LinkedList() { } /** * 構造指定集合的list * @param c 集合 * @throws NullPointerException 若是集合爲null */ public LinkedList(Collection<? extends E> c) { this(); addAll(c); }
添加元素add()
/** * 在LinkList的尾部添加元素 * 這個方法至關於addLast * 返回true/false */ public boolean add(E e) { linkLast(e); return true; }
linkLast的具體實現以下:
void linkLast(E e) { //l賦值爲last節點 final Node<E> l = last; //建立新的節點e,前面元素是l,後面是null final Node<E> newNode = new Node<>(l, e, null); //把新的節點標記爲last節點 last = newNode; //判斷是否是第一個節點 //若是是,新的節點就是第一個節點 if (l == null) first = newNode; else //若是不是,以前的最後一個節點後面是新的節點 l.next = newNode; size++; modCount++; }
刪除元素remove()
public boolean remove(Object o) { if (o == null) { for (Node<E> x = first; x != null; x = x.next) { if (x.item == null) { unlink(x); return true; } } } else { for (Node<E> x = first; x != null; x = x.next) { if (o.equals(x.item)) { unlink(x); return true; } } } return false; }
從頭部遍歷全部的節點,若是當前節點元素與要刪除的節點元素相同,那麼移除當前節點,若是LinkList包含多個要刪除的元素,那麼只會刪除index較小的那個節點.