我需要创建一个缓存第三方查找服务结果的地图。该请求由两个对象组成,例如time
和month
。地图需要在(time, month)
和结果之间进行映射。
我最初的想法是创建一个对象来将time
和month
有效地包装成一个元组对象,因此缓存是这个对象和结果之间的映射。
有没有更好的方法来执行此操作,而无需在每次需要使用缓存时将请求包装到元组对象中?
非常感谢。
答案 0 :(得分:5)
我最初的想法是创建一个对象将
time
和month
有效地包装成一个元组对象
这是正确的想法。覆盖元组的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
我的结果是,在为复合键分配对象之间实际上没有太多的运行时差异,或者在没有对象的对象分配的情况下进行三级缓存查找。但是,使用的基准测试框架没有正确考虑垃圾收集。在我切换到另一个基准框架后,我将进行更全面的评估。