一、java.util.ArrayList的數據結構爲數組結構,爲可序列化類型、實現了List接口,類聲明方式:
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
JDK8中該類在初始化時的容量爲0,在add時會將容量擴充到10,當容量較少時會進行擴容。
1.擴容邏輯
代碼如下:
初始化:
private static final int DEFAULT_CAPACITY = 10; private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //構造函數 public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } //添加數據 public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } //擴容邏輯 private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); } 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); }
可以看到,在方法ensureCapacityInternal中,選取當前size+1與默認容量10中較大的值minCapaccity,如果該值比當前容量小,則不進行擴容;如果已經大於當前容量,則進行擴容:根據當前容量oldCapacity,擴展oldCapacity的一半,並保證可以滿足當前使用,否則直接使用minCapaccity的值。
2.equals
代碼如下:
public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof List)) return false; ListIterator<E> e1 = listIterator(); ListIterator<?> e2 = ((List<?>) o).listIterator(); while (e1.hasNext() && e2.hasNext()) { E o1 = e1.next(); Object o2 = e2.next(); if (!(o1==null ? o2==null : o1.equals(o2))) return false; } return !(e1.hasNext() || e2.hasNext()); }
比較兩個list對象中的所有元素的順序、取值是否完全一致。
3.contains和indexOf
代碼如下:
//查看是否包含某元素 public boolean contains(Object o) { return indexOf(o) >= 0; } //查看某元素在list中的第一個的位置 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; } //查看某元素在list中的最後一個的位置 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; }
indexOf(Object o)的主要作用爲:遍歷arrayList中的所有元素,並返回該元素所在list中的第一個位置。如果未找到則返回-1。
4.removeRange刪除list中某一範圍的元素
protected void removeRange(int fromIndex, int toIndex) { modCount++; int numMoved = size - toIndex; System.arraycopy(elementData, toIndex, elementData, fromIndex, numMoved); // clear to let GC do its work int newSize = size - (toIndex-fromIndex); for (int i = newSize; i < size; i++) { elementData[i] = null; } size = newSize; }
該方法主要是從toIndex的位置開始到最後一位的數據複製到fromIndex起始的位置,然後將最後幾位的位置數據設置爲null。
其中System.arraycopy爲系統方法,爲數據拷貝的方法。elementDate爲需要複製的原始數組;toIndex爲原始數組的起始位置;elementDate爲目標數組;fromIndex爲目標數組的起始位置;numMoved爲拷貝的數組數據的長度。
二、java.util.LinkedList
結構:該list爲鏈表結構,包含first節點、last節點、size值。first節點前置節點爲null,last節點後繼節點爲null,size初始值爲0。
節點結構:
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; } }
即每個節點有個一個前置節點、有一個後繼節點、有一個節點值。
1.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++; }
linkLast方法:首先聲明兩個節點,一個節點l指向list最後一個節點(last);另一個節點newNode指向一個新節點,該節點前置節點指向l,後繼節點指向null,節點值爲想要插入的值。
然後,如果list爲空,則list的首節點first指向newNode節點;如果list不爲空,則l指向newNode,並且將list最後一個節點的後續指向newNode。並且將newNode賦值給last節點。
最後,將size加1,modCount加1。
具體modCount作用:
主要是爲了檢查是否有多個線程同時改變list的內容,可以查看博文的講解:http://www.cnblogs.com/dolphin0520/p/3933551.html
2.contains和indexOf
public boolean contains(Object o) { return indexOf(o) != -1; }
public int indexOf(Object o) { int index = 0; if (o == null) { for (Node<E> x = first; x != null; x = x.next) { if (x.item == null) return index; index++; } } else { for (Node<E> x = first; x != null; x = x.next) { if (o.equals(x.item)) return index; index++; } } return -1; }
indexOf主要是查找list中的某節點值:
1)遍歷list值。從first節點開始到節點不爲null爲止(last節點的後繼節點爲null),每次遍歷節點的後繼節點。
2)如果查找null節點,則直接使用“==”進行比較;如果查找非null節點,則使用equals()方法。
3)返回值爲該節點在list中的位置值index,從0開始,每遍歷一個節點加1,指導找到匹配的值位置,並返回index。如果未找到則返回-1;
contains方法主要是查看list中是否包含某個節點:
通過indexOf方法查找節點,找到(即indexOf方法結果不爲-1)則返回邏輯判斷結果true(boolean類型),否則返回false。
3.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; }
remove通過遍歷list,從first到last節點如果查找到匹配節點,則調用unlink方法進行釋放當前節點。
具體方法如下:
E unlink(Node<E> x) { // assert x != null; final E element = x.item; final Node<E> next = x.next; final Node<E> prev = x.prev; if (prev == null) { first = next; } else { prev.next = next; x.prev = null; } if (next == null) { last = prev; } else { next.prev = prev; x.next = null; } x.item = null; size--; modCount++; return element; }
該方法主要功能爲:
1)如果該節點前置節點爲空,則該節點爲first首節點,即將first首節點指向該節點的後續節點;
如果前置節點不爲空,則將前置節點的後續指向該節點的後續節點,並將該節點的前置節點置爲空,相當於斷掉該節點前面的鏈條。
2)繼續判斷後繼節點,如果爲空,則該節點爲last尾節點,即將last爲節點指向該節點的前置節點;
如果後繼節點不爲空,則將後續節點的前置節點指向該節點的前置節點,並將該節點的後繼節點值爲空,相當於斷掉該節點後面的鏈條。
3)該節點的前後鏈條已經與list斷掉,但是需要釋放掉該節點的空間,並且需要將list的長度size減1,修改modCount的值加1。
4)返回值爲節點的內容。
java.util.ArrayList與LinkedList兩種類型的區別:
1.ArrayList結構爲數組結構,LinkedList爲鏈表結構。
2.ArrayList由於根據index位數查找字段時可以直接取出數組的位置,而LinkedList則需要遍歷到指定位置纔可以取出節點值,因此對於根據位置查找頻繁的項目建議使用ArrayList。但是根據值查詢兩者相差不大。
3.ArrayList刪除時需要將後面所有位數的數據全部前移,而LinkedList刪除某一節點只需要釋放該節點。因此ArrayList速度較慢,對於由頻繁插入和刪除操作的需求,建議使用LinkedList。
但是由於linkedList在刪除或者插入時需要定位到該數據,因此也會對效率有影響,因此具體問題也許要綜合分析。
樣例如下:
List<String> sList = new ArrayList<String>(); for (int i = 0; i < 10000000; i++) { sList.add(String.valueOf(i)); } long begina1 = System.currentTimeMillis(); System.out.println("根據位置查詢arrayList的值==" + sList.get(999999)); long enda1 = System.currentTimeMillis(); System.out.println("根據位置查詢arrayList時間==" + (enda1 - begina1)); long begina2 = System.currentTimeMillis(); System.out.println("根據值查詢arrayList的位置==" + sList.indexOf("999999")); long enda2 = System.currentTimeMillis(); System.out.println("根據值查詢arrayList時間==" + (enda2 - begina2)); long begina3 = System.currentTimeMillis(); sList.remove(0); long enda3 = System.currentTimeMillis(); System.out.println("刪除arrayList中數據使用的時間==" + (enda3 - begina3)); List<String> linkList = new LinkedList<String>(); for (int i = 0; i < 10000000; i++) { linkList.add(String.valueOf(i)); } long beginb1 = System.currentTimeMillis(); System.out.println("根據位置查詢linkList的值==" + linkList.get(999999)); long endb1 = System.currentTimeMillis(); System.out.println("根據位置查詢linkList的時間==" + (endb1 - beginb1)); long beginb2 = System.currentTimeMillis(); System.out.println("根據值查詢linkList的值==" + linkList.indexOf("999999")); long endb2 = System.currentTimeMillis(); System.out.println("根據值查詢linkList的時間==" + (endb2 - beginb2)); long beginb3 = System.currentTimeMillis(); linkList.remove(0); long endb3 = System.currentTimeMillis(); System.out.println("刪除linkList中數據使用的時間==" + (endb3 - beginb3));
結果爲,數字爲毫秒數:
根據位置查詢arrayList的值==999999 根據位置查詢arrayList時間==0 根據值查詢arrayList的位置==999999 根據值查詢arrayList時間==16 刪除arrayList中數據使用的時間==6 根據位置查詢linkList的值==999999 根據位置查詢linkList的時間==12 根據值查詢linkList的值==999999 根據值查詢linkList的時間==17 刪除linkList中數據使用的時間==0