一、概述
在之前的文章裏已經分析過,在發生hash碰撞(多個key的hash值相同)的時候,hashMap首先會採用鏈表進行存儲,當鏈表節點數量達到一定閾值(8)會將鏈表上的節點再組織成一棵紅黑樹。紅黑樹是一種二叉樹,每個父節點可以由左右兩個節點。
當put一個新元素時,如果該元素鍵的hash值小於當前節點的hash值的時候,就會作爲當前節點的左節點;hash值大於當前節點hash值得時候作爲當前節點的右節點。那麼hash值相同的時候呢?這時還是會先嚐試看是否能夠通過Comparable進行比較一下兩個對象(當前節點的鍵對象和新元素的鍵對象),要想看看是否能基於Comparable進行比較的話,首先要看該元素鍵是否實現了Comparable接口,此時就需要用到comparableClassFor方法來獲取該元素鍵的Class,然後再通過compareComparables方法來比較兩個對象的大小。
二、方法解析
/**
* 如果對象x的類是C,如果C實現了Comparable<C>接口,那麼返回C,否則返回null
*/
static Class<?> comparableClassFor(Object x) {
if (x instanceof Comparable) {
Class<?> c; Type[] ts, as; Type t; ParameterizedType p;
if ((c = x.getClass()) == String.class) // 如果x是個字符串對象
return c; // 返回String.class
/*
* 爲什麼如果x是個字符串就直接返回c了呢 ? 因爲String 實現了 Comparable 接口,可參考如下String類的定義
* public final class String implements java.io.Serializable, Comparable<String>, CharSequence
*/
// 如果 c 不是字符串類,獲取c直接實現的接口(如果是泛型接口則附帶泛型信息)
if ((ts = c.getGenericInterfaces()) != null) {
for (int i = 0; i < ts.length; ++i) { // 遍歷接口數組
// 如果當前接口t是個泛型接口
// 如果該泛型接口t的原始類型p 是 Comparable 接口
// 如果該Comparable接口p只定義了一個泛型參數
// 如果這一個泛型參數的類型就是c,那麼返回c
if (((t = ts[i]) instanceof ParameterizedType) &&
((p = (ParameterizedType)t).getRawType() ==
Comparable.class) &&
(as = p.getActualTypeArguments()) != null &&
as.length == 1 && as[0] == c) // type arg is c
return c;
}
// 上面for循環的目的就是爲了看看x的class是否 implements Comparable<x的class>
}
}
return null; // 如果c並沒有實現 Comparable<c> 那麼返回空
}
/**
* 如果x所屬的類是kc,返回k.compareTo(x)的比較結果
* 如果x爲空,或者其所屬的類不是kc,返回0
*/
@SuppressWarnings({"rawtypes","unchecked"}) // for cast to Comparable
static int compareComparables(Class<?> kc, Object k, Object x) {
return (x == null || x.getClass() != kc ? 0 :
((Comparable)k).compareTo(x));
}
如果兩者不具有compare的資格,或者compare之後仍然沒有比較出大小。那麼就要通過一個決勝局再比一次,這個決勝局就是tieBreakOrder方法。
/**
* 用這個方法來比較兩個對象,返回值要麼大於0,要麼小於0,不會爲0
* 也就是說這一步一定能確定要插入的節點要麼是樹的左節點,要麼是右節點,不然就無法繼續滿足二叉樹結構了
*
* 先比較兩個對象的類名,類名是字符串對象,就按字符串的比較規則
* 如果兩個對象是同一個類型,那麼調用本地方法爲兩個對象生成hashCode值,再進行比較,hashCode相等的話返回-1
*/
static int tieBreakOrder(Object a, Object b) {
int d;
if (a == null || b == null ||
(d = a.getClass().getName().
compareTo(b.getClass().getName())) == 0)
d = (System.identityHashCode(a) <= System.identityHashCode(b) ?
-1 : 1);
return d;
}