Java性能优化——HashCode的使用

背景

告警子系统监控4万个大网元所有端口的某些指标数据,根据阈值配置判断是否产生告警。采集——数据处理子系统每5分钟会主动采集24万次数据,发送24万条消息给告警子系统,这24万条消息涉及100万实体的数十个指标数据。告警子系统采用多节点部署方式分担压力,每个节点处理不同网元类型,不同实体,不同指标的数据。海量数据的过滤,必然会大量使用集合逻辑运算,使用不当,则会造成性能瓶颈。

例子

存在告警节点监控的实体动态变化,所以每个告警节点需要动态维护自己的监控列表,所以代码中会用到Collection.removeAll求差集的计算,计算出新增的实体,然后进一步计算出这些新增实体的历史平均值,方差等数据。

package com.coshaho.hash;

import java.util.ArrayList;
import java.util.List;

public class HashObject {

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

        // 2000长度的List求差集
        for(int i = 0; i < 2000; i++)
        {
            list1.add("" + i);
            list2.add("" + (i + 1));
        }
        long startTime = System.currentTimeMillis();
        list1.removeAll(list2);
        long endTime = System.currentTimeMillis();
        System.out.println("2000 list remove all cost: " + (endTime - startTime) + "ms.");

        // 10000长度的List求差集
        list1.clear();
        list2.clear();
        for(int i = 0; i < 10000; i++)
        {
            list1.add("" + i);
            list2.add("" + (i + 1));
        }
        startTime = System.currentTimeMillis();
        list1.removeAll(list2);
        endTime = System.currentTimeMillis();
        System.out.println("10000 list remove all cost: " + (endTime - startTime) + "ms.");

        // 50000长度的List求差集
        list1.clear();
        list2.clear();
        for(int i = 0; i < 50000; i++)
        {
            list1.add("" + i);
            list2.add("" + (i + 1));
        }
        startTime = System.currentTimeMillis();
        list1.removeAll(list2);
        endTime = System.currentTimeMillis();
        System.out.println("50000 list remove all cost: " + (endTime - startTime) + "ms.");
    }
}

上述代码我们分别对长度为2000,10000,50000的List进行了求差集的运算,耗时如下:

2000 list remove all cost: 46ms.
10000 list remove all cost: 1296ms.
50000 list remove all cost: 31028ms.

可以看到,数据量每增加5倍,ArrayList的求差集运算时间消耗增加30倍。当我们进行数十万元素的求差集运算时,时间消耗是我们不可承受的。

Equals

实体过滤中,为了找到我们关心的实体数据,我们必然会采用Collection.contains过滤实体ID,这里面会使用到字符串equals方法判断两个ID是否相等。对于我们来说,两个字符串相等的含义就是两个字符串长度一致,对应位置的字符编码相等。如果大量字符串两两比较都采用上述算法,那将会进行海量的运算,消耗大量性能。这个时候,HashCode的作用就显得尤其重要。

HashCode

HashCode是int类型。两个对象如果相等(equals为true),则HashCode必然相等;反之,HashCode不等的两个对象,equals必然为false。最优秀的Hash算法,不相等的对象HashCode都不相同,所有equals比较都只调用HashCode的恒等比较,那么计算量就大大减小了。实际上,任何一个Hash算法都不能达到上述要求(HashCode为int类型,说明HashCode取值范围有限,对象超过int取值范围个数,就必然出现不相等对象对应同一个HashCode值)。不相等的对象对应相同的HashCode称之为Hash冲突。

但是,好的Hash算法确出现Hash冲突的概率极低。比如0.01%的Hash冲突概率,这样就意味着,我们平均进行10000次不相等对象的equals比较,只会出现一次Hash冲突,也就意味着只需要调用一次equals主逻辑。我们在设计equals方法时,先比较两个对象HashCode是否相等,不相等则返回false,相等才进行equals主逻辑比较。

原始的HashCode方法是由虚拟机本地实现的,可能采用的对象地址进行运算。String复写了HashCode方法,代码如下:

    // Object
    public native int hashCode();

    // String
    public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

HashMap
HashMap是一个利用Key的HashCode进行散列存储的容器。它采用数组->链表->红黑树存储数据。结构如下图:

最简单的设想,计算一个Key在数组中的位置时,采用HashCode%数组长度求余计算则可(实际上JDK采用了更好的散列算法)。可以想象,相同的散列算法下,数组长度越长,Hash冲突概率越小,但是使用的空间越大。

JDK默认采用0.75为元素容量与数组长度的比例。默认初始化数组长度为16(采用2的n次方是考虑HashMap的扩容性能),当元素个数增加到16*0.75=12个时,数组长度会自动增加一倍,元素位置会被重新计算。在数据量巨大的情况下,我们初始化HashMap时应该考虑初始化足够的数组长度,特别是性能优先的情况下,我们还可以适当减小元素容量与数组长度的比例。HashMap部分源码:

    /**
     * The default initial capacity - MUST be a power of two.
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    /**
     * The maximum capacity, used if a higher value is implicitly specified
     * by either of the constructors with arguments.
     * MUST be a power of two <= 1<<30.
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * The load factor used when none specified in constructor.
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * Constructs an empty <tt>HashMap</tt> with the specified initial
     * capacity and load factor.
     *
     * @param  initialCapacity the initial capacity
     * @param  loadFactor      the load factor
     * @throws IllegalArgumentException if the initial capacity is negative
     *         or the load factor is nonpositive
     */
    public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);

        this.loadFactor = loadFactor;
        threshold = initialCapacity;
        init();
    }

    /**
     * Constructs an empty <tt>HashMap</tt> with the specified initial
     * capacity and the default load factor (0.75).
     *
     * @param  initialCapacity the initial capacity.
     * @throws IllegalArgumentException if the initial capacity is negative.
     */
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    /**
     * Constructs an empty <tt>HashMap</tt> with the default initial capacity
     * (16) and the default load factor (0.75).
     */
    public HashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    }

大数据集合运算性能考虑
通过上述分析,我们知道在性能优先的场景下,大数据集合运算一定要使用Hash集合(HashMap,HashSet,HashTable)存储数据。文章开头的集合求余运算,我们修改为使用HashSet.removeAll,代码如下:

package com.coshaho.hash;

import java.util.Collection;
import java.util.HashSet;

public class HashObject {

    public static void main(String[] args)
    {
        Collection<String> list1 = new HashSet<String>();
        Collection<String> list2 = new HashSet<String>();

        // 2000长度的List求差集
        for(int i = 0; i < 2000; i++)
        {
            list1.add("" + i);
            list2.add("" + (i + 1));
        }
        long startTime = System.currentTimeMillis();
        list1.removeAll(list2);
        long endTime = System.currentTimeMillis();
        System.out.println("2000 list remove all cost: " + (endTime - startTime) + "ms.");

        // 10000长度的List求差集
        list1.clear();
        list2.clear();
        for(int i = 0; i < 10000; i++)
        {
            list1.add("" + i);
            list2.add("" + (i + 1));
        }
        startTime = System.currentTimeMillis();
        list1.removeAll(list2);
        endTime = System.currentTimeMillis();
        System.out.println("10000 list remove all cost: " + (endTime - startTime) + "ms.");

        // 50000长度的List求差集
        list1.clear();
        list2.clear();
        for(int i = 0; i < 50000; i++)
        {
            list1.add("" + i);
            list2.add("" + (i + 1));
        }
        startTime = System.currentTimeMillis();
        list1.removeAll(list2);
        endTime = System.currentTimeMillis();
        System.out.println("50000 list remove all cost: " + (endTime - startTime) + "ms.");
    }
}

运行效果如下:

2000 list remove all cost: 31ms.
10000 list remove all cost: 0ms.
50000 list remove all cost: 16ms.
时间: 2024-12-12 22:13:02

Java性能优化——HashCode的使用的相关文章

Java性能优化技巧及实战

Java性能优化技巧及实战 关于Java代码的性能优化,是每个javaer都渴望掌握的本领,进而晋升为大牛的必经之路,但是对java的调优需要了解整个java的运行机制及底层调用细节,需要多看多读多写多试,并非一朝一夕之功.本文是近期笔者给公司员工内部做的一个培训,主要讲述在系统压测过程中出现的性能问题,以及如何在编码过程中提升代码的运行效率,需要掌握哪些实战技巧.片子里干货较多,也很具有实操性,因此发文出来,共享给大家(部分数据做了去除公司特征信息,见谅).(PS:由于原文是ppt,因此做了导

java性能优化技巧

一.通用篇 "通用篇"讨论的问题适合于大多数 Java应用. 1.1     new 1.1     new 11..11 不用 nneeww关键词创建类的实例 用new 关键词创建类的实例时,构造函数链中的所有构造函数都会被自动调用.但如 果一个对象实现了Cloneable 接口,我们可以调用它的clone()方法.clone()方法不会调用任 何类构造函数. 在使用设计模式(Design Pattern)的场合,如果用 Factory模式创建对象,则改用clone() 方法创建新的

JAVA性能优化的五种方式

一,JAVA性能优化之设计优化 设计优化处于性能优化手段的上层.它往往须要在软件开发之前进行.在软件开发之前,系统架构师应该就评估系统可能存在的各种潜在问题和技术难点,并给出合理的设计方案,因为软件设计和系统架构对软件总体设计质量有决定性的影响.所以,设计调优对系统的性能影响也是最大的,假设说,代码优化.JVM优化都是对系统微观层次的"量"的优化,那设计优化就是对系统"质"的优化. 设计优化的一大显著特征是:它能够规避某一个组件的性能问题,而是改良组件的实现;比方:

Java性能优化,不得不付诸实践的JVM

暂附贴图,详情稍后叙述,欢迎留言交流 图一.JVM知识体系(部分) 图二.通过jconsole监控jvm 图三.通过jvisualvm监控jvm Java性能优化,不得不付诸实践的JVM,布布扣,bubuko.com

Java 性能优化之 String 篇

原文:http://www.ibm.com/developerworks/cn/java/j-lo-optmizestring/ Java 性能优化之 String 篇 String 方法用于文本分析及大量字符串处理时会对内存性能造成不可低估的影响.我们在一个大文本数据分析的项目中(我们统计一个约 300MB 的 csv 文件中所有单词出现的次数)发现,用于存放结果的 Collection 占用了几百兆的内存,远远超出唯一单词总数 20000 个. 本文将通过分析 String 在 JVM 中的

java性能优化笔记(三)java程序优化

程序代码优化要点: 字符串优化:分析String源码,了解String常用方法,使用StringBuffer.StringBuilder. List.Map.Set优化:分析常用ArrayList.LinkedList.HashMap.TreeMap.LinkedHashMap.Set接口.集合常用方法优化. 使用NIO:Buffered.Channel操作和原理,使用零拷贝. 引用优化:强引用.弱引用.软引用.虚引用.WeekHashMap. 优化技巧:常用代码优化技巧.这里不一一罗列,请参考

java性能优化技巧二

之前整理过一篇java性能优化的博客,链接java性能优化一,今天补充几个 1. 谨慎对待Java的循环遍历 Java中的列表遍历可比它看起来要麻烦多了.就以下面两段代码为例: A: private final List<Bar> _bars; for(Bar bar : _bars) { //Do important stuff } B: private final List<Bar> _bars; for(int i = 0; i < _bars.size(); i++)

[原创]Java性能优化权威指南读书思维导图

[原创]Java性能优化权威指南读书思维导图 书名:Java性能优化权威指南 原书名:Java performance 作者: (美)Charlie Hunt    Binu John 译者: 柳飞 陆明刚 京东购书地址: http://item.jd.com/11407830.html 介绍:<Java性能优化权威指南>是Java应用性能调优的圣经,内容通俗易懂,介绍了大量的监控和测量工具,涉及各种硬件架构和操作系统.涵盖了如何构建实验.解释结果以及如何采取行动等技巧. 现在自己是越来越忙,

15分钟了解Java性能优化以及一切你想知道的(转)

15分钟了解Java性能优化以及一切你想知道的 http://blog.csdn.net/kwensen/article/details/17302371