在阅读博客文章5减少Java垃圾收集开销的技巧时 ,我想起了一个名为Trove的小型Java收集库,该库“为Java提供了高速的常规和原始收集”。 我对应用Trove允许原始类型的集合而不是要求集合中的元素成为完整的引用对象的能力特别感兴趣。 我在这篇文章中会更详细地介绍Trove 。
JDK的标准集合尊重泛型,并要求对象引用为其元素,并且不允许在集合中存储基元。 甚至看起来似乎在标准JDK集合中放置基元的代码实际上也通过自动装箱将对象引用放置在集合中。 这种泛型方法的优点是能够使许多不同类型的对象使用相同的类和方法。 代价是即使可以存储更精简的基元,也需要存储完整的参考对象。
Trove库具有LPGL许可证 ,并且相对较小(不足10 MB),如“下载”页面的下一个屏幕快照所示:
小型下载文件不仅包含JAR格式的必需库。 它还包含文档和源。 库JAR本身(本例中trove-3.1a1.jar
)的大小约为2.5 MB。
Trove易于使用的原因之一是,它在其自己的集合的API中很大程度上模仿了JDK集合的接口。 下一个代码清单演示了如何将值添加到List
实现实质上是与使用JDK 7 List
(在这种情况下为ArrayList)还是Trove提供的TDoubleArrayList相同的API调用。
向JDK的ArrayList和Trove的TDoubleArrayList添加元素
/**
* Demonstrate standard JDK {@code ArrayList<Double>}
* with some JDK 8 functionality.
*/
public void demonstrateJdkArrayListForDoubles()
{
final ArrayList<Double> doubles = new ArrayList<>();
doubles.add(15.5);
doubles.add(24.4);
doubles.add(36.3);
doubles.add(67.6);
doubles.add(10.0);
out.println("JDK ArrayList<Double>:");
out.println("\tDoubles List: " + doubles);
out.println("\tMaximum double: " + doubles.stream().max(Double::compare));
out.println("\tMinimum double: " + doubles.stream().min(Double::compare));
out.println("\tSum of doubles: " + doubles.stream().mapToDouble(Double::doubleValue).sum());
}
/**
* Demonstrate use of TDoubleArrayList and show how
* similar using it is to using {@code ArrayList<Double>}.
*/
public void demonstrateTroveArrayListForDoubles()
{
// Demonstrate adding elements to TDoubleArrayList is
// exactly like adding elements to ArrayList<Double>.
final TDoubleArrayList doubles = new TDoubleArrayList();
doubles.add(15.5);
doubles.add(24.4);
doubles.add(36.3);
doubles.add(67.6);
doubles.add(10.0);
out.println("Trove TDoubleArrayList:"); // TDoubleArrayList overrides toString()
out.println("\tDoubles List: " + doubles);
out.println("\tMaximum double: " + doubles.max());
out.println("\tMinimum double: " + doubles.min());
out.println("\tSum of doubles: " + doubles.sum());
}
上面的代码清单还演示了使用数组列表的Trove实现访问双精度集合的最大值,最小值和总和是多么容易。 将这些集合写入特定原始数据类型(在这种情况下为double)的优点之一是,可以在实现中提供专门应用于该数据类型的方法。 尽管对于String
的集合或任意对象的集合返回最大值,最小值和总和来说并没有TDoubleArrayList
意义,但是对于专门用于双精度的集合(例如TDoubleArrayList
,这些方法的含义显而易见。 上面的清单确实指示了如何使用流使用JDK 8实现相同的功能。
当查看上面的代码时,一个细微的差异(由于自动装箱)可能并不明显,这是JDK实现ArrayList
存储引用Double
对象,而Trove TDoubleArrayList
实现存储原始double
。 Trove提供了各种数字类型的列表,集合和映射的实现,例如字节,字符,short,整数,long,float和double。
Trove提供的有趣的数据结构/集合之一是TDoubleArrayStack 。 在刚刚演示过的TDoubleArrayList的支持下, TDoubleArrayStack
并未在其API中公开用于添加元素的add
方法。 相反,它的方法反映了后进先出(LIFO)堆栈实现中可能期望的语义: push(double)添加, pop()访问和删除最近添加的条目,以及peek()查看最近添加的条目而不删除它。 下一个代码清单显示了此堆栈实现的应用程序。 还有其他数字数据类型的堆栈实现。
Trove的TDoubleArrayStack
/**
* Demonstrate Trove's Double Array Stack.
*
* Trove's TDoubleArrayStack allows access to its
* contents via push, pop, and peek.
*/
public void demonstrateTroveDoubleArrayStack()
{
final TDoubleArrayStack stack = new TDoubleArrayStack();
stack.push(15.5);
stack.push(17.3);
stack.push(16.6);
stack.push(2.2);
out.println("Trove Array Stack of Doubles");
out.println("\tPeek: " + stack.peek() + "; After Size: " + stack.size());
out.println("\tPop: " + stack.pop() + "; After Size: " + stack.size());
out.println("\tPeek: " + stack.peek() + "; After Size: " + stack.size());
}
尽管未在此处显示,但Trove在其gnu.trove.queue包中还支持Java原始类型的先进先出(FIFO)队列结构。 该软件包中的类提供了遵循队列语义的方法: offer , poll和peek 。
使用JDK集合时, java.util.Collections类提供了许多有用的功能。 Trove提供了java.util.Collections
功能的子集,用于在其自己的名为gnu.trove.TCollections的类中使用基于Trove的集合。 具体来说,在撰写本文时, TCollections
类为同步和未修改的Trove集合提供支持。 下一个代码清单演示了如何使用TCollections
,还演示了如何使用Trove集合,该集合面向的数据类型不是double
(在这种情况下为int
)和其他数据结构类型(链接列表)。
展示了TCollections和TIntLinkedList
/**
* Demonstrate one of Trove's "equivalent"s of the
* java.util.Collections class.
*/
public void demonstrateTroveCollectionsClass()
{
final TIntLinkedList integers = new TIntLinkedList();
integers.add(5);
integers.add(7);
integers.add(3);
integers.add(1);
final TIntList unmodifiableIntegers = TCollections.unmodifiableList(integers);
try
{
unmodifiableIntegers.add(15);
}
catch (Exception ex)
{
out.println("\tException caught: " + ex);
}
}
当希望对基于Trove的集合进行迭代时,可以通过传统的迭代器对其进行访问,如下面的代码清单所示。 尽管在此示例中,集合和关联的迭代器在long
值上起作用,但是Trove为Java的其他原始数据类型提供了类似的集合和迭代器。
使用Trove迭代器迭代Trove集合
/**
* Demonstrate "traditional" iteration of a
* Trove collection.
*/
public void demonstrateIterationWithIterator()
{
final TLongHashSet longs = new TLongHashSet();
longs.add(15);
longs.add(6);
longs.add(12);
longs.add(13);
longs.add(2);
TLongIterator longIterator = longs.iterator();
while (longIterator.hasNext())
{
final long longValue = longIterator.next();
out.println(longValue);
}
}
迭代Trove集合的另一种方法是使用Procedure 。 下面的两个代码清单对此进行了演示。 第一个清单演示了一个自定义的面向long
过程的过程,第二个清单演示了将该自定义过程通过其forEach方法应用于TLongLinkedList上的迭代。
使用Trove过程迭代Trove集合
/**
* Demonstrate iteration of a Trove collection
* using a Procedure.
*/
public void demonstrateIterationWithProcedure()
{
final TLongLinkedList longs = new TLongLinkedList();
longs.add(15);
longs.add(6);
longs.add(12);
longs.add(13);
longs.add(2);
longs.forEach(new StandardOutputLongProcedure());
}
先前迭代示例中使用的过程实现
package dustin.examples.trove;
import static java.lang.System.out;
import gnu.trove.procedure.TLongProcedure;
/**
* Simple implementation of TLongProcedure that
* iterates over Trove collection of {@code long}
* values and writes those values to standard
* output with one value per line.
*/
public class StandardOutputLongProcedure implements TLongProcedure
{
@Override
public boolean execute(long longValue)
{
out.println(longValue);
return true;
}
}
值得注意的是Trove集合倾向于提供forEachDescending方法以及以相反顺序提供迭代。
与GNU Trove相关的其他观察
- GNU Trove是一个提供“ Java的高速常规和原始集合”的库,不应与Trove混淆,后者是“ OpenStack的数据库即服务 ”。
- Trove集合和数据结构的名称都以“ T”开头(对于Trove)。 实际上,除了HashingStrategy , IdentityHashingStrategy和Version之外,Trove中的所有类和接口都以“ T” 开头 。
- Trove集合通常提供接受其基础数据类型的数组的构造函数,并提供
toArray()
方法以原始数组的形式提供其数据元素。 - Trove集合通常提供显式覆盖的
toString()
实现,这些实现允许像JDK集合一样轻松编写各个数据元素,并且与Java数组(需要Arrays.toString()方法)不同。 - 有关Trove的其他详细信息,请参见概述 , 常见问题解答和消息论坛 。 其他资源包括使用此Treasure Trove增强集合性能 , Java HashMap性能 , Java中的高性能库以及TROVE – Java高性能集合 。
- Trove的Java软件包通常按数据结构类型进行组织,对于同一数据包中给定的数据结构类型,所有特定于原始类型的实现都可以进行组织。 例如,软件包的名称类似gnu.trove.list , gnu.trove.set等。
- 因为每个Trove集合都特定于特定的原始数据类型,所以每个集合都不需要通用参数,并且没有与通用相关的问题(例如擦除)。 此方法还允许每个集合支持特定于该集合中存储的数据类型的方法。 例如,数字类型的集合可以提供
sum
方法,而特定于字符类型的集合可以提供grep
方法。
结论
在许多常见用途中,JDK提供的集合将表现良好,并且存储对象引用可能不是问题。 但是,在某些情况下,使用Trove集合(特别是存储基元而不是对象引用)的能力可能会提供必要的优势。 随着集合变大,在集合中存储基元而不是它们的等效对象引用的优势变得更加明显。
翻译自: https://www.javacodegeeks.com/2016/01/discovering-trove-java-primitives-collection-handling.html