我正在尝试在Java中实现有效的优先级队列。我得到了一个很好的二进制堆实现,但它没有理想的缓存性能。为此,我开始在二进制堆中研究Van Emde Boas布局,这使我成为二进制堆的“阻塞”版本,其中诀窍是计算子索引和父索引。
虽然我能够做到这一点,但缓存行为(和运行时间)变得更糟。我认为问题是:引用位置可能无法实现,因为它是Java - 我不太确定使用对象数组实际上是否使对象连续Java中的内存,有人可以确认吗?
另外,我非常想知道Java的本机PriorityQueue使用什么类型的数据结构,如果有的话可以知道。
答案 0 :(得分:2)
通常,没有什么好方法可以强制队列中的对象占用连续的内存块。但是,有一些技术适用于特殊情况。
在较高的层面上,这些技术涉及使用字节数组和“序列化”数据与数组之间的数据。如果您要存储非常简单的对象,这实际上非常有效。例如,如果要存储一堆2D点+权重,则只需编写等效的权重,x坐标,y坐标。
此时的问题当然是在偷看/弹出时分配实例。您可以使用回调来避免这种情况。
请注意,即使在存储对象本身很复杂的情况下,使用与此类似的技术,您可以为权重保留一个数组,并为实际对象提供单独的引用数组,这样可以避免跟踪对象引用,直到绝对必要。
回到存储简单的不可变值类型的方法,这里有一个不完整的草图,你可以做什么:
abstract class LowLevelPQ<T> {
interface DataHandler<R, T> {
R handle(byte[] source, int startLoc);
}
LowLevelPQ(int entryByteSize) { ... }
abstract encode(T element, byte[] target, int startLoc);
abstract T decode(byte[] source, int startLoc);
abstract int compare(byte[] data, int startLoc1, int startLoc2);
abstract <R> R peek(DataHandler<R, T> handler) { ... }
abstract <R> R pop(DataHandler<R, T> handler) { ... }
}
class WeightedPoint {
WeightedPoint(int weight, double x, double y) { ... }
double weight() { ... }
double x() { ... }
...
}
class WeightedPointPQ extends LowLevelPQ<WeightedPoint> {
WeightedPointPQ() {
super(4 + 8 + 8); // int,double,double
}
int compare(byte[] data, int startLoc1, int startLoc2) {
// relies on Java's big endian-ness
for (int i = 0; i < 4; ++i) {
int v1 = 0xFF & (int) data[startLoc1];
int v2 = 0xFF & (int) data[startLoc2];
if (v1 < v2) { return -1; }
if (v1 > v2) { return 1; }
}
return 0;
}
...
}
答案 1 :(得分:1)
我认为不会。请记住,“对象数组”不是对象数组,它们是对象引用数组(与基元数组不同,实际上是基元数组)。我希望对象引用在内存中是连续的,但是因为你可以让这些引用随时引用你想要的任何对象,我怀疑是否可以保证引用数组引用的对象在内存中是连续的。
对于它的价值,JLS section on arrays对任何连续性保证一无所知。
答案 2 :(得分:1)
我认为这里有一些FUD。基本上不可思议的是,阵列的任何实现都不会使用连续的内存。在描述.class文件格式时,在JVM规范中使用该术语的方式使得很明显没有考虑其他实现。
java.util.PriorityQueue使用二进制堆,就像在Javadoc中所说的那样,通过数组实现。