有什么适用于Java的良好持久集合框架?

35

所谓持久化集合是指像clojure中的那些集合。

举个例子,我有一个列表,元素为(a,b,c)。 对于普通列表,如果我添加d,那么原始列表将具有元素(a,b,c,d)。 对于持久化列表,当我调用list.add(d)时,我会得到一个新列表,其中包含(a,b,c,d)。 但是,实现尽可能在列表之间共享元素,因此它比仅返回原始列表的副本更节省内存。 它还有不可变性的优点(如果我持有对原始列表的引用,则它始终会返回原始3个元素)。

这些都可以在其他地方更好地解释(例如,http://en.wikipedia.org/wiki/Persistent_data_structure)。

总之,我的问题是......提供此功能以在Java中使用的最佳库是什么? 我是否可以以某种方式使用clojure集合(而不是直接使用clojure)?


2
你是说像Java中的LinkedList吗?我现在明白你的意思了,可以看看http://functionaljava.org/,或许能帮到你。 - Matt
你知道functionaljava.org中是否实现了我上面提到的那种结构吗?(我知道这有点无礼,否则我会去查看源代码的) - bm212
关于我之前的评论 - 是的,它可以实现,但如果可能的话,我更愿意使用Clojure的(因为我知道它们经过了实战考验)。 - bm212
13个回答

17

直接使用Clojure中的内容。即使你可能不想使用该语言本身,你仍然可以直接使用持久化集合,因为它们都只是Java类。

import clojure.lang.PersistentHashMap;
import clojure.lang.IPersistentMap;

IPersistentMap map = PersistentHashMap.create("key1", "value1");

assert map.get("key1").equals("value1");
IPersistentMap map2 = map.assoc("key1", "value1");

assert map2 != map;
assert map2.get("key1").equals("value1");

(免责声明:我实际上没有编译过那段代码 :)

缺点是这些集合没有类型,即它们没有泛型。


2
缺乏泛型将是一个问题,唉,我会研究一下的。否则,这是一个非常好的建议,特别是因为它们被广泛使用。 - bm212
断言 map2 != map 实际上是失败的。似乎存在一些内部优化,如果可能的话会重用现有的地图。 - vitaly
另外,应使用.valAt()而不是.get() - vitaly
这个对我起作用: IPersistentMap map = PersistentHashMap.create("key1", "value1"); assert map.valAt("key1").equals("value1"); IPersistentMap map2 = map.assoc("key1", "value2"); assert map2 != map; assert map2.valAt("key1").equals("value2"); - vitaly

12

是的,我偶然发现了pcollections。 有人知道它们在生产系统中是否被广泛使用(这是为工作项目)?也许应该提出一个单独的问题。 - bm212
不确定为什么我没有想到直接使用Clojure的集合,这可能是一个更好的整体想法。 - bm212
1
使用Clojure的唯一问题是将整个jar包引入项目中(除了泛型,但您可以使用一些包装类来隐藏它)。这可能对您来说是一个问题,也可能不是。我自己从未使用过PCollections,如果您必须绝对依赖于一个非常可靠的实现,请选择Clojure(检查许可证和/或与开发人员交谈,因为您可能只需提取所需的实现)。 - lsoliveira
1
totallylazy 看起来比 Clojure 更好,因为它不仅具有持久集合的实现,还有大量方便的方法来处理这些集合。此外,Clojure 集合只能在动态语言 Clojure 中使用(无法从 Java 中使用)。 - ZhekaKozlov

6

我正在寻找一个轻量级的、Java友好的持久化集合框架,并在这个线程中尝试了TotallyLazyPCollections,因为它们对我来说听起来最有前途。

两者都提供了相当简单的接口来操作持久化列表:

// TotallyLazy
PersistentList<String> original = PersistentList.constructors.empty(String.class);
PersistentList<String> modified = original.append("Mars").append("Raider").delete("Raider");

// PCollections
PVector<String> original = TreePVector.<String>empty();
PVector<String> modified = original.plus("Mars").plus("Raider").minus("Raider");

无论是 PersistentList 还是 PVector 都继承自 java.util.List,因此这两个库都应该很好地集成到现有环境中。

然而,事实证明,当处理更大的列表时(如 @levantpied 上面的评论中已经提到的),TotallyLazy会遇到性能问题。 在我的 MacBook Pro(2013 年底)上插入 100,000 个元素并返回不可变列表需要约 2000ms,而 PCollections 只需要约 120ms。

如果有人想进行更彻底的查看,可以在 Bitbucket 上找到我的(简单)测试用例。

[更新]:我最近看了一下 Cyclops X,它是一个高性能且更完整的针对函数编程的库。Cyclops 还包含一个持久化集合模块。


5

https://github.com/andrewoma/dexx 是将 Scala 的持久化集合移植到 Java 的一个项目。它包括以下内容:

  • Set、SortedSet、Map、SortedMap 和 Vector
  • 将持久化集合视为 java.util 集合的适配器
  • 方便构造的帮助类

5

Paguro提供了类型安全版本的Clojure集合,适用于Java 8+。其中包括:List(Vector)、HashMap、TreeMap、HashSet和TreeSet。它们按照您在问题中指定的方式准确地运行,并已费尽心思地融入了现有的java.util集合接口,以实现最大的类型安全Java兼容性。它们也比PCollections略快一些

在Paguro中编写示例代码如下:

// List with the elements (a,b,c)
ImList<T> list = vec(a,b,c);

// With a persistent list, when I call list.add(d),
// I get back a new list, holding (a,b,c,d)
ImList<T> newList = list.append(d);

list.size(); // still returns 3

newList.size(); // returns 4

你说过,
实现尝试在列表之间共享元素,因此比简单返回原始列表的副本更加内存高效和快速。它还有不可变性的优点(如果我持有对原始列表的引用,则它将始终返回原始的3个元素)。
是的,这正是它的行为方式。Daniel Spiewak解释了这些集合的速度和效率比我能做得更好。

3
最高票答案建议直接使用Clojure集合,我认为这是一个非常好的主意。不幸的是,Clojure是一种动态类型语言,而Java不是,这使得在Java中使用Clojure库非常不舒适。
因此,由于缺乏轻量级、易于使用的Clojure集合类型包装器,我编写了自己的Java包装器库,使用泛型为Clojure集合类型提供重点放在接口易用性和清晰度方面。 https://github.com/cornim/ClojureCollections 也许对某些人有用。
P.S.:目前只实现了PersistentVector、PersistentMap和PersistentList。

非常好的贡献!这是加一。 - developer_hatch

3

3

您可能想查看clj-ds。虽然我没有使用过,但它似乎很有前途。根据项目的自述文件,它从Clojure 1.2.0中提取了数据结构。


看起来它解决了直接使用Clojure集合时出现的所有问题。我会试一试。谢谢。 - bm212

3

Functional Java实现了持久化列表、惰性列表、集合、映射和树。可能还有其他的,但我只是参考网站首页上的信息。

我也想知道Java最好的持久化数据结构库是什么。我的注意力被引导到Functional Java,因为它在书籍Functional Programming for Java Developers中提到。


2
与Cornelius Mund类似,Pure4J将Clojure集合移植到Java并支持泛型。然而,Pure4J旨在通过编译时代码检查向JVM引入纯粹的编程语义,因此它进一步引入了不可变性约束,使集合的元素在集合存在时不能被改变。这可能符合或不符合您的需求:如果您只想在JVM上使用Clojure集合,则应选择Cornelius的方法;但如果您有兴趣在Java中实现纯编程方法,则可以尝试使用Pure4J。声明:我是开发者。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接