Java实现计数器 Counter

试着实现一个更好的计数器.可以对输入的List进行计数.

最终实现版本使用泛型,使得可以对任意对象进行技术,但是在编写过程中,先以String为例.

那么计数这个行为的输入值是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新值,这明显是不科学的.

HashMapput方法,其实是有返回值的,会返回旧值.

这就意味着我们可以通过一次map操作来达到目的.

经过这样两次的优化,现在的方法为:

    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


本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏程序员果果

Eureka 源码分析之 Eureka Client

Eureka是一种基于REST(Representational State Transfer)的服务,主要用于AWS云,用于定位服务,以实现中间层服务器的负载...

17030
来自专栏javathings

场景:当所有的 Controller,都需要传入某个参数,代码如何写?

假设有个场景,每一个 controller 都需要接收一个共同的参数,那么除了在每一个方法中都加入这个参数之外,还有一个方法就是使用@ControllerAdv...

21650
来自专栏全栈技术Kojo

Spring的JavaConfig

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

13750
来自专栏python前行者

selenium Firefox 设置代理(认证)0

版权声明:如需转载本文章,请保留出处! https://blog.csdn.net/xc_z...

48230
来自专栏全栈技术Kojo

滚动条插件

最近项目中 老板需要 将聊天界面 的滚动条美化一下,所以主编海百了一波,发现一个样式和兼容性不错的滚动条插件 jQuery自定义滚动条样式插件mCustomSc...

27040
来自专栏毛毛v5

RGB24,RGB565,RGB444图片质量比较

以下图片,第二幅是RGB24的原图。第一幅是对第二幅进行RGB444的有损变换图,第三幅是对第二幅进行RGB565的有损变换图。其中肉眼很难分辨RGB565和R...

38460
来自专栏python前行者

selenium自动化测试资源整理(chrome,chromedriver、firefox,geckodriver)

版权声明:如需转载本文章,请保留出处! https://blog.csdn.net/xc_zhou...

18740
来自专栏程序员

Linux-网卡命名方式改变了-enp0s3,enp0s8

版权声明:可以转载,请标明作者和来源。原创首发http://kakazai.cn https://...

48540
来自专栏javathings

HttpServletRequest 使用@Autowired 注解会有问题吗?

SpringBoot 中,默认注入的对象是单例对象,那么,我们如果注入一个 HttpServletRequest,会有问题吗?因为每次请求,Request 是不...

85350
来自专栏算法修养

HttpClient参观记:.net core 2.2 对HttpClient到底做了神马

.net core 于 10月17日发布了 ASP.NET Core 2.2.0 -preview3,在这个版本中,我看到了一个很让我惊喜的新特性:HTTP C...

19720

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励