我正在构建一个LRU缓存,我需要存储最后N个插入的项目。项目将被频繁插入(即许多写入操作),并且读取操作通常会返回大量事件总是严格按顺序,尽管从高速缓存中的任意点开始。例如,假设缓存包含事件:
[1, 2, 3, 4, 5, 6]
合法读取操作是在事件[2, 3, 4]
上返回迭代器。
由于读取操作可能是长期存在的,我想使用一种数据结构,我可以安全地迭代序列的逻辑副本,以便每次读取尝试,从而防止高速缓存读取从阻止任何后续写入。但是,使用vanilla Java ArrayList
或LinkedList
意味着制作完整副本的开销很大。
我的问题:是否有任何第三方Java库提供类似于Scala的不可变数据结构,因此修改数据结构的尝试返回一个新的不可变副本(实际上是基于原始的数据结构,因此复制操作非常快)?显然,数据结构无法符合Java Collections API,因为像add(T)
这样的操作需要返回新的集合(而不是void
)。
(请不要评论/回答引用这个过早优化的情况。)
提前致谢。
注意
Guava的ImmutableList
几乎达到了我的需求:它允许你调用copyOf
,其中副本通常引用原始文件(避免执行实际的副本)。不幸的是,你不能走另一条路并将一个项目添加到列表中并获取包含新元素的副本。
答案 0 :(得分:8)
Functional Java以库(不是不同的语言)的形式出现,并提供不可变的集合。不确定它是否符合您的需求但值得一试。
答案 1 :(得分:4)
答案 2 :(得分:2)
你看过CopyOnWriteArrrayList了吗?列表中的每个突变都会将所有内容复制到新的后备阵列,而当前阵列可能会在不受影响的情况下进行迭代。
答案 3 :(得分:2)
看起来你想在这里实现一个单链表,然后可以由不同的包装器对象共享。你想删除元素,还是只添加新元素?
如果只有添加和不删除,我想一个更简单的CopyOnWriteArrayList变体,只有在旧数组已满时才能进行复制。然后sublist()
方法只会创建一个新的包装器对象。
/**
* A list which only supports appending objects.
*/
public class OnlyAppendingList<E> extends AbstractList<E> {
private Object[] data;
private int len;
public int size() {
return this.len;
}
public E get(int index) {
if(index >= this.len)
throw new IndexOutOfBoundsException(index + " >= " + this.len);
@SuppressWarnings("unchecked")
E res = this.data[index];
return res;
}
public boolean add(E element) {
if(len == data.length) {
this.resize();
}
this.data[this.len] = element;
this.len++;
return true;
}
private void resize() {
this.data = Arrays.copyOf(data, data.length * 2 +2);
}
public void add(int index, E element) {
if(index > this.len) {
throw new IndexOutOfBoundsException(index + " > " + len);
}
if(index < this.len) {
throw new UnsupportedOperationException("we only support appending, not insertion!");
}
this.add(element);
}
/**
* Returns an immutable sublist of this list.
*/
public List<E> subList(final int fromIndex, final int toIndex) {
// TODO: bounds checks
return new SubList<E>(this.data, fromIndex, fromIndex - toIndex);
}
private static class SubList<E> extends AbstractList<E> {
private Object[] data;
private int start;
private int len;
SubList(Object[] data, int start, int len) {
this.data = data; this.start = start; this.len = len;
}
public int size() {
return this.len;
}
public E get(int index) {
if(index >= this.len)
throw new IndexOutOfBoundsException(index + " >= " + this.len);
if(index < 0)
throw new IndexOutOfBoundsException(index + " < 0");
@SuppressWarnings("unchecked")
E res = this.data[index + start];
return res;
}
public List<E> subList(int from, int to) {
// TODO: bounds check
return new SubList(data, start + from, to - from);
}
}
}
如果多线程修改了这个,我认为应该add
方法同步,len
变量volatile
。 (我没有完全检查它是否是线程安全的。)
答案 4 :(得分:2)
Google Guava可以满足您的需求。
如果要更新缓存,请使用Guava的构建器模式从旧缓存创建新缓存,然后删除旧缓存。
要更新缓存,请创建ImmutableList.Builder()
并使用现有的ImmutableList
对其进行初始化。通过Builder界面修改列表。然后调用.build()
以获取新的ImmutableList
,并删除旧缓存。新缓存将重用所有旧对象,因此这是一个非常轻量级的操作。
当有人想要缓存(或其中一个项目)的不可变副本时,返回copyOf(),他们将获得对不可变快照的访问权限。
警告,如果你正在使用线程,请确保将列表包装在一个对象中并同步它的get()&amp; insert()方法。
您可以在the Guava site阅读更多内容。
答案 5 :(得分:2)
JDK 9有新的of()
方法工厂。例如。你可以immutable Set作为
Set<Integer> intSet = Set.of(1, 2, 3);
您可以对List执行相同的操作,例如
List<String> stringList = List.of("A", "B", "C");
和Map
:
Map<String, String> doubleMap = Map.of("key1", "val1",
"key2", "val2");
答案 6 :(得分:1)
通过Clojure持久集合类的副本提供不可变集合。它可能不完全是你所追求的,因为它是关于在java程序的(子集)上强制执行纯函数语义。
另一方面,它对元素和集合都有不变性保证。当你从一个集合中添加/删除一个元素时,你会得到一个新的集合,原始文件保持不变。