1、Java常用容器:List,Set,Map List: 繼承了Collection介面(public interface List<E> extends Collection<E> ),有序且允許出現重覆值。 Set: 繼承了Collection介面(public interface Set<E ...
1、Java常用容器:List,Set,Map
List:
- 繼承了Collection介面(public interface List<E> extends Collection<E> ),有序且允許出現重覆值。
Set:
- 繼承了Collection介面(public interface Set<E> extends Collection<E> ),無序且不允許出現重覆值。
Map:
- 是一個使用鍵值對存儲的容器(public interface Map<K,V> )。
2、Collection 和 Collections 的區別
Collection:
- Collection是一個集合類的通用介面(源碼:public interface Collection<E> extends Iterable<E>)。
- 通過查看源碼可以發現,其中包含的都是一些通用的集合操作介面,他的直接繼承介面有List和Set。
Collections:
- Collections是一個集合工具類(源碼:public class Collections)。
- 其中提供一系列對集合操作的靜態方法,比如排序:sort(),集合安全:synchronizedCollection(),反轉:reverse()等等。
3、ArrayList 和 LinkedList 的區別
ArrayList:
- 底層數據結構是一個數組,查詢效率比較高,添加刪除較慢(預設添加在末尾,在指定位置添加元素效率比較低,因為需要將指定位置後續的元素都往後移位)。
LinkedList:
- 底層數據結構是一個雙向鏈表(prev指向前節點,next指向後節點),查詢效率比較慢,添加刪除比較快。
4、ArrayList 和 Vector 的區別
ArrayList:
- 非線程安全,讀取效率較高。
Vector:
- 線程安全(源碼中顯示該類的方法使用了synchronized),讀取效率較低(推薦使用CopyOnWriteArrayList,該類適合讀多寫少的場景)。
5、HashMap 和 Hashtable 的區別
HashMap:
- 非線程安全,允許空鍵值,執行效率相對較高(底層使用的數據結構是數組+鏈表+紅黑樹(jdk8)或者數組+鏈表(jdk7))。
Hashtable:
- 線程安全,不允許空鍵值,執行效率相對較低(推薦使用ConcurrentHashMap)。
6、HashMap 和 TreeMap 的使用場景
HashMap:
- 一般情況下進行插入,刪除,定位元素的話,使用HashMap(常用)。
TreeMap:
- 如果需要使用有序的集合,推薦用TreeMap。
7、HashMap 實現原理
以put操作為例:
- 首先會根據key的hashCode得到hash值(部分源碼:return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16)),依據hash值得到該元素在數組的位置(下標),如果該位置不存在元素,則將該元素直接放入此位置上;否則判斷元素是否相等,如果是,則覆蓋,否則使用拉鏈法解決衝突(創建一個鏈表,先加入的放到鏈尾,後加入的放在鏈頭,超過8位時,使用紅黑樹存儲)。
- 放入的元素是包含了鍵值對的元素,而非僅僅只有值。
8、HashSet 實現原理
以add操作為例:
- 進入add源碼(return map.put(e, PRESENT)==null),可以看到其底層是用map來實現的,只是傳入的值當做了map的key,而map的value使用的是統一的PRESENT。
9、迭代器:Iterator
Iterator:
- 是一個輕量級的對象(創建代價小),主要用來對集合進行遍歷移除等操作。
- 示例代碼如下
package com.spring.test.service.demo; import java.util.*; /** * @Author: philosopherZB * @Date: 2019/10/1 */ public class Demo { public static void main(String[] args){ List<String> list = new ArrayList<>(5); for(int i=0;i<5;i++){ list.add("Test" + i); System.out.println("輸入:Test" + i); } //利用iterator()返回一個Iterator對象 Iterator<String> it = list.iterator(); //判斷是否還存在元素 while(it.hasNext()){ //第一次調用next()會返回集合中的第一個元素,之後返回下一個 String s = it.next(); if("Test3".equals(s)) //移除某個元素 it.remove(); } list.forEach(l->{ System.out.println(l); }); } }View Code
10、ArrayList 擴容源碼解析(JDK8)
源碼解析:
- 首先我們使用 ArrayList<String> list = new ArrayList<>(5)創建一個ArrayLsit,這表明創建的ArrayList初始容量為5.
- 源碼如下:
//預設初始容量10 private static final int DEFAULT_CAPACITY = 10; //一個空的預設對象數組,當ArrayList(int initialCapacity),ArrayList(Collection<? extends E> c)中的容量等於0的時候使用 private static final Object[] EMPTY_ELEMENTDATA = {}; //一個空的預設對象數組,用於ArrayList()構造器 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //一個對象數組,transient表示不能序列化 transient Object[] elementData; //數組大小 private int size; //以傳入的容量構造一個空的list public ArrayList(int initialCapacity) { //如果傳入值大於0,則創建一個該容量大小的數組。 if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { //否則如果傳入值等於0,則創建預設空數組 this.elementData = EMPTY_ELEMENTDATA; } else { //如果小於0則拋出異常 throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } }
- 接著我們使用add方法添加一個字元串到該list中,list.add("Test")。進入add源碼會發現,真正的擴容是發生在add操作之前的。
- 源碼如下:
//預設添加在數組末尾 public boolean add(E e) { //添加之前先確認是否需要擴容 ensureCapacityInternal(size + 1); // Increments modCount!! //新加入的元素是添加在了數組的末尾,隨後數組size自增。 elementData[size++] = e; return true; }
- 進入ensureCapacityInternal()方法查看對應源碼如下:
private void ensureCapacityInternal(int minCapacity) { //先通過calculateCapacity方法計算最終容量,以確認實際容量 ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); }
- 到這一步,我們需要先進入calculateCapacity()方法看看他是如何計算最後容量的,源碼如下:
private static int calculateCapacity(Object[] elementData, int minCapacity) { //如果elementData為預設空數組,則比較傳入值與預設值(10),返回兩者中的較大值 //elementData為預設空數組指的是通過ArrayList()這個構造器創建的ArrayList對象 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } //返回傳入值 return minCapacity; }
- 現在我們確認了最終容量,那麼進入ensureExplicitCapacity,查看源碼如下:
private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code //如果最終確認容量大於數組容量,則進行grow()擴容 if (minCapacity - elementData.length > 0) grow(minCapacity); }
- 可以看到,只有當最終容量大於數組容量時才會進行擴容。那麼以我們上面的例子而言具體分析如下:
- 首先因為我們創建的時候就賦了初始容量5,所以elementData.length = 5。
- 當我們add第一個元素的時候,minCapacity是等於size + 1 = 1的。
- 此時minCapacity - elementData.length > 0條件不成立,所以不會進入grow(minCapacity)方法進行擴容。
- 以此類推,只有添加到第五個元素的時候,minCapacity = 6 大於 elementData.length = 5,這時就會進入grow(minCapacity)方法進行擴容。
- grow()以及hugeCapacity()源碼如下:
//可分配的最大數組大小 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; //擴容 private void grow(int minCapacity) { // overflow-conscious code //oldCapacity表示舊容量 int oldCapacity = elementData.length; //newCapacity表示新容量,計算規則為舊容量+舊容量的0.5,即舊容量的1.5倍。如果超過int的最大值會返回一個負數。 //oldCapacity >> 1表示右移一位,對應除以2的1次方。 int newCapacity = oldCapacity + (oldCapacity >> 1); //如果新容量小於最小容量,則將最小容量賦值給新容量(有時手動擴容可能也會返回<0,對應方法為ensureCapacity()) if (newCapacity - minCapacity < 0) newCapacity = minCapacity; //如果新容量大於MAX_ARRAY_SIZE,則執行hugeCapacity(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) { //如果最小容量超過了int的最大值,minCapacity會是一個負數,此時拋出記憶體溢出錯誤 if (minCapacity < 0) // overflow throw new OutOfMemoryError(); //比較最小容量是否大於MAX_ARRAY_SIZE,如果是則返回Integer.MAX_VALUE,否則返回MAX_ARRAY_SIZE return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
(以上所有內容皆為個人筆記,如有錯誤之處還望指正。)