【问题标题】:How do I return a limited number of cached instances in Java?如何在 Java 中返回有限数量的缓存实例?
【发布时间】:2016-07-19 06:58:10
【问题描述】:

我有一个“配置”类,它成为其他几个类的字段。它表示那些其他类的某种配置或“能力”以允许或禁止操作。到目前为止,配置类包含一组四个独立的布尔值,并且可能会保持这种状态——或者随着另一个布尔值增长——。配置是不可变的:对象一旦创建,配置就永远不会改变。

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 同事对此并不满意。

不同配置的数量是有限的(布尔值的组合),但是使用它们的对象数量非常多。为了减少内存消耗,我想到了某种“多单例”、枚举或缓存实例。这就是我现在的位置。什么是最好的?

【问题讨论】:

  • 第一件事:您应该更多地担心好的设计,而不是“内存效率”。在没有真正问题的情况下优化代码......通常会导致不合标准的代码。在您的情况下:仅将配置表示为一组 4 个布尔值的想法......听起来有点奇怪。仅使用 4 个布尔值的列表会有什么问题?
  • 这些对象的创建成本高吗?如果是,请使用enum。如果没有,只需创建允许创建多个实例,并覆盖equals/hashCode
  • “我的 Java-all-my-life 同事不赞成”请他们解释他们的保留意见。
  • 顺便说一下,如果您是 Java 新手,我强烈建议您阅读 Josh Bloch 编写的 Effective Java 2nd Ed - 它可能会帮助您理解您所使用的成语同事。特别是,第 32 条与这个问题有关。
  • @Jägermeister 对。但是我们必须在 RAM 中加载大约 6k 的对象才能运行,目前我们可以在 4GB 中为 JVM 保留大约 3K 的对象。在真正的问题之前,这肯定不是。当然,我的 one-int-implementation 只在 RAM 中多获得了 10% 的对象,所以这并没有太大的改进。

标签: java enums singleton factory-pattern


【解决方案1】:

我认为 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);
    }
}

【讨论】:

  • 这实际上并没有回答这个问题:“如何在 Java 中返回有限数量的缓存实例?”。这只是展示了如何定义实例类。
  • 通过getConfig 方法。您只需要知道将布尔值放在 configs 数组中的哪个位置,然后将该位置提供给 getConfig 方法并获取您刚刚设置的配置。
  • 但是对于每个要配置的对象,您仍然有一个配置类实例,对吧?
  • @manuelvigarcia 是的。我编辑了我的答案以更好地处理它。这就是你的想法吗?
  • 那一行的东西。现在只会创建有限数量的实例,我想我可以放弃位图并使其“对所有读者”更具可读性。
【解决方案2】:

我建议以下,它很容易扩展,因为您只需向您的枚举添加另一个能力。

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);
    }
}

【讨论】:

  • Java 7 受限。
  • 1) 使用EnumSet 而不是HashSet 来保存枚举实例:它旨在使contains(等)操作更便宜,因为ordinal 在@987654326 上的可用性@实例; 2)在pre-lambda Java中使用EnumSet.copyOf(Arrays.asList(configuredAbilities))来做这个(实际上,你也需要检查参数列表为空的情况)。
  • 这实际上并没有回答这个问题:“如何在 Java 中返回有限数量的缓存实例?”。这只是展示了如何定义实例类。
  • 用 Andy Turners 的评论和 Java 7 编辑
  • 我在这里看到了一些亮光......我可以使用的东西。但是,对于我需要的每个配置,我仍然得到一个 Configuration 类的实例。而且由于我将这些能力作为单独的布尔值获得,因此将它们转换为 Set 并不像 Arrays.asList 那样容易。
【解决方案3】:

如果配置实现对象很小且创建成本不高,则无需缓存它们。因为每个怪物对象都必须保留对其每个配置的引用,并且在机器级别,引用是一个指针,并且至少使用与 int 相同的内存。

@gamulf 提出的 EnumSet 方式大概可以直接使用,无需任何缓存,因为根据 EnumSet javadoc:

枚举集在内部表示为位向量。这种表示非常紧凑和高效。这个类的空间和时间性能应该足够好,可以用作传统的基于 int 的“位标志”的高质量、类型安全的替代品。

我没有对它进行基准测试,但是 @gamulf 的解决方案可能无法使用缓存,因为 Configuration 对象只包含一个 EnumSet,它只包含一个 int。

如果您有一个繁重的配置类(在内存方面或创建成本高)并且只有少量可能的配置,您可以在类中使用静态 HashSet 成员和静态工厂 将返回缓存对象的方法:

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 提出的轻量级对象可能毫无用处

【讨论】:

  • 这应该是对@gamulf的回答的评论,但对它来说有点太长了......
【解决方案4】:

我想分享我根据您的回答所做的调查,因此我将发布一个带有这些结果的答案。这样一来,我为什么选择一个答案而不是另一个答案可能会更清楚。

裸结果排名如下(内存用于 600 个“怪物”对象,占所需内存的 10%):

  1. 小选项:内部有四个布尔值的类:22.200.040
  2. 初始选项:使用一个整数作为位映射的类:22.200.040
  3. “multiton”选项:一个工厂类,它返回对平凡选项类的引用:4.440.040
  4. EnumSet(没有 guava 缓存):53.401.896(在这个我可能搞砸了,因为结果不符合预期......我稍后可能会进一步研究)
  5. 带有番石榴缓存的枚举集:4.440.040

由于我的测试首先运行了一系列比较以确保所有实现对所有配置都给出完全相同的结果,所以很明显4.440.040 数字是列表的大小我用来保存项目,因为在我决定在测量内存之前将其设置为 null 之前,这些数字始终是 0

请不要讨论我是如何测量内存消耗的(gc(); freeMemory(); 在我释放每个列表并将其设置为 null 之前和之后),因为我对所有人都使用了相同的方法,并以不同的执行顺序每次执行 20 次。结果对我来说足够一致。

这些结果表明multiton 解决方案是性能最佳的最简单的解决方案。这就是我将其设置为选定答案的原因。

作为旁注/好奇心,请注意,本次调查开始的项目选择了琐碎的选项作为解决方案,并且大部分调查都是为了满足我自己的好奇心 - 并且有一些隐藏的愿望能够证明其他一些解决方案会soooo比简单的解决方案更有效......但没有 -。这就是为什么我花了这么长时间才得出结论的原因。

【讨论】:

    猜你喜欢
    • 2019-11-07
    • 1970-01-01
    • 2018-05-28
    • 2021-01-07
    • 2010-10-16
    • 2011-04-08
    • 2019-11-03
    • 2011-05-07
    • 1970-01-01
    相关资源
    最近更新 更多