jdk學習筆記

1、ArrayList分析

一、基類

public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializablejava

能夠總結爲: ArrayList 繼承了AbstractList  實現了 List 、 RondomAcess 、 cloneable 、 serializable 對於通常的容器都會實現cloneable 用於克隆 實現serializable 實現序列化node

二、變量

主要有4個變量,在瞭解這些變量以前咱們須要知道一些基本信息,ArrayList 底層用的是數組實現的,初始的默認容量是10 ,數組

private static final int DEFAULT_CAPACITY = 10;//初始的默認容量安全

private static final Object[] EMPTY_ELEMENTDATA = {}; //空數組dom

private transient Object[] elementData;//ArrayList 中真正存數據的容器ide

private int size;//當前數組中的數據個數函數

三、構造函數

ArrayList中總共有三個構造函數,分別是有初始化容量的構造函數,沒有參數的構造函數,帶有集合參數的構造函數ui

(1) 從源碼中咱們能夠看到這只是一個很簡單的 建立數組對象的過程,不過在建立對象以前須要對初始容量值判斷是否<0 this

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);
    }
}

(2)無參的構造函數,這裏咱們須要注意的是雖然數組默認容量是10 ,可是無參的狀況下數組的初始化實際是個空數組而不是建立大小爲10的數組spa

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

(3)集合 由於是對數組操做因此 用的是Arrays.copyOf 進行復制和類型轉換

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;
    }
}

四、增長

(1)增長一個數的操做,size記錄當前數組中的數個數因此實際操做是在數組下標爲size位置賦值,而後 size++ ,可是在這以前得判斷size+1是否越界,也就是是否須要進行擴容,擴容咱們下面再講,該函數的返回值是Boolean類型

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

(2)在指定的下標增長一個數的操做。在這裏咱們須要注意一個函數就是System.arraycopy()這個是ArrayList中經常使用的API,主要用於數組移位,原先的數存在elementData[]中,一共size個對象,如今咱們須要在下標爲index插入新對象 就須要咱們將 下標[index ,size-1]範圍的對象移位到[index+1,size]

操做是   System.arraycopy(elementData, index, elementData, index + 1,size - index);

  (源數組 ,複製開始下標,目標數組,目標數組開始下標,複製對象的個數)     

public void add(int index, E element) {
    rangeCheckForAdd(index);

    ensureCapacityInternal(size + 1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    elementData[index] = element;
    size++;
}

(3)增長一個集合的操做。首先將集合轉換爲數組,再判斷假如該集合數目的對象是否須要擴容,再調用System.arraycopy函數將新數組中的數複製到elementData[]中

public boolean addAll(Collection<? extends E> c) {
    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);  // Increments modCount
    System.arraycopy(a, 0, elementData, size, numNew);
    size += numNew;
    return numNew != 0;
}

五、刪除

(1)刪除下標爲index的對象,先判斷index是否有效,而後是向前移一位複製,最後size下標位置賦值爲null,返回刪除的對象

public E remove(int index) {
    rangeCheck(index);

    modCount++;
    E oldValue = elementData(index);
   // 這裏可能會疑惑爲何是elementData(index)而不是elementData[index] 
   // 由於elementData(index)是ArrayList的一個內部函數,實際也是返回下標爲index的對象。
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    //當別人問我ArrayList中對什麼印象最深入那麼就是這個了
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}

(2)刪除某個對象操做。實際上這是個遍歷數組順便比較的過程,分兩個條件去遍歷,當刪除的對象是 null的狀況可非null的狀況下進行,從中咱們能夠看出實際上在遇到第一個符合條件的對象就返回了,因此這個操做並不刪除完全部

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;
}

(3) 刪除一個集合的操做。

public boolean removeAll(Collection<?> c) {
    Objects.requireNonNull(c);
    return batchRemove(c, false);
}
private boolean batchRemove(Collection<?> c, boolean complement) {
    final Object[] elementData = this.elementData;
    int r = 0, w = 0;
    boolean modified = false;
    try {
        for (; r < size; r++)
            if (c.contains(elementData[r]) == complement)
                elementData[w++] = elementData[r];
    } finally {
        // Preserve behavioral compatibility with AbstractCollection,
        // even if c.contains() throws.
        if (r != size) {
            System.arraycopy(elementData, r,
                             elementData, w,
                             size - r);
            w += size - r;
        }
        if (w != size) {
            // clear to let GC do its work
            for (int i = w; i < size; i++)
                elementData[i] = null;
            modCount += size - w;
            size = w;
            modified = true;
        }
    }
    return modified;
}

六、修改

(1) 對index下標處的對象賦值新的對象,首先判斷index是否有限,而後對數組下標爲index位置賦值,返回舊對象

public E set(int index, E element) {
    rangeCheck(index);

    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}

七、查找

(1)查找下標爲index 的對象 ,先判斷 index是否有效,再返回下標爲index的數組

public E get(int index) {
    rangeCheck(index);

    return elementData(index);
}

八、擴容分析

上面咱們在分析增長一個對象操做以前會調用 ensureCapacityInternal(size + 1) ,如今咱們將沿着這條線去分析擴容的過程

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);
    //這裏能夠看出擴容的容量是舊容量的1.5倍
    if (newCapacity - minCapacity < 0)
    //還要將1.5倍舊容量與最小須要的容量比較取較大值
        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);
}
private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
    //從這裏能夠看出數組可以容許的最大擴容大小爲Integer.MAX_VALUE
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;

總結: ArrayList 底層用的是數組實現,默認初始化容量爲10 ,擴容規則爲舊數組長度的1.5倍與當前須要的最小容量的最大值,最經常使用的操做是Arrays.copyOf() 和  System.arraycopy() 

九、複製數組兩種方式的區別。

System.arraycopy()

int[] arr = {1,2,3,4,5};
 
int[] copied = new int[10];
System.arraycopy(arr, 0, copied, 1, 5);//5 is the length to copy
 
System.out.println(Arrays.toString(copied));

運行結果:

[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 1, 2, 3, 4, 5, 0, 0, 0, 0]

Arrays.copyof()

int[] copied = Arrays.copyOf(arr, 10); //10 the the length of the new array
System.out.println(Arrays.toString(copied));
 
copied = Arrays.copyOf(arr, 3);
System.out.println(Arrays.toString(copied));

運行結果:

[1, 2, 3, 4, 5, 0, 0, 0, 0, 0]
[1, 2, 3]

二、二者間的主要區別

二者的區別在於,Arrays.copyOf()不只僅只是拷貝數組中的元素,在拷貝元素時,會建立一個新的數組對象。而System.arrayCopy只拷貝已經存在數組元素。

若是咱們看過Arrays.copyOf()的源碼就會知道,該方法的底層仍是調用了System.arrayCopyOf()方法。

public static int[] copyOf(int[] original, int newLength) { 
   int[] copy = new int[newLength]; 
   System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); 
   return copy; 
}

十、ConcurrentModificationException

即當modCount != expectedModCount時,執行next()就會拋出ConcurrentModificationException
而何時會形成modCount != expectedModCount呢?
ArrayList.remove()方法,每執行一次都會modCount++,但不改變expectedModCount的值。expectedModCount的值是在構建迭代的時候初始爲expectedModCount=modCount的。
因此構建迭代器後,用迭代器來add和remove就沒有問題。由於它會在改變modCount的值以後,又把值賦給了expectedModCount,從而保證modCount=expectedModCount

public E next() {
    checkForComodification();
    try {
        int i = cursor;
        E next = get(i);
        lastRet = i;
        cursor = i + 1;
        return next;
    } catch (IndexOutOfBoundsException e) {
        checkForComodification();
        throw new NoSuchElementException();
    }
}

每次循環next都會調用checkForComodification方法來判斷

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

十一、線程不安全

在上述例子中,能夠體現出在 addsize 和 toString 的執行過程當中出現了一個方法執行到一半而執行了另外一個方法的狀況,由此產生了輸出不一致的問題。針對上述狀況,繼承 ArrayList 進行同步限制:

private static class SafeArrayList<T> extends ArrayList<T> {

    private static final Object lock = new Object();

    @Override

    public boolean add(T o) {

        synchronized (lock) {

            return super.add(o);

        }

    }

    @Override

    public String toString() {

        synchronized (lock) {

            return super.toString();

        }

    }

    @Override

    public int size() {

        synchronized (lock) {

            return super.size();

        }

    }

}

2、LinkedList分析

一、LinkedList是雙向鏈表但不是循環的列表

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;
    }
}

二、 內部維護size,first,last,支持雙向遍歷,鏈表增刪操做時,注意維護好這三個值;

private void linkFirst(E e) {
    final Node<E> f = first;
    final Node<E> newNode = new Node<>(null, e, f);
    first = newNode;
    if (f == null)
        last = newNode;
    else
        f.prev = newNode;
    size++;
    modCount++;
}

三、循環linkedList,不能使用for循環,可使用foreach和iteritor

Node<E> node(int index) {
    // assert isElementIndex(index);

    if (index < (size >> 1)) {
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

來算一下:按照前一半算應該是(1 + 0.5N) * 0.5N / 2,後一半算上即乘以2,應該是(1 + 0.5N) * 0.5N = 0.25N2 + 0.5N,忽略低階項和首項係數,得出結論,LinikedList遍歷的時間複雜度爲O(N2),N爲LinkedList的容量

四、加強for循環的原理

for (Integer i : list) {
    System.out.print(i + ",");
}

第一種是普通的for循環遍歷、第二種是使用迭代器進行遍歷,第三種咱們通常稱之爲加強for循環(for each)。

實現原理

能夠看到,第三種形式是JAVA提供的語法糖,這裏咱們剖洗一下,這種加強for循環底層是如何實現的。

咱們對如下代碼進行反編譯

for (Integer i : list) {
       System.out.println(i);
   }

反編譯後:

Integer i;
    for(Iterator iterator = list.iterator(); iterator.hasNext(); System.out.println(i)){
        i = (Integer)iterator.next();        
    }
相關文章
相關標籤/搜索