java常用集合淺層解析-面試必備

來源:互聯網
上載者:User
ArrayList

1.動態數組

2.線程不安全

3.儲存空間連續

4.查詢快,添加刪除慢

  • 構造方法
/** + Shared empty array instance used for default sized empty instances. We + distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when + first element is added. */private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};/** + Constructs an empty list with an initial capacity of ten. */public ArrayList() {    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}

這個構造方法很簡單,初始化了一個空的elementData,並沒有賦予數組長度

  • 元素添加
/** + Default initial capacity. */private static final int DEFAULT_CAPACITY = 10;/** + The array buffer into which the elements of the ArrayList are stored. + The capacity of the ArrayList is the length of this array buffer. Any + empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA + will be expanded to DEFAULT_CAPACITY when the first element is added. */transient Object[] elementData; // non-private to simplify nested class access/** + The size of the ArrayList (the number of elements it contains). * + @serial */private int size;/** + Appends the specified element to the end of this list. * + @param e element to be appended to this list + @return <tt>true</tt> (as specified by {@link Collection#add}) */public boolean add(E e) {    // 首先進行擴充    ensureCapacityInternal(size + 1);  // Increments modCount!!     // 將元素追加到最後    elementData[size++] = e;     return true;}// 擴充private void ensureCapacityInternal(int minCapacity) {    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));}// 計算數組大小 第一次調用此處的elementData={},所以傳回值為DEFAULT_CAPACITY=10,也就是預設的數組長度是10private static int calculateCapacity(Object[] elementData, int minCapacity) {    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {        return Math.max(DEFAULT_CAPACITY, minCapacity);    }    return minCapacity;}private void ensureExplicitCapacity(int minCapacity) {    modCount++;    // overflow-conscious code    if (minCapacity - elementData.length > 0) // 當加上當前元素後的集合長度(size)大於現在數組長度(elementData.length)在進行擴充        grow(minCapacity);}// 真正的擴充操作private void grow(int minCapacity) {    // overflow-conscious code    int oldCapacity = elementData.length; // 此處oldCapacity=0    int newCapacity = oldCapacity + (oldCapacity >> 1); // 此處newCapacity=0     if (newCapacity - minCapacity < 0) // 此處minCapacity=10        newCapacity = minCapacity; // 此處newCapacity=10    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); //數組拷貝}

真正的數組長度是在第一次添加的時候進行初始化的,預設為10
最主要的消耗是在擴容(數組拷貝)
當集合長度大於數組長度的時候進行擴充,擴充的標準是1.5倍[oldCapacity + (oldCapacity >> 1)]

  • 查詢
public E get(int index) {    rangeCheck(index);// 校正    return elementData(index);}E elementData(int index) {    return (E) elementData[index];}
Vector

1.動態數組,類似於ArrayList

2.安全執行緒

3.消耗大

  • 構造方法
public Vector() {    this(10); // initialCapacity初始容量}
  • 元素添加
/** * Appends the specified element to the end of this Vector. * * @param e element to be appended to this Vector * @return {@code true} (as specified by {@link Collection#add}) * @since 1.2 */public synchronized boolean add(E e) {    modCount++;    ensureCapacityHelper(elementCount + 1);    elementData[elementCount++] = e;    return true;}

被synchronized修飾,安全執行緒,但是效率較低

  • 在指定位置添加元素
public void add(int index, E element) {    insertElementAt(element, index);}public synchronized void insertElementAt(E obj, int index) {    modCount++;    if (index > elementCount) {        throw new ArrayIndexOutOfBoundsException(index                                                 - " > " + elementCount);    }    ensureCapacityHelper(elementCount + 1);    System.arraycopy(elementData, index, elementData, index + 1, elementCount - index);    elementData[index] = obj;    elementCount++;}
LinkedList

1.雙向鏈表:jdk1.7/8以後

2.插入快,查詢慢

  • 建構函式
/** * Constructs an empty list. */public LinkedList() {}

空的構造方法

  • 元素添加
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++;    }

預設添加到鏈表結尾,prev指向原結尾元素,原結尾元素next指標指向新添加元素,並記錄結尾元素為新添加元素。只有指標移動,並沒有數組拷貝,所以插入效率較快

  • 查詢
public E get(int index) {    checkElementIndex(index);    return node(index).item;}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;    }}

查詢採用二分法尋找,先將數組拆分成一半,然後進行遍曆。所以查詢較慢。當index值接近二分之一size時,更慢。

HashMap

1.儲存結構:數組+鏈表/數組+紅/黑樹狀結構

2.線程不安全

  • 構造方法
static final float DEFAULT_LOAD_FACTOR = 0.75f;public HashMap() {    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted}

沒有初始化數組,負載因子為0.75

  • 添加元素
public V put(K key, V value) {    return putVal(hash(key), key, value, false, true);}final V putVal(int hash, K key, V value, boolean onlyIfAbsent,               boolean evict) {    Node<K,V>[] tab; Node<K,V> p; int n, i;    if ((tab = table) == null || (n = tab.length) == 0) //[1]        n = (tab = resize()).length; // [2]    if ((p = tab[i = (n - 1) & hash]) == null) [// [3]        tab[i] = newNode(hash, key, value, null); // [4]    else {        Node<K,V> e; K k;        if (p.hash == hash &&            ((k = p.key) == key || (key != null && key.equals(k))))// [5]            e = p; // [6]        else if (p instanceof TreeNode) // [7]            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);  //[8]        else { //[9]            for (int binCount = 0; ; ++binCount) {                 if ((e = p.next) == null) { // [10]                    p.next = newNode(hash, key, value, null); // [11]                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st [12]                        treeifyBin(tab, hash); // [13]                    break;                }                if (e.hash == hash &&                    ((k = e.key) == key || (key != null && key.equals(k)))) // [14]                    break;                p = e;            }        }        if (e != null) { // existing mapping for key  [15]            V oldValue = e.value;            if (!onlyIfAbsent || oldValue == null) // [16]                e.value = value;            afterNodeAccess(e);            return oldValue;        }    }    ++modCount;    if (++size > threshold) // [17]        resize();    afterNodeInsertion(evict);    return null;}

[1]判斷table是否為null,長度是否為0,table用於擴充時記錄擴充後的新數組
[2]進行數組擴充,將新數組賦值給tab,n為新數組的長度
[3]判斷新key需要儲存的數組節點是否有值
[4]如果沒有值,直接儲存於該節點,如果當前數組節點有值
[5]判斷新key與當前儲存的key是否相同
[6]記錄當前儲存元素到e
[7]判斷當前節點是否為數節點
[8]進行樹節點操作
[9]當前節點儲存的key與新key不同,並且不是樹形結構(鏈表結構)
[10]迴圈遍曆,找到鏈表的尾節點
[11]將新元素追加到鏈表的末尾,即原尾節點的next指標指向新元素
[12]當鏈表的長度達到8時,轉為樹形結構[13]
[14]迴圈過程中如果發現儲存的key與新key相同,則中斷迴圈
[15]如果存在匹配的key,則替換value
[16]返回舊值
[17]能走到這裡說明是新增元素,並不是更新元素,判斷當前集合長度是否大於threshold(threshold=當前集合長度*0.75),如果大於需要進行擴充

// 擴容final Node<K,V>[] resize() {    Node<K,V>[] oldTab = table; // [1]    int oldCap = (oldTab == null) ? 0 : oldTab.length; //[2]    int oldThr = threshold; // [3]    int newCap, newThr = 0;    if (oldCap > 0) {        if (oldCap >= MAXIMUM_CAPACITY) { // [4]            threshold = Integer.MAX_VALUE;            return oldTab;        }        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&                 oldCap >= DEFAULT_INITIAL_CAPACITY) // [5]            newThr = oldThr << 1;     }    else if (oldThr > 0) // [6]        newCap = oldThr;    else { // [7]                      newCap = DEFAULT_INITIAL_CAPACITY;         newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);     }    if (newThr == 0) { // [8]        float ft = (float)newCap * loadFactor;        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?                  (int)ft : Integer.MAX_VALUE);    }    threshold = newThr;  // [9]    @SuppressWarnings({"rawtypes","unchecked"})        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];     table = newTab;  // [10]    if (oldTab != null) {        for (int j = 0; j < oldCap; ++j) {// [11]            Node<K,V> e;            if ((e = oldTab[j]) != null) {// [12]                oldTab[j] = null;                if (e.next == null)                     newTab[e.hash & (newCap - 1)] = e;                else if (e instanceof TreeNode) // [13]                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);                else { // preserve order  [14]                    Node<K,V> loHead = null, loTail = null;                    Node<K,V> hiHead = null, hiTail = null;                    Node<K,V> next;                    do {                        next = e.next;                        if ((e.hash & oldCap) == 0) {                            if (loTail == null)                                loHead = e;                            else                                loTail.next = e;                            loTail = e;                        }                        else {                            if (hiTail == null)                                hiHead = e;                            else                                hiTail.next = e;                            hiTail = e;                        }                    } while ((e = next) != null);                    if (loTail != null) {                        loTail.next = null;                        newTab[j] = loHead;                    }                    if (hiTail != null) {                        hiTail.next = null;                        newTab[j + oldCap] = hiHead;                    }                }            }        }    }    return newTab;}

[1]oldTab用來記錄上次擴充的table
[2]oldCap用來記錄上次擴充table的長度
[3]oldThr用來記錄上次擴充的閾值
[4]如果oldCap大於等於最大值(2^30),threshold等於2^30-1,直接返回,不在進行擴充
[5]newCap等於(oldCap*2),如果newCap小於最大值(2^30)並且oldCap大於初始值(2^4),則newThr=oldThr*2
[6]如果oldThr大於0,則newCap等於oldThr,上次擴充的閾值
[7]如果oldCap和oldThr都不大於0,則newCap等於2^4,newThr等於2^4*0.75(首次擴充)
[8]當oldCap小於2^4的時,newThr等於0,newThr=2*oldCap*0.75
[9]threshold等於newThr,記錄下次需要擴充的閾值
[10]建立新的Node數字,長度為newCap
[11]如果oldTab不為空白,則遍曆這個數組
[12]將原數組的元素散列到新數組中
[13]以紅/黑樹狀結構的結構重新散列元素
[14]以鏈表的結構重新散列元素

  • get方法,先根據key計算出對應的數組指標位置,然後遍曆鏈表或者紅/黑樹狀結構擷取相同key的元素
Iterator<Map.Entry<String, Integer>> entryIterator = map.entrySet().iterator();    while (entryIterator.hasNext()) {        Map.Entry<String, Integer> next = entryIterator.next();        System.out.println("key=" + next.getKey() + " value=" + next.getValue());    }
Iterator<String> iterator = map.keySet().iterator();    while (iterator.hasNext()){        String key = iterator.next();        System.out.println("key=" + key + " value=" + map.get(key));    }
map.forEach((key,value)->{    System.out.println("key=" + key + " value=" + value);});

hashmap只能在單線程中使用,並盡量減少擴容,迴圈鏈表的時間複雜度是O(n),O(logn)

多線程情境下推薦使用ConcurrentHashMap

ConcurrentHashMap
Object put(Object key, int hash, Object value, boolean onlyIfAbsent) {    lock();    try {        int c = count;        if (c++ > threshold) // ensure capacity            rehash();        HashEntry[] tab = table;        int index = hash & (tab.length - 1);        HashEntry first = tab[index];        HashEntry e = first;        while (e != null && (e.hash != hash || !key.equals(e.key)))            e = e.next;        Object oldValue;        if (e != null) {            oldValue = e.value;            if (!onlyIfAbsent)                e.value = value;        }        else {            oldValue = null;            ++modCount;            tab[index] = new HashEntry(key, hash, first, value);            count = c; // write-volatile        }        return oldValue;    } finally {        unlock();    }}

ConcurrentHashMap之所以是安全執行緒的是因為在添加元素的時候先上了一個鎖,操作完成在解鎖。

HashSet

1.hashmap儲存資料
2.不允許儲存重複元素的集合

  • 構造方法
public HashSet() {    map = new HashMap<>();}
  • 添加元素
private static final Object PRESENT = new Object();public boolean add(E e) {    return map.put(e, PRESENT)==null;}

此方法將添加的元素e作為hashmap的key,value都是相同的PRESENT,因為hashmap的key是不允許重複的,所以相同的元素添加進來,後添加的會覆蓋先添加的,這就是不允許重複的原因

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.