【问题标题】:How to increment value stored in HashBasedTable properly?如何正确增加存储在 HashBasedTable 中的值?
【发布时间】:2014-11-14 12:09:59
【问题描述】:

现在,我将一些数值存储为 HashBasedTable 的值。当我想改变它的数量时,我需要写这样的东西:

table.put("AS", "BS", table.get("AS", "BS") == null? 123: table.get("AS", "BS")+123);

在我看来这似乎很混乱......还有其他方法可以正确处理它吗?我的意思是没有空检查,离开地图并重新放回去。 也许您可以建议任何其他数据类型而不是 Guava 表?

谢谢!

【问题讨论】:

    标签: java collections guava


    【解决方案1】:

    这大致是使用Table 接口的方式,尽管我会使用firstNonNull(T, T) 来避免调用 get 两次:

    Table<String, String, Integer> table = HashBasedTable.create();
    table.put("AS", "X", 2);
    // with null check
    table.put("AS", "BS", Objects.firstNonNull(table.get("AS", "BS"), 0) + 123);
    System.out.println(table);
    // prints: {AS={BS=123, X=2}}
    

    另一方面,如果您真的不需要Table 接口和/或计数对您来说更重要(并且数字不是负数),请考虑使用Map&lt;String, Multiset&lt;String&gt;。获取多集时空检查会更痛苦,但添加元素会更容易(因为Multiset 的功能):

    Map<String, Multiset<String>> mapWithMultiset = new HashMap<>();
    // create new multiset to be put as value
    HashMultiset<String> x = HashMultiset.create();
    x.add("X", 2);
    mapWithMultiset.put("AS", x);
    
    // this feels dirty
    if (!mapWithMultiset.containsKey("AS")) {
      mapWithMultiset.put("AS", HashMultiset.<String>create());
    }
    mapWithMultiset.get("AS").add("BS", 123);
    
    System.out.println(mapWithMultiset);
    // prints: {AS=[BS x 123, X x 2]}
    

    看来你真的需要像 MultisetMultimap aka BagMultimap 这样的东西:

    1. Guava 中不存在
    2. 会为您节省脏东西。

    可能看起来像这样,尽管命名会变得很棘手,并且 Map / Collection 中的 put / add 方法的使用会令人困惑(明确免责声明:没有比如MultisetMultimap):

    MultisetMultimap<String, String> multisetMultimap = HashMultisetMultimap.create();
    multisetMultimap.add("AS", "X", 2);
    multisetMultimap.add("AS", "BS", 123);
    
    System.out.println(mapWithMultiset);
    // should print: {AS=[BS x 123, X x 2]}
    

    (如果您对此解决方案感兴趣,您应该使用new issue on Github。如果 Google 内部的 Guava 中有类似的东西,也许一些 Guava 开发人员可以回答。)

    【讨论】:

      【解决方案2】:

      我想到的是LoadingCache&lt;StringPair, AtomicInteger&gt;

      关键是:

         public final class StringPair {
             private final String key1, key2;
             // + constructor, hashCode, equals
         }
      

      我会这样创建它...

       LoadingCache<StringPair, AtomicInteger> cache = CacheBuilder.newBuilder()
         .build(
             new CacheLoader<StringPair, AtomicInteger>() {
               public AtomicInteger load(StringPair key) {
                 return new AtomicInteger(0);
               }
             });
      

      那么……

       AtomicInteger value = cache.get(new StringPair("AB", "AB"));
       value.addAndGet(123);
      

      cache.get(new StringPair("AB", "AB")).addAndGet(123)
      

      在这种模式下,您只需进行一次查找,并且更新非常有效,因为它不必再次遍历地图。您并没有真正将缓存用作缓存,而只是一个加载地图。

      只要你不需要只用第一个键访问地图,我认为这会很好。

      【讨论】:

      • 这种方式也可以,不过我清理了你的代码,希望你不要介意。如果它不应该同时使用,我也会将CacheBuilder#concurrencyLevel(int) 设置为 1。
      • @Xaerxess 我不介意。感谢您的修复和并发建议。干杯!
      【解决方案3】:

      我不确定你到底在问什么。

      但是你可以像这样重构你的代码。

      Integer val = table.get("AS", "BS");
      val = val == null ? 123 : (val + 123);
      table.put("AS", "BS", val);
      

      【讨论】:

        猜你喜欢
        • 2016-05-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-06-25
        • 2016-10-13
        • 1970-01-01
        相关资源
        最近更新 更多