Day18-集合(下)集合原始碼及面試題
HashSet原始碼
package com.dream.enum_util_class;
import java.util.EnumMap;
import java.util.Map.Entry;
import java.util.Set;
public class Test02 {
public static void main(String[] args) {
/**
*
* EnumMap工具類
*
* 將列舉類中物件放入Map集合中
*/
EnumMap<Signal, String> map = new EnumMap<>(Signal.class);
map.put(Signal.RED, "紅燈");
map.put(Signal.YELLOW, "黃燈");
map.put(Signal.GREEN, "綠燈");
Set<Entry<Signal,String>> entrySet = map.entrySet();
for (Entry<Signal, String> entry : entrySet) {
Signal key = entry.getKey();
String value = entry.getValue();
System.out.println(key + " -- " + value);
}
}
}
理解HashSet底層由HashMap實現
HashMap原始碼
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>{
//預設長度
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;//16
//預設負載因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//最大長度
static final int MAXIMUM_CAPACITY = 1 << 30;
//負載因子
final float loadFactor;//0.75
//閾值(容量*負載因子)
int threshold;//12
//空例項的陣列
static final Entry<?,?>[] EMPTY_TABLE = {};
//存放資料的容器
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;//new Entry[16]
//hash掩碼/hash隨機值
transient int hashSeed = 0;
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
public HashMap(int initialCapacity, float loadFactor) {
//initialCapacity = 16
//loadFactor = 0.75
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))//NAN:not a Number
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
threshold = initialCapacity;
init();
}
public V put(K key, V value) {
//第一次新增元素時進入的判斷
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
//判斷key是否為null
if (key == null)
return putForNullKey(value);
//獲取到key的hash值
int hash = hash(key);
//通過hash值獲取陣列中的下標
int i = indexFor(hash, table.length);
//e = 楊晨的Entry
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
//如果key相同,就替換value值,並返回老的value
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;//16
//當容量 == 最大容量時
if (oldCapacity == MAXIMUM_CAPACITY) {
//閾值 = Integer.MAX_VALUE
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];//new Entry[32];
//資料的移位:把原來陣列中的資料遷移到新陣列中
transfer(newTable, initHashSeedAsNeeded(newCapacity));
table = newTable;
//計算閾值
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {
while(null != e) {
Entry<K,V> next = e.next;
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
}
private V putForNullKey(V value) {
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(0, null, value, 0);
return null;
}
void addEntry(int hash, K key, V value, int bucketIndex) {
//判斷是否擴容
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);//頭插法
size++;
}
static int indexFor(int h, int length) {
return h & (length-1);
}
final int hash(Object k) {
int h = hashSeed;
//如果key是String型別,則計算hash值的過程中使用到hashSeed
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode();
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
private void inflateTable(int toSize) {//16
// 傳入的數字經過計算,都會或得到2的冪
int capacity = roundUpToPowerOf2(toSize);//capacity = 16
//threshold = 12
threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
table = new Entry[capacity];
initHashSeedAsNeeded(capacity);
}
private static int roundUpToPowerOf2(int number) {//16
return number >= MAXIMUM_CAPACITY
? MAXIMUM_CAPACITY
: (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
}
//獲取的hash掩碼
final boolean initHashSeedAsNeeded(int capacity) {
boolean currentAltHashing = hashSeed != 0;
boolean useAltHashing = sun.misc.VM.isBooted() &&
(capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
boolean switching = currentAltHashing ^ useAltHashing;
if (switching) {
hashSeed = useAltHashing
? sun.misc.Hashing.randomHashSeed(this)
: 0;
}
return switching;
}
//對映關係類/節點類
static class Entry<K,V> implements Map.Entry<K,V> {
final K key; ----- key
V value; --------- value
Entry<K,V> next; - 下一個節點的地址
int hash; -------- key的hash值
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
}
}
-
HashMap的陣列長度為什麼是2的冪?
陣列長度2的冪會使元素在陣列中分佈的更加均勻
因為如果不是2的冪,length-1的二進位制有可能某一位出現0,就會導致陣列中某個下標上永遠存不到元素,就會使得空間的浪費
-
HashMap的擴容機制/擴容倍數?
HashMap擴容的長度是原來的2倍
-
HashMap預設負載因子是多少?
0.75
-
為什麼HashMap預設負載因子是0.75
取得了時間和空間的平衡
負載因子過小:儲存元素個數較少的時候就擴容,浪費空間
負載因子過大:儲存元素個數較多的時候就擴容,浪費時間
-
HashMap的陣列最大長度是多少?
1<<30
-
HashMap的陣列最大長度為什麼是1<<30?
HashMap陣列長度必須是2的冪,並且陣列長度的型別是int
所以1<<30是int型別取值範圍內能取到最大的2的冪
-
什麼是HashMap的hash表
就是陣列
-
什麼是hash碰撞/衝突
準元素在陣列中的下標上有元素,這就是hash碰撞
-
什麼是hash桶
Hash陣列中的Entry,因為Entry中有個叫做next的屬性,Entry就是一個單向列表
-
HashMap存放null鍵的位置在哪?
在陣列中下標為0的位置
-
HashMap擴容出現的問題/Hash迴環/Hash死迴圈
在擴容的情況下,Hash桶中Entry的next指向下一個Entry的地址出現重複
一般Hash迴環出現在多執行緒擴容下,出現這個問題都是程式設計師的責任,因為HashMap明確說明該集合是執行緒不安全,在多執行緒的情況下應該使用ConcurrentHashMap
-
JDK1.8版本對於HashMap進行了哪些改動?
hash()進行了改進:將hashCode值認為有高16位和低16位,並且將高16位 ^ 低16位,讓hash值分佈更加均勻
JDK1.7:陣列+連結串列的形式,頭插法
JDK1.8:陣列+連結串列+紅黑樹,尾插法
- JDK1.8版本HashMap為什麼有黑紅數的改進?
加入紅黑樹使得查詢更有效率
- JDK1.8版本HashMap什麼時候從連結串列轉換為紅黑樹?
陣列長度大於64,並且連結串列長度大於8
- JDK1.8版本HashMap為什麼連結串列大於8就轉換為紅黑樹?
因為泊松分佈額統計概率學,統計出連結串列大於8的情況以及是及其微小了
經驗:要防止hash碰撞,一定也要重寫hashCode()
TreeSet原始碼
public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>{
private transient NavigableMap<E,Object> m;
private static final Object PRESENT = new Object();
public TreeSet() {
this(new TreeMap<E,Object>());
}
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
TreeSet(NavigableMap<E,Object> m) {
this.m = m;
}
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
}
TreeSet底層由TreeMap實現
TreeMap原始碼
public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>{
private final Comparator<? super K> comparator;
private transient Entry<K,V> root = null;
public TreeMap() {
comparator = null;
}
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
public V put(K key, V value) {
//t = 麻生希的Entry
Entry<K,V> t = root;
if (t == null) {
compare(key, key); //目的:1.檢查Key是否為null 2.檢查該類是否實現內建比較器
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
//比較結果
int cmp;
Entry<K,V> parent;
Comparator<? super K> cpr = comparator;
if (cpr != null) {//外接比較器的邏輯
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}else {//內建比較器的邏輯
if (key == null)
throw new NullPointerException();
Comparable<? super K> k = (Comparable<? super K>) key;
do {
//椎名空的Entry
parent = t;
//4
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
//北島玲的Entry
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
//紅黑樹的制衡
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
final int compare(Object k1, Object k2) {
return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
: comparator.compare((K)k1, (K)k2);
}
static final class Entry<K,V> implements Map.Entry<K,V> {
K key;
V value;
Entry<K,V> left = null;
Entry<K,V> right = null;
Entry<K,V> parent;
boolean color = BLACK;
Entry(K key, V value, Entry<K,V> parent) {
this.key = key;
this.value = value;
this.parent = parent;
}
}
}
外接比較器優先於 內建比較器
https://www.cs.usfca.edu/~galles/visualization/Algorithms.html
《演算法導論》中對於紅黑樹的定義如下:
- 每個結點或是紅的,或是黑的
- 根節點是黑的
- 每個葉結點是黑的
- 如果一個結點是紅的,則它的兩個兒子都是黑的
- 對每個結點,從該結點到其子孫節點的所有路徑上包含相同數目的黑結點
