Map值增加的最高效的方法:只一次搜索鍵
這個(gè)問題初看起來可能會(huì)比較基礎(chǔ),但卻在論壇里頻繁地討論。在這篇文章中,我將會(huì)討論一種只在 Map 中搜索一次鍵的方法。
讓我們看一個(gè)例子。假設(shè)我正在創(chuàng)建一個(gè)詞頻表,使用 Map 來保存,每一個(gè)鍵都是一個(gè)待統(tǒng)計(jì)的詞而值則是其頻率(每次添加詞的時(shí)候都遞增)。一個(gè)直接的實(shí)現(xiàn)方法是:
- int count = map.containsKey(string) ? map.get(string) : 0;
- map.put(string, count + 1);
由于這段代碼包含了3 個(gè)潛在的浪費(fèi)時(shí)間的操作(containsKey()、get()、put()),所以效率不會(huì)很高。每次執(zhí)行統(tǒng)計(jì)操作,都會(huì)搜索 Map 中的鍵?,F(xiàn)在,我們以此為例子,看如何為 Map 值增加提高性能。
Integer VS MutableInteger VS AtomicInteger
我們不得不調(diào)用三次消耗性能的操作,一個(gè)重要的原因就是使用了Integer來計(jì)數(shù)。在Java中,Integer是不可以被改變的。它在構(gòu)造完成以后就會(huì)阻止我們修改其整數(shù)值。因而,為了讓計(jì)數(shù)器增長(zhǎng),我們就不得不從map中先獲得整數(shù),然后再創(chuàng)建另外一個(gè)新的整數(shù),新增并且添加回map中
需要使得計(jì)數(shù)器可修改,有幾種方法。其中一個(gè)就是簡(jiǎn)單的創(chuàng)建你自己的MutableInteger,想我在下面展示的這樣:
- public class MutableInteger {
- private int val;
- public MutableInteger(int val) {
- this.val = val;
- }
- public int get() {
- return val;
- }
- public void set(int val) {
- this.val = val;
- }
- }
另外一種方法也許就是使用Java中AtomicInteger了,它被用于諸如需要原子增長(zhǎng)計(jì)數(shù)器的應(yīng)用程序之中。而把AtomicInteger作為***是因?yàn)槟銜?huì)想要在對(duì)整數(shù)進(jìn)行操作的時(shí)候?qū)崿F(xiàn)線程安全。因此它不能作為Integer的替代。基于此,如果線程安全并不是你的項(xiàng)目一個(gè)重要的考慮事項(xiàng),那我就不會(huì)推薦AtomicInteger。
只一次搜索鍵
在使用MutableInteger之后,我們改變上面的代碼如下:
- if (map.containsKey(string)) {
- MutableInteger count = map.get(string);
- count.set(count.get() + 1);
- } else {
- map.put(string, new MutableInteger(1));
- }
或者
- MutableInteger count = map.get(string);
- if (count != null) {
- count.set(count.get() + 1);
- } else {
- map.put(string, new MutableInteger(1));
- }
在最糟糕的時(shí)候,當(dāng)鍵還沒有出現(xiàn)過,這段代碼會(huì)執(zhí)行2個(gè)搜索:一次是獲取MutableInteger,另一次是是設(shè)值。這比前面的那段代碼更優(yōu)化。但我們不應(yīng)該僅僅滿足現(xiàn)在,如果你查看了[Map.putt()]方法。(http://docs.oracle.com/javase/7/docs/api/java/util/Map.html#put(K, V)) 在java文檔中的方法。你會(huì)發(fā)現(xiàn)這個(gè)方法會(huì)返回先前與之關(guān)聯(lián)鍵的值。這就意味著我們可以合并重新獲取對(duì)象和設(shè)置方法。然而,也許你會(huì)好奇:如果我們不首先獲得計(jì)數(shù)器,我們?cè)趺磥碓O(shè)置新的計(jì)數(shù)器呢?現(xiàn)在我們終于碰到了這篇文章中最棘手的部分:我們可以簡(jiǎn)單的使用零頻率計(jì)數(shù)器!
- public int incrementCount(K key, int count) {
- MutableInteger tmpCount = new MutableInteger(0);
- MutableInteger oldCount = map.put(key, tmpCount);
- if (oldCount != null) {
- count += oldCount.get();
- }
- tmpCount.set(count);
- return count;
- }
把所有必要操作放入到類中看起來對(duì)以后的使用非常有用。因此我創(chuàng)建了一個(gè)Counter類, 并聲明它為公共可用。在這個(gè)Counter中定義了一個(gè)集合,用于記錄一個(gè)對(duì)象在集合中出現(xiàn)的次數(shù)。假如你有一個(gè)包含集合{a, a, b, c}的計(jì)數(shù)器。調(diào)用getCount()方法,那么“a”將會(huì)返回2,然而調(diào)用keySet()將會(huì)返回{a,b,c}。這個(gè)類和Map的工作原理很像,但是它卻比Map有更簡(jiǎn)單的方法。
獲得/設(shè)置/遞增計(jì)數(shù)對(duì)象并計(jì)算各種函數(shù)的計(jì)數(shù)。Counter的構(gòu)造器和addAll()方法可用來復(fù)制另一個(gè)計(jì)數(shù)器的內(nèi)容??梢酝ㄟ^ IntCounter和 AbstractMapBag對(duì)Counter類進(jìn)行修改。
Counter中一些被強(qiáng)調(diào)的操作方法如下:
- incrementCount()和 decrementCount():根據(jù)給定的鍵值對(duì)當(dāng)前的計(jì)數(shù)增加/減去給定的數(shù)值。如果這個(gè)鍵值在以前沒有出現(xiàn)過,那么可以斷定它的計(jì)數(shù)是0,增加計(jì)數(shù)的方法將會(huì)設(shè)置它的計(jì)數(shù)到給定的值。減值的方法將會(huì)把它的值設(shè)置為-1。
- getCount():返回給定鍵值當(dāng)前的計(jì)數(shù),如果以前沒有出現(xiàn)過就返回0。
- keysAt(), keysAbove()和keysBelow():返回給定鍵值的計(jì)數(shù),計(jì)數(shù)必須是與給定的閾值相等,大于或者小于。這個(gè)集合可能有0個(gè)元素,但是它不會(huì)為空。
- argmin() 和 argmax():查找并返回在這個(gè)計(jì)數(shù)器中最小或者***計(jì)數(shù)的鍵值。如果有多個(gè)最小或者***計(jì)數(shù),那么就隨機(jī)返回一個(gè)值。當(dāng)Counter為空的時(shí)候返回空值。
英文原文:Most efficient way to increment a Map value in Java — Only search the key once