Java實現計數器-Counter

試着實現一個更好的計數器.能夠對輸入的List進行計數.java

最終實現版本使用泛型,使得能夠對任意對象進行技術,可是在編寫過程當中,先以String爲例.bash

那麼計數這個行爲的輸入值是List<String>,輸出值爲Map<String,Integer>. 這裏不是強行要求Integer的,只要可以標識數量便可.多線程

簡單版本

直接隨手寫一個:學習

HashMap<String,Integer> c = new HashMap<>();
        stringList.forEach(per->{
            c.put(per, c.getOrDefault(per, 0) + 1);//步驟1
        });
        return c;
    }
複製代碼

這裏面有幾個點:測試

  1. Integer是一個不可變的類,所以,在步驟1中發生了,取到當前數字,對其加一輩子成新的Integer對象,將這個對象放進map裏面.頻繁的建立中間對象,浪費.
  2. 對於須要計數的每個值,進行了兩次map的操做,第一次獲取其當前次數,第二次put加一以後的次數.

可變Integer

先解決第一個問題,封裝一個可變的Integer類或者使用AtomicInteger. 在沒有多線程的要求下,本身封裝一個:優化

public static final class MutableInteger {
        private int val;

        public MutableInteger(int val) {
            this.val = val;
        }

        public int get() {
            return this.val;
        }

        public void set(int val) {
            this.val = val;
        }

        public String toString() {
            return Integer.toString(val);
        }
    }
複製代碼

對map的操做減小

對於每個字符串,都須要get確認,而後put新值,這明顯是不科學的.this

HashMapput方法,實際上是有返回值的,會返回舊值.url

這就意味着咱們能夠經過一次map操做來達到目的.spa

通過這樣兩次的優化,如今的方法爲:線程

public static Map<String, MutableInteger> count2(List<String> strings) {
        HashMap<String, MutableInteger> c = new HashMap<>();
        strings.forEach(per -> {
            MutableInteger init = new MutableInteger(1);
            MutableInteger last = c.put(per, init);
            if (last != null) {
                init.set(last.get() + 1);
            }
        });
        return c;
    }
複製代碼

簡單測試一下:

public static void main(String[] args) {
        List<String> list = new ArrayList<>();

        String[] ss = {"my", "aa", "cc", "aa", "cc", "b", "w", "sssssa", "10", "10"};

        for (int i = 0; i < 100000000; i++) {
            list.add(ss[i % 10]);
        }
        long s = System.currentTimeMillis();
        System.out.println(count1(list));
        System.out.println(System.currentTimeMillis() - s);

        long s1 = System.currentTimeMillis();
        System.out.println(count2(list));
        System.out.println(System.currentTimeMillis() - s1);

    }
複製代碼

測試結果以下:

{aa=20000000, cc=20000000, b=10000000, w=10000000, sssssa=10000000, my=10000000, 10=20000000}
4234
{aa=20000000, cc=20000000, b=10000000, w=10000000, sssssa=10000000, my=10000000, 10=20000000}
951
複製代碼

能夠看到結果很是明顯,效率提升了4倍.

NOTE: 這個測試明顯是有偏向的,由於我這個1億條數據,只有幾種,因此數據重複率很是高.可是平常使用中數據重複率不會有這麼誇張. 可是構建1億條重複率不高的測試數據,太麻煩了.

分析

其實起做用比較大的是可變的Integer類.

而map的操做咱們知道,取和放到時O(1)的.因此這個的提高不是特別的大.經測試,修改成兩次操做,僅增長80ms.

最終代碼(使用泛型實現通用類)

實現瞭如下幾個API:

  • add(T): 向計數器添加一個值.
  • addAll(List): 一次性添加多個值.以List的形式.
  • get(T): 返回該值目前的數量.
  • getALl(): 返回該計數器目前全部的計數信息.形式爲,Map<T,Integer>
package daily.counter;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/** * Created by pfliu on 2019/04/21. */
public class Counter<T extends Object> {

    private HashMap<T, MutableInteger> c = new HashMap<>();
    
    public void add(T t) {
        MutableInteger init = new MutableInteger(1);
        MutableInteger last = c.put(t, init);
        if (last != null) {
            init.set(last.get() + 1);
        }
    }

    public void addAll(List<T> list) {
        list.forEach(this::add);
    }

    public int get(T t) {
        return c.get(t).val;
    }

    public Map<T, Integer> getAll() {
        Map<T, Integer> ret = new HashMap<>();
        c.forEach((key, value) -> ret.put(key, value.val));
        return ret;
    }

    public static final class MutableInteger {

        private int val;

        MutableInteger(int val) {
            this.val = val;
        }

        public int get() {
            return this.val;
        }

        void set(int i) {
            this.val = i;
        }
    }
}
複製代碼

固然你徹底不用本身實現,網上一大把已經實現的.

可是本身思考一下爲何要這樣實現,仍是有不少的好處的.


完。



ChangeLog

2019-04-22 完成

以上皆爲我的所思所得,若有錯誤歡迎評論區指正。

歡迎轉載,煩請署名並保留原文連接。

聯繫郵箱:huyanshi2580@gmail.com

更多學習筆記見我的博客------>呼延十

相關文章
相關標籤/搜索