1. 程式人生 > >【小家java】Java之Apache Commons-Collections4使用精講(含有Bag、Map、List、Set全覆蓋)

【小家java】Java之Apache Commons-Collections4使用精講(含有Bag、Map、List、Set全覆蓋)

相關閱讀

【小家java】java5新特性(簡述十大新特性) 重要一躍
【小家java】java6新特性(簡述十大新特性) 雞肋升級
【小家java】java7新特性(簡述八大新特性) 不溫不火
【小家java】java8新特性(簡述十大新特性) 飽受讚譽
【小家java】java9新特性(簡述十大新特性) 褒貶不一
【小家java】java10新特性(簡述十大新特性) 小步迭代
【小家java】java11新特性(簡述八大新特性) 首個重磅LTS版本


前言

這個庫簡化了你的程式碼,使它易寫、易讀、易於維護。它能提高你的工作效率,讓你從大量重複的底層程式碼中脫身。

雖然JDK提供給我們的集合框架已經足夠強大,基本能解決我們平時的絕大所述問題,並且效率還挺高。

本文針對於Apache提供的Collections4元件提供的一些特殊資料結構,通過例子解決一些實際問題的講解。

® bag介面
® 固定大小的map、lru (最近最少使用演算法)map和雙重(dual)map
® 物件陣列和map的迭代器
® map的multikey
® 大量的工具類,提供了使用api的快捷方式
® 封裝器,對大多數類提供了自定義的方法

Bag

Bag繼承自Collection介面,定義了一個集合,該集合會記錄物件在集合中出現的次數。

假設你有一個包,包含{a, a, b, c}。呼叫getCount(a)方法將返回2,呼叫uniqueset()方法將返回{a, b, c}的set集合。

public interface Bag<E> extends Collection<E> {}

顧名思義,它是包的意思,所以也是拿來裝資料的。

HashBag

HashBag使用HashMap作為資料儲存,是一個標準的Bag實現。

    public static void main(String[] args) {
        Bag hashBag = new HashBag();
        String s1 = "s1";
        String s2 = "s2";
        hashBag.add(s1);
        hashBag.
add(s1); //一次性放置多個元素 hashBag.add(s2, 3); // 獲得包中元素迭代器 Iterator<?> iterator = hashBag.iterator(); System.out.println("包中元素為:"); while (iterator.hasNext()) { System.out.println(iterator.next()); } System.out.println("包中元素個數為:" + hashBag.size()); //5 //下面兩個特有的方法 使用起來較為方便 System.out.println("包中entity1個數為:" + hashBag.getCount(s1)); //2 System.out.println("去重後個數為:" + hashBag.uniqueSet().size()); //2 } 結果輸出: 包中元素為: s1 s1 s2 s2 s2 包中元素個數為:5 包中entity1個數為:2 去重後個數為:2
TreeBag

TreeBag使用TreeMap作為資料儲存,用法與HashBag類似,只是TreeBag會使用自然順序對元素進行排序。

總結

使用的方式和List差不多,效果也大同小異。
場景:比如我們需要具體知道每個元素出現的次數的時候,並且實現快速去重,使用Bag會非常便捷

對應的BagUtils,能提供BagUtils.EMPTY_BAG、synchronizedBag、unmodifiableBag等程式設計同步、只讀的快捷方法

BidiMap: 雙重Map

使用雙向對映,可以使用值查詢鍵,並且可以使用鍵輕鬆查詢值。(自然,它可以根絕key移除,也可以根據value移除)

該場景使用還是比較多的,比如一對一的對映關係,都可以使用這來儲存。如果你使用HashMap,那你得維護兩個,還是比較麻煩的

public interface BidiMap<K, V> extends IterableMap<K, V> {}

也是個普通的Map。繼承IterableMap增加了一種迭代方式,例子裡會有講解

DualHashBidiMap

底層維護兩個HashMap,一個正向,一個逆向來達到效果的。

    public DualHashBidiMap() {
        super(new HashMap<K, V>(), new HashMap<V, K>());
    }
	//把一個普通的Map轉成BidiMap
    public DualHashBidiMap(final Map<? extends K, ? extends V> map) {
        super(new HashMap<K, V>(), new HashMap<V, K>());
        putAll(map);
    }

看個示例:

    public static void main(String[] args) {
        BidiMap<String, String> map = new DualHashBidiMap<>();
        map.put("key1", "value1");
        map.put("key2", "value2");
        map.put("key3", "value3");

        //多出來的一種遍歷方式  還是分廠人性化的
        MapIterator<String, String> it = map.mapIterator();
        while (it.hasNext()) {
            it.next(); //此句話必須呼叫  返回的是key,效果同getKey,但必須呼叫
            System.out.println(it.getKey() + "---" + it.getValue());
        }

        System.out.println(map.get("key1")); //value1
        //根據value拿key
        System.out.println(map.getKey("value1")); //key1
        //這個方法是Map介面的
        System.out.println(map.getOrDefault("k", "defaultValue")); //defaultValue
        //返回一個逆序的檢視  注意是檢視
        BidiMap<String, String> inverseMap = map.inverseBidiMap();

        //根據key刪除
        inverseMap.remove("key1");
        //根據value刪除
        inverseMap.removeValue("value2");

        System.out.println(map); //{key1=value1, key2=value2, key3=value3}
        System.out.println(inverseMap); //{value2=key2, value1=key1, value3=key3}
    }
輸出:
key1---value1
key2---value2
key3---value3
value1
key1
defaultValue
{key1=value1, key2=value2, key3=value3}
{value2=key2, value1=key1, value3=key3}
DualLinkedHashBidiMap

底層採用兩個LinkedHashMap儲存,其餘同上

DualTreeBidiMap

底層採用兩個TreeMap儲存,其餘同上

它不要求key和value都是實現了比較器介面的,但是自己可以自定義比較器介面傳進去

TreeBidiMap

注意TreeBidiMap和DualTreeBidiMap的區別
TreeBidiMap採用是紅黑樹:Node。一個node就是put的一個鍵值對,這樣子來實現雙端的Map,底層的原理和上面的不一樣。這樣的好處:可以最大程度的節約儲存空間,從而提高效率。

firstKey、lastKey、nextKey等等都有一套自己的實現,處理效率還是蠻高的

備註:使用起來基本同上,因此例項省略

此Map要求key和value必須必須必須都實現了比較器介面

MultiKeyMap:多鍵Map

MultiKeyMap能夠解決我們平時可能遇到的一個痛點。
比如我們Map的key,可能是由多個欄位的值聯合決定的(有點類似聯合索引的意思),這個時候我們一般方案為:自己拼接字串,然後put進去。

但現在有了MultiKeyMap,我們可以非常優雅的解決這個問題:

     public static void main(String[] args) {
        // MultiKey功能很簡單:裝載多個key的一個物件
        MultiKey<String> multiKey = new MultiKey<>("a", "b");
        System.out.println(multiKey); //MultiKey[a, b]


        MultiKeyMap<String, String> multiKeyMap = new MultiKeyMap();

        // 多個鍵對應一個值 兩個key:name和NAME
        multiKeyMap.put("name", "NAME", "jianggujin");
        System.out.println(multiKeyMap); //{MultiKey[name, NAME]=jianggujin}
        System.out.println(multiKeyMap.get("name")); //null
        System.out.println(multiKeyMap.get("NAME")); //null
        System.out.println(multiKeyMap.get("name", "NAME")); //jianggujin

        //測試key覆蓋
        multiKeyMap.put("name", "shixiang", "cover");
        System.out.println(multiKeyMap); //{MultiKey[name, shixiang]=cover, MultiKey[name, NAME]=jianggujin}

        //這樣子  value值才會被覆蓋
        multiKeyMap.put("name", "NAME", "cover");
        System.out.println(multiKeyMap); //{MultiKey[name, shixiang]=cover, MultiKey[name, NAME]=cover}
    }

我們可以看到 name+NAME聯合確定了一個value值。這樣子,我們就可以非常優雅的處理這種情況,並且還不容易犯錯。

MultiKeyMap底層採用MultiKey作為普通Map的key,採用HashedMap儲存

HashedMap:
原始碼解釋:

* A <code>Map</code> implementation that is a general purpose alternative
 * to <code>HashMap</code>.
 * <p>
 * This implementation improves on the JDK1.4 HashMap by adding the
 * {@link org.apache.commons.collections4.MapIterator MapIterator}
 * functionality and many methods for subclassing.

簡單的說就是做了一個HashMap的通用替代品。讓也能使用IterableMap的迭代器那樣去使用和迭代Map了,沒什麼多餘的可以說明的。

MultiValuedMap:多值Map

一個key可對應多個值,內部的資料結構邏輯交給它去維護。
我們平時使用的Map<String,List<Long>>這種資料結構,就可以被這種代替,使用起來非常方便

ArrayListValuedHashMap

見名之意,values採用ArrayList來儲存

    public static void main(String[] args) {
        MultiValuedMap<String, String> map = new ArrayListValuedHashMap<>();

        map.put("key1", "value1");
        System.out.println(map); //{key1=[value1]}
        map.put("key1", "value11111");
        System.out.println(map); //{key1=[value1, value11111]}

        Collection<String> values = map.values();
        System.out.println(values); //[value1, value11111]

        //map.remove("key1");
        //System.out.println(map); //{}

        //強悍 可以直接幹掉values裡的某一個值
        map.removeMapping("key1", "value1");
        System.out.println(map); //{key1=[value11111]}

        //一次性放多個value
        map.putAll("key2", Arrays.asList("fang", "shi", "xiang"));
        System.out.println(map); //{key1=[value11111], key2=[fang, shi, xiang]}

		 //get方法  返回List
        Collection<String> collection = map.get("key2");
        MultiSet<String> keys = map.keys();
        Set<String> strings = map.keySet();
        System.out.println(keys); //[key1:1, key2:3] //後面的數字表示對應的value的數量
        System.out.println(strings); //[key1, key2]
        System.out.println(map.size()); //4 注意此處的size,是所有value的size 不是key的
        System.out.println(collection); //[fang, shi, xiang]

    }
HashSetValuedHashMap

基本用法同上,但是很顯然values用set去儲存。那就無序,但是去重了

這些多值的Map的key,都是採用HashMap的結構儲存的

MultiMapUtils提供一些基礎工具方法:emptyMultiValuedMap、unmodifiableMultiValuedMap、newListValuedHashMap、getValuesAsSet、getValuesAsList等等

MultiSet

set我們都知道,它是無序的,並且是不允許出現重複元素的。
但有些場景我們不需要順序,但是我們需要知道指定key出現的個數(比如每樣產品ID對應的剩餘數量這種統計資訊),那麼用MultiSet統計是一個很好的方案

HashMultiSet

底層實現原理為HashMap和MutableInteger

    public static void main(String[] args) {
        MultiSet<String> set = new HashMultiSet<>();

        set.add("fang");
        set.add("fang");
        set.add("shi");
        set.add("xiang");
        set.add("xiang");
        set.add("xiang");

        //我們發現此set是無序的,但是允許了重複元素的進入 並且記錄了總數
        System.out.println(set); //[shi:1, xiang:3, fang:2]
        System.out.println(set.size()); //6 = 1+3+2

        //批量新增  一些字就新增N個
        set.add("test",5);
        System.out.println(set); //[test:5, shi:1, xiang:3, fang:2]

        //移除方法
        System.out.println(set.getCount("fang")); //2
        set.remove("fang");
        //此移除 一次性只會移除一個
        System.out.println(set.getCount("fang")); //1
        //一次性全部移除 N個
        set.remove("xiang", set.getCount("xiang"));
        System.out.println(set.getCount("xiang")); //0  已經被全部移除了

        //removeAll 吧指定的key,全部移除
        set.removeAll(Arrays.asList("fang","shi","xiang","test"));
        System.out.println(set); //[]
    }
PredicatedMultiSet 使用較少,不做講解

BoundedCollection:有限制的集合

繼承自Collection,他提供了一些列的有用的實現

FixedSizeList:固定長度大小的List
    public static void main(String[] args) {
        FixedSizeList<String> c = FixedSizeList.fixedSizeList(Arrays.asList("fang", "shi", "xiang"));

        System.out.println(c); //[fang, shi, xiang]
        System.out.println(c.size()); //3

        //c.remove("fang"); //java.lang.UnsupportedOperationException: List is fixed size
        //c.add("fang"); //UnsupportedOperationException: List is fixed size

        //雖然不能增加和刪除 但可以改
        c.set(2, "heng");
        System.out.println(c); //[fang, shi, heng]
        System.out.println(c.get(2));

        //BoundedCollection提供的兩個方法
        c.isFull(); //如果是FixedSizeList 永遠返回true 因為大小肯定是固定的
        c.maxSize(); //值同size()方法
    }

UnmodifiableBoundedCollection:不能修改的List
CircularFifoQueue:環形的先進先出佇列

當達到指定的size長度後,符合FIfo先進先出的原則被環形覆蓋

    public static void main(String[] args) {
        CircularFifoQueue<String> c = new CircularFifoQueue<>(3);

        // 這個siz二和MaxSize就有差異化了
        System.out.println(c.size()); //0
        System.out.println(c.maxSize()); //3

        c.add("fang");
        c.add("shi");
        c.add("xiang");

        //我們發現雖然長度是3  但是因為迴圈的特性 再新增一個並不會報錯  而是
        c.add("heng");
        System.out.println(c); //[shi, xiang, heng]

        // 繼續新增 就會把前面的繼續擠出來 滿員了之後,符合先進先出的原則
        c.add("heng2");
        c.add("heng3");
        System.out.println(c); //[heng, heng2, heng3]
    }

BoundedMap:

FixedSizeMap
    public static void main(String[] args) {
        FixedSizeMap<String, String> m = FixedSizeMap.fixedSizeMap(new HashMap<String, String>() {{
            put("fang", "a");
            put("shi", "b");
            put("xiang", "c");
        }});

        System.out.println(m); //{shi=b, xiang=c, fang=a}
        System.out.println(m.size()); //3

        //不能再往裡面新增資料了
        //m.put("aaa", "aaa"); //java.lang.IllegalArgumentException: Cannot put new key/value pair - Map is fixed size

        //在我沒有改變長度的情況下 是可以修改的
        m.put("fang", "aaaaaaaa");
        System.out.println(m); //{shi=b, xiang=c, fang=aaaaaaaa}

    }
FixedSizeSortedMap

區別:底層採用SortedMap

LRUMap

底層是LRU演算法

LRU演算法的設計原則是:如果一個數據在最近一段時間沒有被訪問到,那麼在將來它被訪問的可能性也很小。也就是說,當限定的空間已存滿資料時,應當把最久沒有被訪問到的資料淘汰。

    public static void main(String[] args) {
        LRUMap<Object, Object> map = new LRUMap<>(3);

        System.out.println(map); //{}
        System.out.println(map.size()); //0
        System.out.println(map.maxSize()); //3
        System.out.println