随笔 - 909  文章 - 0  评论 - 16  阅读 - 60万

在java中高效的计数器

在编程中,经常会用到HashMap作为计数器,本文简单介绍三种实现方式

第一种,最直观的计数器。

 

public void naiveCounter(String sArr[]) {
        HashMap<String, Integer> counter = new HashMap<String, Integer>();
        for (String a : sArr) {
            if (counter.containsKey(a)) {
                int oldValue = counter.get(a);
                counter.put(a, oldValue + 1);
            } else {
                counter.put(a, 1);
            }
        }
    }

在这种方式中,每次循环都去检查Map中是否包含Key,如果包含则将原值+1再保存,如果不存在,则直接保存1.这种方式是最简单直接的一种方式,但是并不是最有效的方式,其低效的原因:

 

1、如果已经存在某个key(a),containsKey()和get()方法会扫描Map两次

2、由于Integer是不可变对象,因此每次循环,都会创建一个新的对象放到Map中。

第二种,比较好的计数器

由于上述原因,自然可以想到创建一个可变的Integer对象,这样可以避免创建过多的Integer对象,

可变的Integer对象定义如下:

 

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;
        }

        public String toString() {
            return Integer.toString(val);
        }
    }

然后将上面的计数器优化一下,改成下面这张格式

 

 

public void betterCounter(String[] sArr) {
        HashMap<String, MutableInteger> newCounter = new HashMap<String, MutableInteger>();
        for (String a : sArr) {
            if (newCounter.containsKey(a)) {
                MutableInteger oldValue = newCounter.get(a);
                oldValue.set(oldValue.get() + 1);
            } else {
                newCounter.put(a, new MutableInteger(1));
            }
        }
    }

第三种,高效的计数器

 

上面第二种已经解决了创建过多Integer对象的问题,但是扫描两次Map,

其实在HashMap中,存在一个能够返回当前值的方法(HashMap.put(key,value)),

在上面的基础上,我们可以通过对原来的引用进行更新,而不必扫描两次Map

 

public void efficientCounter(String[] sArr) {
        HashMap<String, MutableInteger> efficientCounter = new HashMap<String, MutableInteger>();
        for (String a : sArr) {
            MutableInteger initValue = new MutableInteger(1);
            MutableInteger oldValue = efficientCounter.put(a, initValue);
             //MutableInteger是可变的,此处只需判断之前是否为空,
             // 如果非空,则更新MutableInteger的引用即可
            if (oldValue != null) {
                initValue.set(oldValue.get() + 1);
            }
        }
    }


下面是经过500W次操作后,三者方法所消耗的时间

 

start test naiveCounter
end test naiveCounter,time is:5629


start test betterCounter
end test betterCounter,time is:5030


start test efficientCounter
end test efficientCounter,time is:4418

可以看出,最后一种确实是三种里面最高效的方式

 

posted on   我的小人生  阅读(2803)  评论(1编辑  收藏  举报
编辑推荐:
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 字符编码:从基础到乱码解决
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

点击右上角即可分享
微信分享提示