爪哇。为什么一个集合可以包含重复的元素

231 阅读2分钟

爪哇。为什么一个集合可以包含重复的元素

发布者::Per Minborg inCore Java January 20th, 2022 0 Views

在低延迟的应用程序中,通常通过重复使用可变对象来避免创建不必要的对象,以减少内存压力,从而减少垃圾收集器的负载。这使得应用程序的运行更有确定性,抖动更少。然而,必须注意如何使用这些重复使用的对象,否则可能会出现意想不到的结果,例如,包含重复元素的Set的形式,如[B, B]。

哈希代码和等价物

Java内置的ByteBuffer提供了对堆和本地内存的直接访问,使用32位寻址。Chronicle Bytes是一个64位寻址的开源替代物,允许对更大的内存段进行寻址。这两种类型都提供了一个hashCode()和一个equals()方法,它依赖于对象底层内存段的字节内容。虽然这在很多情况下是有用的,但像这样的易变对象不应该用在大多数Java的内置Set类型中,也不应该作为大多数内置Map类型中的键。

注意:在现实中,只有31位和63位可以作为有效的地址偏移(例如,使用int和long偏移参数)。

可变的键

下面,介绍了一个小的代码例子,说明了重复使用可变对象的问题。该代码显示了Bytes,但对于ByteBuffer ,也存在着非常相同的问题。

Set<CharSequence> set = new HashSet<>();

Bytes<?> bytes = Bytes.from("A");

set.add(bytes);


// Reuse

bytes.writePosition(0);


// This mutates the existing object already

// in the Set

bytes.write("B");


// Adds the same Bytes object again but now under

// another hashCode()

set.add(bytes);


System.out.println(“set = “ + set);

上面的代码将首先添加一个以 "A "为内容的对象,意思是这个集合包含[A]。然后,现有对象的内容将被修改为 "B",这有一个副作用,即改变集合包含[B],但会使旧的哈希代码值和相应的哈希桶保持不变(有效地变得陈旧)。最后,修改后的对象被再次添加到集合中,但现在在另一个哈希代码下,导致该对象的前一个条目将保持不变

结果是,这将产生以下输出,而不是预期的[A, B]。

set = [B, B]

ByteBuffer和Bytes对象作为地图中的键

当使用Java的ByteBuffer对象或Bytes对象作为地图中的键或集合中的元素时,一个解决方案是使用IdentityHashMap或Collections.newSetFromMap(new IdentityHashMap<>())来防止上述的可变对象的特殊性。这使得对象的散列与实际的字节内容无关,而是使用System.identityHashCode(),它在对象的生命周期内永远不会改变。

另一个选择是使用对象的只读版本(例如通过调用ByteBuffer.asReadOnlyBuffer()),并避免持有对原始可变对象的任何引用,这可能为修改所谓的只读对象的内容提供一个后门。

Chronicle Map和Chronicle Queue

Chronicle Map是一个开源库,它的工作方式与内置的Java Map实现有点不同,它将对象序列化并放在堆外内存中,为超大型的地图开放,这些地图可以大于分配给JVM的RAM内存,并允许将这些地图持久化到内存映射的文件中,以便应用程序可以更快地重新启动。

序列化过程还有一个不太为人所知的优点,即它实际上允许可重复使用的可变对象作为键,因为对象的内容被复制,并且在每次将一个新的关联放入地图时被有效冻结。因此,对易变对象的后续修改将不会影响冻结的序列化内容,允许不受限制的对象重用。

开源的Chronicle Queue以类似的方式工作,可以提供可以容纳数千兆字节的数据持久化到二级存储的队列,并且由于与Chronicle Map相同的原因,允许对象重用可变元素。

结论

在一些Map和Set的实现中使用易变的对象,比如Bytes和ByteBuffer,其中hashCode()取决于对象的内容,这是非常危险的。

IdentityHashMap可以防止由于对象变异而导致的地图和集合的损坏,但使这些结构与实际的字节内容无关。

以前修改过的内存段对象的只读版本可能会提供一个替代的解决方案。

Chronicle Map和Chronicle Queue允许不受限制地使用可变异对象,为确定的低延迟操作开辟了道路。

资源

Chronicle主页

GitHub上的Chronicle Bytes (开源)

GitHub上的Chronicle Map (开源)

GitHub上的Chronicle Queue (开源)

由我们的JCG项目合伙人Per Minborg授权发布在Java Code Geeks上。点击这里查看原文。Java。为什么一个集合可以包含重复的元素

Java Code Geeks撰稿人所表达的观点属于他们自己。

2022-01-20

+Per Minborg