对和值之间的Java Map

时间:2015-12-01 16:45:32

标签: java caching dictionary hashmap

我需要创建一个缓存第三方查找服务结果的地图。该请求由两个对象组成,例如timemonth。地图需要在(time, month)和结果之间进行映射。

我最初的想法是创建一个对象来将timemonth有效地包装成一个元组对象,因此缓存是这个对象和结果之间的映射。

有没有更好的方法来执行此操作,而无需在每次需要使用缓存时将请求包装到元组对象中?

非常感谢。

6 个答案:

答案 0 :(得分:5)

  

我最初的想法是创建一个对象将timemonth有效地包装成一个元组对象

这是正确的想法。覆盖元组的hashCode()equals(Object)以使其与HashMap<TimeMonthTuple>compareTo(TimeMonthTuple)一起使用,以使其与TreeMap<TimeMonthTuple>一起使用

  

有更好的方法吗?

这是最简单的方法,除非你有一个类可以用有意义的东西替换TimeMonthTuple。例如,时间和日期可以合并为Date对象。

在某些情况下,您可以根据原始包装器创建密钥。例如,如果time表示为自午夜以来的分钟数,month是1到12之间的数字(包括1和12),则可以将这两个值都包装到Integer中,然后使用它作为关键:

Integer makeTimeMonthKey(int time, int month) {
    return (time * 12) + (month - 1);
}

答案 1 :(得分:0)

如果你想避免创建一个包装器对象,你应该看一下Guava Tables

答案 2 :(得分:0)

您可以创建地图地图:

Map<MonthType, Map<TimeType, Value>> map;

所以你打电话:

Value value = map.get(month).get(time);

检索值(前提是您之前为month添加了值)。

然而,直接使用并不是特别好,因为您需要进行大量containsKey / null次检查。你可以将它包装成一个便利类:

class MapOfMaps {
  final Map<MonthType, Map<TimeType, Value>> map = new HashMap<>();

  void put(MonthType month, TimeType time, Value value) {
    Map<TimeType, Value> timeMap;
    if (map.containsKey(month)) {
      timeMap = map.get(month);
    } else { 
      timeMap = new HashMap<>();
      map.put(month, timeMap);
    }
    timeMap.put(time, value);
  }

  Value get(MonthType month, TimeType time) {
    if (!map.containsKey(month)) {
      return null;
    }
    return map.get(month).get(time);
  }
}

答案 3 :(得分:0)

我也会这样做,但是在定义HashMap的Key时要小心:它应该是不可变的,否则改变它可能会破坏映射和散列,并且应该实现散列键的两个要求:hashCode和等于方法。 像这样:

final class YourWrapper {
    private final Integer month;
    private final Integer time;

    public YourWrapper(Integer month, Integer time) {
        this.month = month;
        this.time = time;
    }

    public Integer getMonth() {
        return month;
    }

    public Integer getTime() {
        return time;
    }

    @Override
    public int hashCode() {
        return month.hashCode() ^ time.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        return (obj instanceof YourWrapper) 
                && ((YourWrapper) obj).month.equals(month)
                && ((YourWrapper) obj).time.equals(time);
    }
}

答案 4 :(得分:0)

如果您不想使用地图地图(不是很好,但它有效),您仍然有一些选择...例如在字符串中存储月份和时间,使用类似的东西 DateFormat ds = new SimpleDateFormat(MM HH:mm:s),然后用于转换填充了您的值的日历对象

Calendar cal = Calendar.getInstance();
cal.set(Calendar.MONTH, yourmonth);
cal.set(Calendar.HOUR_OF_DAY, yourhours);
cal.set(Calendar.MINUTE, yourminutes);
cal.set(Calendar.SECOND, yoursecs);
String val_to_store=ds.format(cal.getTime());

或者,也许,您可以存储日历对象。

答案 5 :(得分:0)

很棒的问题!

首先,@ dasblinkenlight的答案是正确的,让我们说,大部分时间。构建单个对象键是最直接和最明显的解决方案。它易于理解且非常有效。如果此缓存不是您的应用程序的热点,则无需再次考虑。

但是,有一些替代方案可能会提高效率。

从概念上讲,有两种可能性:

  • 为复合键构造单个关键对象。这是一种常见模式,如果您使用复合键进行数据库访问则非常典型
  • 执行两级或多级分层查找,例如:store.get(month).get(time)

对于分层查找,不需要额外的对象分配,但是,您将其交换为第二个哈希表访问。为了获得最大的内存效率,首先放置具有最小值空间的密钥非常重要。

如果这是您的应用程序的一个非常重要的位置,更好的方法是将第一个查找阶段,即十二个月,放在一个数组中并在启动时初始化它:

Cache<Time, Value>[] month2ValueCache = new Cache<Time, Value>[12];
{
  for (int i = 0; i < 12; i++) {
    month2ValueCache[i] = new Cache<Time, Value>(...);
  }
}
Value get(int month, Time, time) {
  return month2ValueCache[month].get(time);
}

我使用DateFromatter对日期格式进行了比较基准测试。这种接缝类似于您的使用案例。这实际上有三个关键组件:日期,格式和区域设置。您可以在此处找到它:https://github.com/headissue/cache2k-benchmark/blob/master/zoo/src/test/java/org/cache2k/benchmark/DateFormattingBenchmark.java

我的结果是,在为复合键分配对象之间实际上没有太多的运行时差异,或者在没有对象的对象分配的情况下进行三级缓存查找。但是,使用的基准测试框架没有正确考虑垃圾收集。在我切换到另一个基准框架后,我将进行更全面的评估。