java 集合类中的性能PK
2015-03-21 19:02
176 查看
性能观察: Trove 集合类
更小、更普通、更易上手Trove 类。
转自 http://www.ibm.com/developerworks/cn/java/j-perf09284.html
0
![](http://dw1.s81c.com/developerworks/i/v17/dw-cmts-arrow.png)
评论:
Jack
Shirazi(jack@JavaPerformanceTuning.com),
董事, JavaPerformanceTuning.com
Kirk
Pepperdine(kirk@JavaPerformanceTuning.com),
CTO, JavaPerformanceTuning.com
2004 年 9 月 28 日
![](http://www.ibm.com/i/c.gif)
内容
![](http://dw1.s81c.com/developerworks/i/bluemix-graphic.png)
在 IBM Bluemix 云平台上开发并部署您的下一个应用。
现在就开始免费试用
几年前,也就是 2001 年后期,我们收到了 Eric Friedman 的电子邮件。他说他已经决定构造一组开放源代码的集合类,用于取代
java.util类。而且这些类执行速度更快、更轻巧、更灵活。是的,Eric
要创建 Six Million Dollar集合!
事实上,当时我们对是否能够直接取代 Java 平台的集合类并不特别感兴趣。Joshua Bloch 的工作做得很好,创建的通用集合框架非常有用、快捷而且能够扩展。但是通用与高效常常是互斥的两个目标。创建 专门的 集合类来处理
int或
boolean这类基本类型,对于我们而言即使在
2001 年也不是多么不寻常的事。否则,在 Java 平台通用集合框架中存储基本类型的集合都需要通过
Integer和
Boolean之类的包装器类进行包装和解包,这样做不但非常麻烦,而且大量的包装和解包很容易成为性能瓶颈。
这样说有点离题了。
美梦还是恶梦?
Eric 建议创建不通过包装和解包就直接处理数据原型的集合类。这是性能优化人员的梦想,而且可以改进类型安全性,但却是架构师的恶梦。比方说 Map吧,它包括键和值,每个键或者值都可以是
Object或者
8 种基本数据类型之一:
byte、
short、
char、
int、
long、
float、
double和
boolean。
这样就有 9 种键和 9 种值,组成 81 种不同类型的
Map!更糟的是,只要其中一个存在缺陷,就意味着其他
80 种很可能也存在缺陷,而能够共用的代码很有限,因为为了有效地处理每种数据类型,需要分别实现算法,从而,维护的工作量太大了。我非常钦佩 Eric,但是并不期望这种集合能够很快出现,因为开发和调试需要大量的时间。
在 2001 年,我们为 Eric 提供的支持很少。在“Java Performance Tuning” 12 号时事通讯中(请参阅 参考资料),我们看到了
Eric 关于 Trove 项目的宣告。除了偶尔的 bug 修复外,Eric 都是一个人在所有这些不同的集合版本中埋头苦干。他做了一项了不起的工作,今天我们可以欣然享受他艰苦劳作的成果了。Trove 集合可以使用了,而且确实非常有用。
回页首
许许多多的集合
好了,上面是一段小插曲,您到底能够从 Trove 中得到什么呢?许许多多的高效集合。除了那 81 种不同的 HashMap版本之外(比如
TIntIntHashMap、
TIntObjectHashMap等),还有
List和
Set类可以存储基本类型(如
TIntHashSet、
TFloatArrayList等)。这种体系结构甚至允许您插入自己的散列策略,以便选择对您的数据集而言可能更有效(或者更灵活)的算法。当然,支持灵活的散列算法也要付出性能代价
—— 如果散列映射成为瓶颈,很可能是因为大量地使用它,这意味着散列函数被反复不断地调用。因此,散列函数中每一点多余的开销都可能成为瓶颈。(顺便说一下,如果散列函数足够简单,JIT 编译器就可能将其编译成内联函数,这样在支持灵活的散列策略的同时又不会带来额外的开销 —— 真是免费的午餐!)
此外,Eric 还实现了 Smalltalk 范型,就是说集合能够对自己的元素执行过程。这种处理不同于 Java 平台中一般的集合元素遍历,需要稍微讨论一下。在 Java 编程中,如果需要处理集合中的所有元素,通常要有一个迭代器(Iterator)来访问和处理每个元素,如清单 1 所示:
清单 1. 集合的遍历
Iterator iterator = collection.iterator(); while (iterator.hasNext()) { Object o = iterator.next(); ... //do something with 'o' }
Trove 也支持这种迭代模式,但是还支持向集合传递过程,然后集合在内部迭代每个元素,依次将该过程应用于每个元素。尽管这种技术不一定能提高效率 —— 如果迭代器对象不能像集合本身那样高效地遍历元素,则可以提高效率 —— 但是有助于提高清晰性和灵活性。清单 2 是上述例子改写后的版本:
清单 2. 向集合传递过程
Procedure procedure = new DoSomethingWithOProcedure(); collection.forEach(procedure);
回页首
开放选址
Trove 映射是采用开放选址而不是链接来实现的。在 Java 核心集合类中,多数映射都是使用链接实现,就是说如果多个键映射到表中的同一索引位置,则索引位置保存一个链表,其中存放映射到该位置的所有元素。开放选址映射则假设表中邻近的位置存在没有使用的索引。如果目标位置已经被占用,映射实现就查看附近的几个位置找到一个没有使用的位置。这种方法不需要链表节点,因此 Trove 映射和相同的核心集合类相比占用的内存更少。使用开放选址必须保证有足够的空闲索引,否则可能影响性能。(Trove 保持装载因子小于 0.5。)否则的话,开放选址的效率与链接基本相当,但多数情况下要好于后者。开放选址的另一个优点是实现中不需要链表节点对象,链表节点需要靠链接来实现。为什么特别强调这一点呢?基本上每个 JVM 版本都会改进对象创建和无用单元回收的性能,但是较多的对象总会带来更多的开销。对于任何特定的问题,Java 编程中能够减少对象数量的解决方案通常都有更好的效率,这是一个标准的性能权衡问题,每个有经验的性能优化人员都知道。开放选址使用更少的对象来维护映射结构,这意味着 Trove 映射在多数情况下比核心 Java 映射更有效,也更小。值得一提的是,最近越来越多的 Java 核心
Map实现开始使用开放选址(比如
IdentityHashMap类)。现有的其他核心
Java
Map实现最终也可能改为使用开放选址,虽然我们可能不那么关心,因为如果需要开放选址的实现,使用
Trove 就可以了。
Trove 本身提供了一个小的基准测试,用于跟核心 Java 集合进行性能比较。Dion Almaer 所进行的独立比较测试(请参阅 参考资料)说明了
Trove 在性能和大小上的优势。结果表明,Trove 执行
Map.put()操作的速度要快三倍,而占用的内存只有核心
Map所需要的一半。
回页首
自动装箱
Java 5.0 平台引入了泛化(generics)和自动装箱机制(Autoboxing)。这意味着您可以编写下面这样的代码:清单 3. 使用自动装箱机制
mymap.put("201",201); mymap.put("202",202); int sum = mymap.get("201") + mymap.get("202") mymap.put("sum",sum);
注意,通过使用泛化,
mymap甚至可以限制为仅保存
Integer对象。但是也要注意在幕后仍然会进行自动装箱,清单
3 代码中的所有
int都在运行时使用
Integer对象包装(通常每次都使用新的对象),并使用存取方法来访问(虽然
JIT 编译器可能将访问编译成内联函数)。
需要强调指出:Java 5.0 平台中的自动装箱可能造成高效的假象。在代码层上看起来基本数据类型的使用效率很高,但是在运行时,基本数据包装器类型的使用效率则会变得很低。Heinz Kabutz 博士最近撰文表明,如果不小心在循环中使用自动装箱机制,有可能使性能降低一个数量级(请参阅 参考资料)。
自动装箱的效率比不上使用直接保存基本数据类型的集合,比如值为
int、键为
Object的
Trove
TObjectIntHashMap类型不需要包装和解包
int值。对于其他基本数据类型和集合组合,Trove
都有等价的集合,如
TIntIntHashMap(
int键和
int值)、
TLongArrayList(保存
long的列表)、
TIntSet(保存
int的集合)等等。Dion
Almaer 对
TIntIntHashMap和
HashMap的比较测试也表明
Trove 的速度要快一个数量级。
回页首
结束语
将 Trove 加到您的项目中吧。我们并不建议在每个需要集合的地方都使用 Trove,因为 Trove 毕竟还年轻,使用的还不多,所以很可能会出现一些 bug。但是 Trove 提供了一种选择,如果需要效率更高的类,就可以使用它,当您在调优应用程序时会遇到这种情况。
相关文章推荐
- 三大编程语言的性能PK--Java, C/C++和Ruby
- Android开发之Java集合类性能分析
- Android开发之Java集合类性能分析
- Android开发之Java集合类性能分析
- java 常用写文件方式性能PK
- Android开发之Java集合类性能分析
- 三大编程语言的性能PK--Java, C/C++和Ruby
- Java,C++和Ruby的性能PK(续文)--关于凸包算法(convex hull)的效率
- java集合类性能比较
- Android开发之Java集合类性能分析
- Java:集合类性能分析
- 三大编程语言性能PK:Java, C/C++和Ruby
- Android开发之Java集合类性能分析
- Node、PHP、Java 和 Go 服务端 I/O 性能PK
- Java:集合类性能分析 推荐
- 三大编程语言的性能PK--Java, C/C++和Ruby (原文最终修订于 2006-09-05 下午06:19:40)
- Java:集合类性能分析
- Node、PHP、Java 和 Go 服务端 I/O 性能PK
- Java:集合类性能分析
- Android开发之Java集合类性能分析