JDK源碼學習(3)-java.util.ArrayList與LinkedList

一、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


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章