原始值的映射替代方案

2024-03-17

我对我的应用程序进行了一些分析,结果之一表明堆上大约 18% 的内存被 类型的对象使用Double。事实证明这些对象是中的值Maps,我不能使用原始类型。

我的推理是原始类型double比它的对象消耗更少的内存Double。有没有一种方法可以拥有类似地图的数据结构,可以接受任何类型作为键和基元double作为价值观?

主要业务是:

  • 插入(可能只插入一次)
  • 查找(按键包含)
  • 检索(按键)
  • 迭代

我拥有的典型地图是:

  • HashMap<T, HashMap<NodeData<T>, Double>> graph
  • HashMap<Point2D, Boolean> onSea(虽然不是双值)
  • ConcurrentHashMap<Point2D, HashMap<Point2D, Double>>

全部与 Java 8 一起使用。

Addendum

我主要对解决这些类型地图的框架不感兴趣,而是对解决这些问题时必须考虑的问题感兴趣。如果您愿意,任何此类框架背后的概念/想法/方法是什么。或者解决方案也可能在另一个层面上,其中地图被遵循某种模式的对象替换,就像 @Ilmari Karonen 在他的回答中指出的那样。


其他人已经建议了几种原始值映射的第三方实现。为了完整起见,我想提一些方法完全摆脱地图您可能需要考虑。这些解决方案并不总是可行,但当它们可行时,它们通常会比任何地图更快、更节省内存。

替代方案 1:使用普通的旧数组。

一个简单的double[]数组可能不像精美的地图那么性感,但在紧凑性和访问速度方面很少有人可以击败它。

当然,数组有很多限制:它们的大小是固定的(尽管您始终可以创建一个新数组并将旧数组的内容复制到其中),并且它们的键只能是小的正整数,为了提高效率,应该合理地设置它们。密集(即使用的密钥总数应该是最高密钥值的相当大的一部分)。但是,如果您的键恰好属于这种情况,或者您可以安排这种情况,则原始值数组可能会非常有效。

特别是,如果您可以为每个键对象分配一个唯一的小整数 ID,那么您可以使用该 ID 作为数组的索引。同样,如果您已经将对象存储在数组中(例如,作为某些更复杂的数据结构的一部分)并通过索引查找它们,那么您可以简单地使用相同的索引来查找另一个数组中的任何其他元数据值。

如果您实现了某种冲突处理机制,您甚至可以免除 ID 唯一性要求,但此时您就已经朝着实现自己的哈希表迈进了。在某些情况下might实际上是有道理的,但通常在那时使用现有的第三方实现可能会更容易。

替代方案 2:自定义您的对象。

与其维护从关键对象到原始值的映射,为什么不直接将这些值变成对象本身的属性呢?毕竟,这就是面向对象编程的全部内容——将相关数据分组为有意义的对象。

例如,而不是维护一个HashMap<Point2D, Boolean> onSea,为什么不直接给你的点一个布尔值onSea财产?当然,您需要为此定义自己的自定义点类,但没有理由不能让它扩展标准Point2D如果需要的话,可以将自定义点传递给任何需要的方法Point2D.

同样,这种方法可能并不总是直接有效,例如如果您需要使用无法修改的类(但请参见下文),或者如果您要存储的值与多个对象关联(如您的ConcurrentHashMap<Point2D, HashMap<Point2D, Double>>).

但是,对于后一种情况,您仍然可以通过适当地重新设计数据表示来解决问题。例如,不要将加权图表示为Map<Node, Map<Node, Double>>,你可以定义一个Edge类如:

class Edge {
    Node a, b;
    double weight;
}

然后添加一个Edge[] (or Vector<Edge>) 属性到包含连接到该节点的任何边的每个节点。

替代方案 3:将多张地图合并为一张。

如果您有多个具有相同键的映射,并且不能按照上面的建议将值转换为键对象的新属性,请考虑将它们分组到单个元数据类中,并创建从键到该类的对象的单个映射。例如,代替Map<Item, Double> accessFrequency and a Map<Item, Long> creationTime,考虑定义一个元数据类,例如:

class ItemMetadata {
    double accessFrequency;
    long creationTime;
}

并拥有一个Map<Item, ItemMetadata>存储所有元数据值。这比拥有多个映射更节省内存,并且还可以通过避免冗余映射查找来节省时间。

在某些情况下,为了方便起见,您可能还希望在每个元数据对象中包含对其相应主对象的引用,以便您可以通过对元数据对象的单个引用来访问这两个对象。这自然会导致......

替代方案 4:使用装饰器。

作为前两种替代方案的组合,如果您无法直接将额外的元数据属性添加到关键对象中,请考虑使用装饰者 https://en.wikipedia.org/wiki/Decorator_pattern可以保存额外的值。因此,例如,您可以简单地执行以下操作,而不是直接创建具有额外属性的自己的点类:

class PointWrapper {
    Point2D point;
    boolean onSea;
    // ...
}

如果您愿意,您甚至可以通过实现方法转发将这个包装器变成一个成熟的装饰器,但即使只是一个简单的“哑”包装器也可能足以满足许多目的。

如果您可以安排仅存储和使用包装器,则此方法非常有用,这样您就不需要查找与未包装对象相对应的包装器。当然,如果您确实需要偶尔这样做(例如,因为您只从其他代码接收未包装的对象),那么您可以使用单个Map<Point2D, PointWrapper>,但随后您实际上又回到了之前的选择。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

原始值的映射替代方案 的相关文章

随机推荐