背景

告警子系统监控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.

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

  1. Java 性能优化手册 — 提高 Java 代码性能的各种技巧

    转载: Java 性能优化手册 - 提高 Java 代码性能的各种技巧 Java 6,7,8 中的 String.intern - 字符串池 这篇文章将要讨论 Java 6 中是如何实现 String ...

  2. 【转】10种简单的Java性能优化

    10种简单的Java性能优化 2015/06/23 | 分类: 基础技术 | 14 条评论 | 标签: 性能优化 分享到: 本文由 ImportNew - 一直在路上 翻译自 jaxenter.欢迎加 ...

  3. Java 性能优化之 String 篇

    原文:http://www.ibm.com/developerworks/cn/java/j-lo-optmizestring/ Java 性能优化之 String 篇 String 方法用于文本分析 ...

  4. java 性能优化(代码优化)

    参考博文: java 性能优化:35 个小细节,让你提升 java 代码的运行效率

  5. 读书笔记系列之java性能优化权威指南 一 第一章

    主题:java性能优化权威指南 pdf 版本:英文版 Java Performance Tuning 忽略:(0~24页)Performance+Acknowledge 1.Strategies, A ...

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

    [原创]Java性能优化权威指南读书思维导图 书名:Java性能优化权威指南 原书名:Java performance 作者: (美)Charlie Hunt    Binu John 译者: 柳飞 ...

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

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

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

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

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

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

随机推荐

  1. 关于hp proliant sl210t服务器raid 1阵列配置

    hp proliant sl210t服务器,一般都会带有两个阵列卡 一个服务器自带的Dynamic Smart Array B120i RAID控制器,一个为Slot卡槽上的Smart Array P ...

  2. iOS 禁止手势滑动翻页

    - (void)viewDidAppear:(BOOL)animated{ [super viewDidAppear:animated]; // 禁用 iOS7 返回手势 if ([self.navi ...

  3. python开发环境搭建(windows+python2.7.5+django1.5.4)【原创】

    先插入一条广告,博主新开了一家淘宝店,经营自己纯手工做的发饰,新店开业,只为信誉!需要的亲们可以光顾一下!谢谢大家的支持!店名: 小鱼尼莫手工饰品店经营: 发饰.头花.发夹.耳环等(手工制作)网店: ...

  4. Web开发中需要了解的东西【转载】

    在StackExchange上有人问了这样一个问题:What should every programmer know about web development?(关于Web开发,什么是所有程序员需 ...

  5. HOJ 2156 &POJ 2978 Colored stones(线性动规)

    Colored stones Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 1759 Accepted: 829 Descrip ...

  6. CSU 1804 - 有向无环图 - [(类似于)树形DP]

    题目链接:http://acm.csu.edu.cn/csuoj/problemset/problem?pid=1804 Bobo 有一个 n 个点,m 条边的有向无环图(即对于任意点 v,不存在从点 ...

  7. UVA 11776 - Oh Your Royal Greediness! - [贪心/模拟]

    题目链接:https://cn.vjudge.net/problem/UVA-11776 题意: 给出数字n(0<=n<=1000),代表有n个农民,接下来有n行,每行两个数字S和E代表这 ...

  8. UOJ #146. 【NOIP2015】信息传递 连通分量 tarjan模板题

    http://uoj.ac/problem/146 题解:强连通分量 tarjan模板题.同时试了一下codeblock #include<bits/stdc++.h> using nam ...

  9. tun笔记

    https://www.kernel.org/doc/Documentation/networking/tuntap.txt 虚拟网卡 TUN/TAP 驱动程序设计原理 https://www.ibm ...

  10. redux+react-redux+示例的快速上手体验

    刚学习redux的同学提供一些可供参考的例子. 之前用vue用了很久 vue的语法糖用起来是真的舒服  react 其实毕竟他们都是类似的框架, 虽然语法大不同, 但是有些地方的思想还是很像的, 废话 ...