Guava 優秀源碼記錄

Guava 優秀源碼記錄

Maps.difference(差異)

兩個Map間 高效的差異算法(Maps.difference)其底層的實現也算是最優的實現了,只需要循環一次。
入參就是兩個 Map,比較之後能夠返回四種差異:

  1. 左邊 Map 獨有 key。
  2. 右邊 Map 獨有 key。
  3. 左右邊 Map 都有 key,並且 value 相等。
  4. 左右邊 Map 都有 key,但是 value 不等。
// 對比兩個 map 的差異
private static <K, V> void doDifference(
    Map<? extends K, ? extends V> left,
    Map<? extends K, ? extends V> right,
    Equivalence<? super V> valueEquivalence,
    // key 只在左邊 map 出現
    Map<K, V> onlyOnLeft,
    // key 只在右邊 map 出現,調用 doDifference 方法前已經包含了全部右邊的值
    Map<K, V> onlyOnRight,
    // key 在左右 map 中都出現過,並且 value 都相等
    Map<K, V> onBoth,
    // key 在左右 map 中都出現過,但 value 不等
    Map<K, MapDifference.ValueDifference<V>> differences) {
    // 以左邊 map 爲基準進行循環
    for (Entry<? extends K, ? extends V> entry : left.entrySet()) {
        K leftKey = entry.getKey();
        V leftValue = entry.getValue();
        // 右邊 map 包含左邊的 key
        if (right.containsKey(leftKey)) {
            // onlyOnRight 已經包含全部右邊的值 所以需要刪除當前 key
            V rightValue = onlyOnRight.remove(leftKey);
            // key 相等,並且 value 值也相等
            if (valueEquivalence.equivalent(leftValue, rightValue)) {
                onBoth.put(leftKey, leftValue);
                // key 相等,但 value 值不等
            } else {
                differences.put(leftKey, ValueDifferenceImpl.create(leftValue, rightValue));
            }
            // 右邊 map 不包含左邊的 key,就是左邊 map 獨有的 key
        } else {
            onlyOnLeft.put(leftKey, leftValue);
        }
    }
}

demo 及結果

HashMap<String, Object> left = new HashMap<>();
left.put("1", "1");
left.put("3", "3");
left.put("5", "555");
left.put("6", "");
left.put("8", "left");

HashMap<String, Object> right = new HashMap<>();
right.put("2", "2");
right.put("4", "4");
right.put("5", "555");
right.put("7", "7");
right.put("8", "right");

MapDifference<String, Object> difference = Maps.difference(left, right);
log.info("左邊 map 獨有 key:{}",difference.entriesOnlyOnLeft());
log.info("右邊 map 獨有 key:{}",difference.entriesOnlyOnRight());
log.info("左右邊 map 都有 key,並且 value 相等:{}",difference.entriesInCommon());
log.info("左右邊 map 都有 key,但 value 不等:{}",difference.entriesDiffering());

// 左邊 map 獨有 key:{1=1, 3=3, 6=}
// 右邊 map 獨有 key:{2=2, 4=4, 7=7}
// 左右邊 map 都有 key,並且 value 相等:{5=555}
// 左右邊 map 都有 key,但 value 不等:{8=(left, right)}

Lists.reverse(反轉)

demo:

public static void testReverse(){
    List<String> list = new ArrayList<String>(){{
        add("10");
        add("20");
        add("30");
        add("40");
    }};
    log.info("反轉之前:"+list);
    list = Lists.reverse(list);
    log.info("反轉之後:"+list);
    log.info(list.getClass().getName());
}

// 反轉之前:[10, 20, 30, 40]
// 反轉之後:[40, 30, 20, 10]
// com.google.common.collect.Lists$ReverseList

reverse 方法底層實現非常巧妙,底層覆寫了 List 原生的 get (index) 方法,會把傳進來的 index 進行 (size - 1) - index 的計算,使計算得到的索引位置和 index 位置正好相反,這樣當我們 get 時,數組索引位置的 index 已經是相反的位置了,達到了反轉排序的效果,其實底層並沒有進行反轉排序,只是在計算相反的索引位置,通過計算相反的索引位置這樣簡單的設計,得到了反轉排序的效果,很精妙。

private int reverseIndex(int index) {
    int size = size();
    checkElementIndex(index, size);
    return (size - 1) - index;
}

ArrayList的初始化

當我們清楚ArrayList的大小時,減少擴容的次數,可以在創建時,指定其大小。

// 可以預估 list 的大小爲 20
List<String> list = Lists.newArrayListWithCapacity(20);
// 不太肯定 list 大小是多少,但期望是大小是 20 左右。
List<String> list = Lists.newArrayListWithExpectedSize(20);

newArrayListWithCapacity (20) 方法內部實現是:new ArrayList<>(20);,而 newArrayListWithExpectedSize 方法內部實現是對 List 大小有一個計算公式的,計算公式爲:5L + arraySize + (arraySize / 10) ,arraySize 表示傳進來的值,公式簡化下就是 5 + 11/10 * arraySize,因爲這個方法表示期望的大小,所以這裏取的約是期望值的十分之十一,比傳進來的值約大十分之一,所以根據 20 最終計算出來的值是 27。

HashMap的初始化

與ArrayList的初始化類似,創建時指定大小,是爲了減少擴容次數。
經典的計算初始大小的公式:取最大值(期望的值 / 0.75 + 1,默認值 16)。newHashMapWithExpectedSize 方法底層也是這麼算的初始化大小的

Map<String,String> withExpectedSizeHashMap = Maps.newHashMapWithExpectedSize(20);

其計算容量,最終調用的是capacity方法

static int capacity(int expectedSize) {
    if (expectedSize < 3) {
        // 檢查是否負數
        checkNonnegative(expectedSize, "expectedSize");
       
        return expectedSize + 1;
    }
    // 是否小於2^30
    if (expectedSize < Ints.MAX_POWER_OF_TWO) {
        // This is the calculation used in JDK8 to resize when a putAll
        // happens; it seems to be the most conservative calculation we
        // can make.  0.75 is the default load factor.
        return (int) ((float) expectedSize / 0.75F + 1.0F);
    }
    return Integer.MAX_VALUE; // any large value
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章