如何在 Java 中 return 限制数量的缓存实例?
How do I return a limited number of cached instances in Java?
我有一个 "configuration" class 成为其他几个 class 的字段。它表示某种配置或 "abilities" 其他 classes 允许或禁止操作。截至目前,配置 class 包含一组四个独立的布尔值,并且可能会保持原样——或者随着另一个布尔值增长——。配置是不可变的:一旦对象被创建,配置就永远不会改变。
public class Configuration {
private final boolean abilityOne;
private final boolean abilityTwo;
private final boolean abilityThree;
private final boolean abilityFour;
public Configuration (final boolean abilityOne, final boolean abilityTwo,
final boolean abilityThree, final boolean abilityFour) {
this.configuration = ((1 * (abilityOne ? 1 : 0)) +
(2 * (abilityTwo ? 1 : 0)) +
(4 * (abilityThree ? 1 : 0)) +
(8 * (abilityFour ? 1 : 0)));
}
public boolean isAbilityOne() {
return((1 & this.configuration) > 0);
}
public boolean isAbilityTwo() {
return((2 & this.configuration) > 0);
}
public boolean isAbilityThree() {
return((4 & this.configuration) > 0);
}
public boolean isAbilityFour() {
return((8 & this.configuration) > 0);
}
}
由于 C / 硬件背景有限,我的下一个实现(尝试减少内存占用)是使用 int
作为位图:1 -> 第一个布尔值,2-> 第二个,4 -> 第三,8-> 第四。这样我存储了一个整数,我需要的布尔函数如下:
它工作正常并且内存效率很高。但我的 Java-all-my-life 同事不赞成这样做。
不同配置的数量有限(布尔值的组合),但是使用它们的对象数量非常多。为了减少内存消耗,我想到了某种"multi-singleton"、枚举或缓存实例。这就是我现在的位置。什么最好?
我建议如下,它很容易扩展,因为你只需要在枚举中添加另一个 Ability。
enum Ability {
Ability1, Ability2, Ability3, Ability4
}
public class Configuration {
private static LoadingCache<Set<Ability>, Configuration> cache = CacheBuilder.newBuilder()
.build(new CacheLoader<Set<Ability>, Configuration>() {
@Override
public Configuration load(Set<Ability> withAbilities) {
return new Configuration(withAbilities);
}
});
Set<Ability> abilities;
private Configuration(Collection<Ability> withAbilities) {
this.abilities = createAbilitySet(withAbilities);
}
public static Configuration create(Ability... withAbilities) {
Set<Ability> searchedAbilities = createAbilitySet(Arrays.asList(withAbilities));
try {
return cache.get(searchedAbilities);
} catch (ExecutionException e) {
Throwables.propagateIfPossible(e);
throw new IllegalStateException();
}
}
private static Set<Ability> createAbilitySet(Collection<Ability> fromAbilities) {
if (fromAbilities.size() == 0) {
return Collections.emptySet();
} else {
return EnumSet.copyOf(fromAbilities);
}
}
public boolean hasAbility(Ability ability) {
return abilities.contains(ability);
}
}
我认为 multiton 模式是最有效的方式:
public class Configuration {
private static Map<Long, Configuration> configurations = new HashMap<>();
private long key;
private long value;
public static Configuration getInstanse(long key, boolean... configs) {
if (configurations.containsKey(key)) {
return configurations.get(key).setConfigs(configs);
}
Configuration configuration = new Configuration(key, configs);
configurations.put(key, configuration);
return configuration;
}
// Max number of configs.length is 64
private Configuration(long key, boolean... configs) {
this.key = key;
setConfigs(configs);
}
private Configuration setConfigs(boolean[] configs) {
this.value = 0L;
boolean config;
for (int i = 0; i < configs.length; i++) {
config = configs[i];
this.value = this.value | (config ? (1L << i) : 0L);
}
}
public long getKey() {
return key;
}
public boolean getConfig(int place) {
return (value & (1L << place)) == (1L << place);
}
}
如果配置实现对象很小且创建成本不高,则无需缓存它们。因为每个怪物对象都必须保留对其每个配置的引用,并且在机器级别,引用是一个指针,并且至少使用与 int 相同的内存。
@gamulf 提出的 EnumSet 方式可能可以在没有任何缓存的情况下使用,因为根据 EnumSet javadoc:
Enum sets are represented internally as bit vectors. This representation is extremely compact and efficient. The space and time performance of this class should be good enough to allow its use as a high-quality, typesafe alternative to traditional int-based "bit flags."
我没有对它进行基准测试,但缓存可能对@gamulf 的解决方案毫无用处,因为 Configuration 对象只包含一个 EnumSet,而 EnumSet 最多包含一个 int。
如果你有一个繁重的配置 class(在内存方面或创建昂贵)并且只有少量可能的配置,你可以在 class 中使用静态 HashSet 成员,和一个静态 factory 方法,它将 return 缓存的对象:
public class Configuration {
static Set<Configuration > confs = new HashSet<>();
...
public Configuration (Ability ... abs) {
...
}
public boolean hasAbility(Ability ab) {
...
}
static Configuration getConfiguration(Ability ... abs) {
for (ConfImpl2 conf: confs) {
if (conf.isSame(abs)) { return conf; }
}
ConfImpl2 conf = new ConfImpl2(abs);
confs.add(conf);
return conf;
}
private boolean isSame(Ability ... abs) {
// ensures that this configuration has all the required abilities and only them
...
}
}
但是正如我已经说过的,对于@gamulf
提出的轻量级对象来说,这可能是无用的
我想分享我根据您的回答所做的调查,因此我发布了一个包含这些结果的答案。这样可能会更清楚为什么我选择一个答案而不是其他答案。
裸结果排名如下(内存用于 600 "monster" 个对象,需要的 10%):
- 普通选项:Class 内部有四个布尔值:
22.200.040
- 初始选项:Class 以一个整数作为位图:
22.200.040
- "multiton" 选项:一个工厂 class returns 引用了普通选项的 Class:
4.440.040
- EnumSet(没有 guava 缓存):
53.401.896
(在这一个中我可能搞砸了,因为结果不符合预期......我可能会在稍后进一步研究)
- 带有番石榴缓存的枚举集:
4.440.040
自从我的测试 运行 首先进行了一系列比较以确保所有实现对所有配置给出完全相同的结果后,很明显 4.440.040
数字是列表的大小<> 我曾经持有这些项目,因为在我决定在测量内存之前将其设置为 null
之前,那些数字一直是 0
.
请不要深究我是如何测量内存消耗的(gc(); freeMemory();
在我释放每个列表并将其设置为 null
之前和之后),因为我对所有人都使用相同的方法,并且每次执行 20 次并且执行顺序不同。结果对我来说足够稳定。
这些结果表明 multiton
解决方案是性能最好的解决方案中最简单的。这就是为什么我将其设置为已选答案的原因。
作为一方 note/curiosity,请注意,本次调查开始的项目选择了简单的选项作为解决方案,本次调查的大部分内容是为了满足我自己的好奇心——并隐藏了一些希望能够证明其他一些解决方案 soooo 比微不足道的解决方案更有效......但是没有--。这就是为什么我花了这么长时间才得出结论。
我有一个 "configuration" class 成为其他几个 class 的字段。它表示某种配置或 "abilities" 其他 classes 允许或禁止操作。截至目前,配置 class 包含一组四个独立的布尔值,并且可能会保持原样——或者随着另一个布尔值增长——。配置是不可变的:一旦对象被创建,配置就永远不会改变。
public class Configuration {
private final boolean abilityOne;
private final boolean abilityTwo;
private final boolean abilityThree;
private final boolean abilityFour;
public Configuration (final boolean abilityOne, final boolean abilityTwo,
final boolean abilityThree, final boolean abilityFour) {
this.configuration = ((1 * (abilityOne ? 1 : 0)) +
(2 * (abilityTwo ? 1 : 0)) +
(4 * (abilityThree ? 1 : 0)) +
(8 * (abilityFour ? 1 : 0)));
}
public boolean isAbilityOne() {
return((1 & this.configuration) > 0);
}
public boolean isAbilityTwo() {
return((2 & this.configuration) > 0);
}
public boolean isAbilityThree() {
return((4 & this.configuration) > 0);
}
public boolean isAbilityFour() {
return((8 & this.configuration) > 0);
}
}
由于 C / 硬件背景有限,我的下一个实现(尝试减少内存占用)是使用 int
作为位图:1 -> 第一个布尔值,2-> 第二个,4 -> 第三,8-> 第四。这样我存储了一个整数,我需要的布尔函数如下:
它工作正常并且内存效率很高。但我的 Java-all-my-life 同事不赞成这样做。
不同配置的数量有限(布尔值的组合),但是使用它们的对象数量非常多。为了减少内存消耗,我想到了某种"multi-singleton"、枚举或缓存实例。这就是我现在的位置。什么最好?
我建议如下,它很容易扩展,因为你只需要在枚举中添加另一个 Ability。
enum Ability {
Ability1, Ability2, Ability3, Ability4
}
public class Configuration {
private static LoadingCache<Set<Ability>, Configuration> cache = CacheBuilder.newBuilder()
.build(new CacheLoader<Set<Ability>, Configuration>() {
@Override
public Configuration load(Set<Ability> withAbilities) {
return new Configuration(withAbilities);
}
});
Set<Ability> abilities;
private Configuration(Collection<Ability> withAbilities) {
this.abilities = createAbilitySet(withAbilities);
}
public static Configuration create(Ability... withAbilities) {
Set<Ability> searchedAbilities = createAbilitySet(Arrays.asList(withAbilities));
try {
return cache.get(searchedAbilities);
} catch (ExecutionException e) {
Throwables.propagateIfPossible(e);
throw new IllegalStateException();
}
}
private static Set<Ability> createAbilitySet(Collection<Ability> fromAbilities) {
if (fromAbilities.size() == 0) {
return Collections.emptySet();
} else {
return EnumSet.copyOf(fromAbilities);
}
}
public boolean hasAbility(Ability ability) {
return abilities.contains(ability);
}
}
我认为 multiton 模式是最有效的方式:
public class Configuration {
private static Map<Long, Configuration> configurations = new HashMap<>();
private long key;
private long value;
public static Configuration getInstanse(long key, boolean... configs) {
if (configurations.containsKey(key)) {
return configurations.get(key).setConfigs(configs);
}
Configuration configuration = new Configuration(key, configs);
configurations.put(key, configuration);
return configuration;
}
// Max number of configs.length is 64
private Configuration(long key, boolean... configs) {
this.key = key;
setConfigs(configs);
}
private Configuration setConfigs(boolean[] configs) {
this.value = 0L;
boolean config;
for (int i = 0; i < configs.length; i++) {
config = configs[i];
this.value = this.value | (config ? (1L << i) : 0L);
}
}
public long getKey() {
return key;
}
public boolean getConfig(int place) {
return (value & (1L << place)) == (1L << place);
}
}
如果配置实现对象很小且创建成本不高,则无需缓存它们。因为每个怪物对象都必须保留对其每个配置的引用,并且在机器级别,引用是一个指针,并且至少使用与 int 相同的内存。
@gamulf 提出的 EnumSet 方式可能可以在没有任何缓存的情况下使用,因为根据 EnumSet javadoc:
Enum sets are represented internally as bit vectors. This representation is extremely compact and efficient. The space and time performance of this class should be good enough to allow its use as a high-quality, typesafe alternative to traditional int-based "bit flags."
我没有对它进行基准测试,但缓存可能对@gamulf 的解决方案毫无用处,因为 Configuration 对象只包含一个 EnumSet,而 EnumSet 最多包含一个 int。
如果你有一个繁重的配置 class(在内存方面或创建昂贵)并且只有少量可能的配置,你可以在 class 中使用静态 HashSet 成员,和一个静态 factory 方法,它将 return 缓存的对象:
public class Configuration {
static Set<Configuration > confs = new HashSet<>();
...
public Configuration (Ability ... abs) {
...
}
public boolean hasAbility(Ability ab) {
...
}
static Configuration getConfiguration(Ability ... abs) {
for (ConfImpl2 conf: confs) {
if (conf.isSame(abs)) { return conf; }
}
ConfImpl2 conf = new ConfImpl2(abs);
confs.add(conf);
return conf;
}
private boolean isSame(Ability ... abs) {
// ensures that this configuration has all the required abilities and only them
...
}
}
但是正如我已经说过的,对于@gamulf
提出的轻量级对象来说,这可能是无用的我想分享我根据您的回答所做的调查,因此我发布了一个包含这些结果的答案。这样可能会更清楚为什么我选择一个答案而不是其他答案。
裸结果排名如下(内存用于 600 "monster" 个对象,需要的 10%):
- 普通选项:Class 内部有四个布尔值:
22.200.040
- 初始选项:Class 以一个整数作为位图:
22.200.040
- "multiton" 选项:一个工厂 class returns 引用了普通选项的 Class:
4.440.040
- EnumSet(没有 guava 缓存):
53.401.896
(在这一个中我可能搞砸了,因为结果不符合预期......我可能会在稍后进一步研究) - 带有番石榴缓存的枚举集:
4.440.040
自从我的测试 运行 首先进行了一系列比较以确保所有实现对所有配置给出完全相同的结果后,很明显 4.440.040
数字是列表的大小<> 我曾经持有这些项目,因为在我决定在测量内存之前将其设置为 null
之前,那些数字一直是 0
.
请不要深究我是如何测量内存消耗的(gc(); freeMemory();
在我释放每个列表并将其设置为 null
之前和之后),因为我对所有人都使用相同的方法,并且每次执行 20 次并且执行顺序不同。结果对我来说足够稳定。
这些结果表明 multiton
解决方案是性能最好的解决方案中最简单的。这就是为什么我将其设置为已选答案的原因。
作为一方 note/curiosity,请注意,本次调查开始的项目选择了简单的选项作为解决方案,本次调查的大部分内容是为了满足我自己的好奇心——并隐藏了一些希望能够证明其他一些解决方案 soooo 比微不足道的解决方案更有效......但是没有--。这就是为什么我花了这么长时间才得出结论。