【小家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