在java中,EnumSet使用long
(RegularEnumSet
)或long[]
(JumboEnumSet
)将其包含的项目存储在位掩码/位向量中。我现在遇到了一个用例,我有几千个域对象(让我们称之为Node
),每个对象都会显示一个枚举的所有项目(让我们称之为Flag
)。每个对象会有所不同。
目前我将订单存储为Guava ImmutableSet
,因为这可以保证保留插入订单。但是,我使用the methods explained on this page来比较EnumSet<Flag>
,ImmutableSet<Flag>
和Flag[]
中的内存使用情况。以下是a)Flag有64个枚举项目和b)所有三个变量包含所有64个项目的结果:
EnumSet:32个字节
ImmutableSet:832字节
数组:272字节
所以我的问题是:是否有一种聪明的方法将枚举排序打包成数值以使内存占用量小于数组的内存占用量?如果它有所不同:在我的用例中,我会假设订单总是包含所有枚举项。
澄清:我的枚举比这小得多,我现在没有任何记忆问题,这种情况也不会给我带来记忆问题。只是在这种微观层面上,这种低效率会让我感到困惑。
更新
根据各种答案和评论的建议后,我想出了这个使用字节数组的数据结构。警告:它没有实现Set接口(不检查唯一值),并且它不会扩展到超出字节可容纳的大枚举。此外,复杂性非常糟糕,因为必须重复查询Enum.values()see here for a discussion of this problem),但这里有:
public class EnumOrdering<E extends Enum<E>> implements Iterable<E> {
private final Class<E> type;
private final byte[] order;
public EnumOrdering(final Class<E> type, final Collection<E> order) {
this.type = type;
this.order = new byte[order.size()];
int offset = 0;
for (final E item : order) {
this.order[offset++] = (byte) item.ordinal();
}
}
@Override
public Iterator<E> iterator() {
return new AbstractIterator<E>() {
private int offset = -1;
private final E[] enumConstants = type.getEnumConstants();
@Override
protected E computeNext() {
if (offset < order.length - 1) {
return enumConstants[order[++offset]];
}
return endOfData();
}
};
}
}
内存占用是:
EnumOrdering:104
到目前为止,这是一个非常好的结果,感谢bestsss和JB Nizet!
更新:我已经将代码更改为仅实现Iterable,因为其他任何东西都需要对equals / hashCode / contains等进行合理的实现。
答案 0 :(得分:6)
有一种聪明的方法可以将枚举排序打包成数值
是的,您可以将排序表示为数值,但要使用它,您需要转换回byte / int数组。而且因为有64个! 64个值的可能排序,64!如果大于Long.MAX_VALUE
,则需要将该数字存储在BigInteger
中。我想这将是存储顺序的最节省内存的方式,尽管你在内存中获得的是由于必须将数字转换为数组而导致的时间损失。
要在数字/数组表示之间进行转换的算法,请参阅this question。
以上是上述内容的替代方案,不知道它是否与该代码一样高效,并且您必须将代码从int
转换为BigInteger
- 但它应该足够给你这个想法:
/**
* Returns ith permutation of the n numbers [from, ..., to]
* (Note that n == to - from + 1).
* permutations are numbered from 0 to n!-1, if i is outside this
* range it is treated as i%n!
* @param i
* @param from
* @param n
* @return
*/
public static int[] perm(long i, int from, int to)
{
// method specification numbers permutations from 0 to n!-1.
// If you wanted them numbered from 1 to n!, uncomment this line.
// i -= 1;
int n = to - from + 1;
int[] initArr = new int[n]; // numbers [from, ..., to]
int[] finalArr = new int[n]; // permutation of numbers [from, ..., to]
// populate initial array
for (int k=0; k<n; k++)
initArr[k] = k+from;
// compute return array, element by element
for (int k=0; k<n; k++) {
int index = (int) ((i%factorial(n-k)) / factorial(n-k-1));
// find the index_th element from the initial array, and
// "remove" it by setting its value to -1
int m = convertIndex(initArr, index);
finalArr[k] = initArr[m];
initArr[m] = -1;
}
return finalArr;
}
/**
* Helper method used by perm.
* Find the index of the index_th element of arr, when values equal to -1 are skipped.
* e.g. if arr = [20, 18, -1, 19], then convertIndex(arr, 2) returns 3.
*/
private static int convertIndex(int[] arr, int index)
{
int m=-1;
while (index>=0) {
m++;
if (arr[m] != -1)
index--;
}
return m;
}
基本上,你的init数组以其自然顺序开始,然后遍历你的最终数组,每次计算下一个应该放置哪些剩余元素。此版本通过将值设置为-1来“删除”init数组中的元素。使用List
或LinkedList
可能会更直观,我只是从我躺在的旧代码中粘贴它。
使用上述方法并将其作为main
:
public static void main(String[] args) {
int n = (int) factorial(4);
for ( int i = 0; i < n; i++ ) {
System.out.format( "%d: %s\n", i, Arrays.toString( perm(i, 1, 4 ) ) );
}
}
您将获得以下输出:
0: [1, 2, 3, 4]
1: [1, 2, 4, 3]
2: [1, 3, 2, 4]
3: [1, 3, 4, 2]
4: [1, 4, 2, 3]
5: [1, 4, 3, 2]
6: [2, 1, 3, 4]
7: [2, 1, 4, 3]
8: [2, 3, 1, 4]
9: [2, 3, 4, 1]
10: [2, 4, 1, 3]
11: [2, 4, 3, 1]
12: [3, 1, 2, 4]
13: [3, 1, 4, 2]
14: [3, 2, 1, 4]
15: [3, 2, 4, 1]
16: [3, 4, 1, 2]
17: [3, 4, 2, 1]
18: [4, 1, 2, 3]
19: [4, 1, 3, 2]
20: [4, 2, 1, 3]
21: [4, 2, 3, 1]
22: [4, 3, 1, 2]
23: [4, 3, 2, 1]
Here is an executable version on ideone
根据BigInteger.bitLength()
判断,应该可以存储不超过37个字节的64个元素的排序(加上使用BigInteger
实例的开销)。我不知道这是值得的麻烦,但它是一个很好的运动!
答案 1 :(得分:2)
如果你有64个枚举值,你可以使用一个字节数组,其中每个字节都包含一个枚举项的序数。对于3个64字节的数组,这需要3 * (64 + 16) = 240
个字节(16字节是字节数组的成本,无论其长度如何)。
这仍然浪费空间,因为每个字节能够存储8位,但是你只需要6来存储从0到63的数字。所以你可以应用一个聪明的打包算法,它将使用3个字节(24位)来存储4枚枚号。这将导致3 * (64 * 3 / 4 + 16) = 192
字节。
我对字节操作感到厌烦,因此我将把实现作为练习留给你。