萬字長文!阿里P7架構師整理的java集合筆記,你值得擁有!

集合

List、Set、Map

集合中的最上層接口只有2類:Map和Collection,List和Set是Collection的下一層。java

LIst

Queue

Set

Map

HashMap

存儲數據的流程node

  • 對key的hash後得到數組index;2.數組位置爲空,初始化容量爲16
  • 數組位置爲空,初試化容量爲16
  • hash後沒有碰撞,就放入數組
  • 有碰撞且節點已存在,則替換掉原來的對象
  • 有碰撞且節點已是樹結構,則掛載到樹上
  • 有碰撞且節點已是鏈表結構,則添加到鏈表末尾,並判斷鏈表是否須要轉換爲樹結構(鏈表結點大於8就轉換)
  • 完成put操做後,判斷是否須要resize()操做

hashMap不安全緣由

  1. 在JDK1.7中,當併發執行擴容操做時會形成環形鏈和數據丟失的狀況。源碼是1.7時的 transfer函數,本身點進去看
  2. 在JDK1.8中,在併發執行put操做時會發生數據覆蓋的狀況。源碼是1.8時的resize函數,本身點進去看

HashMap和Hashtable

key,value爲空的問題:面試

public static void main(String[] args) {
        HashMap<Integer, Integer> hashmap = new HashMap<>();
        hashmap.put(null, null);// hashmap兩個均可以存null
        Hashtable<Integer, Integer> hashtable = new Hashtable<>();
        hashtable.put(null, null);//hashtable任一個都不能存null,但idea不會報錯,運行會出現空指針異常
    }

HashMap的長度爲何是2的冪次方?算法

答:提升數組利用率,減小衝突(碰撞)的次數,提升HashMap查詢效率數組

// 源碼計算index的操做:n是table.length
 if ((p = tab[i = (n - 1) & hash]) == null)

ConcurrentHashMap

線程安全的底層原理:沒有哈希衝突就大量CAS插入+若是有哈希衝突就Syn加鎖緩存

TreeMap

treeMap底層使用紅黑樹,會按照Key來排序安全

  • 若是是字符串,就會按照字典序來排序
  • 若是是自定義類,就要使用2種方法指定比較規則
    • 實現Compareable接口,可是須要從新定義比較規則就要修改源碼,麻煩
    • 建立實例時候,傳入一個比較器Comparator,從新定義規則不須要修改源碼,推薦使用
public class TreeMapDemo {
    public static void main(String[] args) {
        // treeMap中自定義類須要指定比較器
        // 方式一:自定義類實現Comparable接口
        TreeMap<User, User> treeMap1 = new TreeMap<>();
        // 方式二:建立實例指定比較器Comparator
        TreeMap<User, User> treeMap2 = new TreeMap<>(new Comparator<User>() {
            @Override
            public int compare(User o1, User o2) {
                // 定義比較規則
                return 0;
            }
        });
    }
}
public class User implements Comparable {
    private String id;
    private String username;

    @Override
    public int compareTo(Object obj) {
        // 這裏定義比較規則
        return 0;
    }
}

ArrayList和LinkedList

Vetor和CopyOnWriteList

list安全類是以下兩個:Vetor、CopyOnWriteList; Collections.synchronizedLis是JDK包裝實現線程安全的工具類併發

public synchronized int capacity() {
        return elementData.length;
    }

	// Vetor鎖都加在方法上
    public synchronized int size() {
        return elementCount;
    }

    public synchronized boolean isEmpty() {
        return elementCount == 0;
    }
    ...
    }
static class SynchronizedList<E>
        extends SynchronizedCollection<E>
        implements List<E> {
        private static final long serialVersionUID = -7754090372962971524L;

        final List<E> list;
		// Collections.synchronizedList:內部類SynchronizedList,鎖加載內部類裏面
        SynchronizedList(List<E> list) {
            super(list);
            this.list = list;
        }
        SynchronizedList(List<E> list, Object mutex) {
            super(list, mutex);
            this.list = list;
        }
        ....
}
//  CopyOnWriteList 寫加鎖
public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            // CopyOnWriteList是複製數組保證線程安全
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }
// CopyOnWriteList 讀不加鎖,原數組經過 transient volatile保證不可系列化和可見性
private transient volatile Object[] array;

final Object[] getArray() {
    return array;
} 

public E get(int index) {
    return get(getArray(), index);
}

LinkedHashMap和LinkedHashSet

答:LinkedHashMap能夠記錄下元素的插入順序和訪問順序ide

  • LinkedHashMap內部的Entry繼承於HashMap.Node,這兩個類都實現了Map.Entry<K,V>
  • 底層鏈表是雙向鏈表,Node不光有value,next,還有before和after屬性,保證了各個元素的插入順序
  • 經過構造方法public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder), accessOrder傳入true能夠實現LRU緩存算法(訪問順序)

LRU算法

最近最少使用算法: 根據數據的歷史訪問記錄來進行淘汰數據,其核心思想是「若是數據最近被訪問過,那麼未來被訪問的概率也更高」。函數

public class LRUTest {
    // 0.指定map長度size=5
    private static final int size = 5;

    public static void main(String[] args) {
        // 1\. LinkedHashMap三大參數,重寫removeEldestEntry
        Map<String, String> map = new LinkedHashMap<String, String>(size, 0.75f, true) {
            @Override
            protected boolean removeEldestEntry(Map.Entry<String, String> eldest) {

                return size() > size;
            }
        };
        // 2.添加5個數,使得map滿
        map.put("1", "1");
        map.put("2", "2");
        map.put("3", "3");
        map.put("4", "4");
        map.put("5", "5");
        System.out.println("map:" + map.toString());
        // 3.指定map滿了,再put就會移除表頭第一個元素:1=1
        map.put("6", "6");
        System.out.println("map:" + map.toString());
        // 4.get取出的元素,表示是經常使用的,放回到表尾
        map.get("3");
        System.out.println("map:" + map.toString());
    }
}

執行結果:

map:{1=1, 2=2, 3=3, 4=4, 5=5}
map:{2=2, 3=3, 4=4, 5=5, 6=6}
map:{2=2, 4=4, 5=5, 6=6, 3=3}

手寫LRU算法

public class LRUCache {
	// 力扣146同一題
    class DoubleNode {
        int key;
        int value;
        DoubleNode pre;
        DoubleNode next;

        DoubleNode(int key, int value) {
            this.key = key;
            this.value = value;
        }

        DoubleNode() {

        }
    }

    private HashMap<Integer, DoubleNode> cache = new HashMap<>();
    private int size;
    private int capacity;
    private DoubleNode head, tail;

    public LRUCache(int capacity) {
        this.size = 0;
        this.capacity = capacity;
        this.head = new DoubleNode();
        this.tail = new DoubleNode();
        // 建立僞頭部和僞尾部,減小添加和刪除的邏輯
        head.next = tail;
        tail.pre = head;
    }

    public int get(int key) {
        // 1.獲取get元素
        DoubleNode node = cache.get(key);
        // 2.get元素不存就返回-1
        if (node == null) {
            return -1;
        }
        // 3.get元素就移動至頭部,規定經常使用元素移動至頭部
        moveToHead(node);
        return node.value;
    }

    public void put(int key, int value) {
        // 1.獲取put元素
        DoubleNode node = cache.get(key);
        // 2.put元素不存在
        if (node == null) {
            // 生成它
            DoubleNode nowNode = new DoubleNode(key, value);
            // 放進cache
            cache.put(key, nowNode);
            // 添加進頭部
            addToHead(nowNode);
            // 長度++
            size++;
            // 判斷是否超過指定長度
            if (size > capacity) {
                DoubleNode tail = removeTail();
                cache.remove(tail.key);
                size--;
            }
        } else {
            // 3.node存在就更新value,而後移動至頭部
            node.value = value;
            moveToHead(node);
        }
    }

    private void addToHead(DoubleNode node) {
        node.pre = head;
        node.next = head.next;
        head.next.pre = node;
        head.next = node;
    }

    private DoubleNode removeTail() {
        DoubleNode del = tail.pre;
        removeNode(del);
        return del;
    }

    private void removeNode(DoubleNode node) {
        node.pre.next = node.next;
        node.next.pre = node.pre;
    }

    private void moveToHead(DoubleNode node) {
        removeNode(node);
        addToHead(node);
    }
}

Iterator和ListIterator

public class IteratorDemo {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("1");
        list.add("2");
        list.add("3");
        Iterator<String> iterator = list.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
}

快速失敗和安全失敗

迭代器在遍歷時直接訪問集合中的內容,而且在遍歷過程當中使用一個 modCount 變量。集合在被遍歷期間若是內容發生變化,就會改變modCount的值。當迭代器使用hashNext()/next()遍歷下一個元素以前,都會檢測modCount變量是否爲expectedModCount值,是的話就返回遍歷;不然拋出異常,終止遍歷

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    ...
    // modCount記錄當前線程更改狀態    
    ++modCount;
	...
    return null;
}

數組和List和遍歷轉換

public class ArrayAndList {
    public static void main(String[] args) {
        // 1.數組遍歷:Arrays.toString
        int[] arr = {1, 2, 3};
        System.out.println(Arrays.toString(arr));
        // 2.數組轉成list,泛型說明不推薦使用,畫蛇添足
        List<int[]> ints1 = Arrays.asList(arr);
        List<int[]> ints = Collections.singletonList(arr);
        for (int[] anInt : ints) {
            System.out.println(Arrays.toString(anInt));
        }
        System.out.println("------------");
        // 3.list遍歷:直接遍歷便可
        ArrayList<Integer> arrayList = new ArrayList<>();
        arrayList.add(1);
        arrayList.add(2);
        arrayList.add(3);
        System.out.println(arrayList);
        // 4.list轉換成數組,list名.toArray(指定數組類型和長度)
        Integer[] integers = arrayList.toArray(new Integer[3]);
        System.out.println(Arrays.toString(integers));
    }
}

總結

感謝你看到這裏,文章有什麼不足還請指正,以爲文章對你有幫助的話記得給我點個贊,天天都會分享java相關技術文章或行業資訊,歡迎你們關注和轉發文章! 歡迎關注公衆號:前程有光,領取一線大廠Java面試題總結+各知識點學習思惟導+一份300頁pdf文檔的Java核心知識點總結!

相關文章
相關標籤/搜索