其他人已经建议了几个原始值映射的第三方实现。为了完整起见,我想提一些您可能想要考虑的完全摆脱地图的方法。这些解决方案并不总是可行的,但当它们可行时,它们通常会比任何地图都更快且内存效率更高。
备选方案 1:使用普通的旧数组。
一个简单的double[] 数组可能没有精美的地图那么性感,但在紧凑性和访问速度方面几乎没有什么能比得上它。
当然,数组有很多限制:它们的大小是固定的(尽管您总是可以创建一个新数组并将旧数组的内容复制到其中),并且它们的键只能是小的正整数,为了提高效率,应该是相当密集的(即使用的键的总数应该是最高键值的相当大的一部分)。但是,如果您的键恰好是这种情况,或者您可以安排这种情况,原始值数组可能会非常有效。
特别是,如果您可以为每个键对象分配一个唯一的小整数 ID,那么您可以使用该 ID 作为数组的索引。同样,如果您已经将对象存储在一个数组中(例如,作为一些更复杂的数据结构的一部分)并通过索引查找它们,那么您可以简单地使用相同的索引来查找另一个数组中的任何其他元数据值。
如果您实现了某种冲突处理机制,您甚至可以免除 ID 唯一性要求,但此时您已经在实现自己的哈希表的路上了。在某些情况下,可能实际上是有意义的,但通常此时使用现有的第三方实现可能更容易。
备选方案 2:自定义您的对象。
与其维护从关键对象到原始值的映射,为什么不把这些值变成对象本身的属性呢?毕竟,这就是面向对象编程的全部内容——将相关数据分组为有意义的对象。
例如,与其维护HashMap<Point2D, Boolean> onSea,不如直接给您的点一个布尔值onSea 属性?当然,您需要为此定义自己的自定义点类,但是如果您愿意,没有理由不能让它扩展标准的Point2D 类,以便您可以将自定义点传递给任何方法需要Point2D。
同样,这种方法可能并不总是直接有效,例如如果您需要使用无法修改的类(但请参见下文),或者您要存储的值与多个对象相关联(如在您的 ConcurrentHashMap<Point2D, HashMap<Point2D, Double>> 中)。
但是,对于后一种情况,您仍然可以通过适当地重新设计数据表示来解决问题。例如,您可以定义一个Edge 类,而不是将加权图表示为Map<Node, Map<Node, Double>>,如下所示:
class Edge {
Node a, b;
double weight;
}
然后将Edge[](或Vector<Edge>)属性添加到包含连接到该节点的任何边的每个节点。
备选方案 3:将多张地图合二为一。
如果您有多个具有相同键的映射,并且不能像上面建议的那样将值转换为键对象的新属性,请考虑将它们分组到单个元数据类中,并创建一个从键到该对象的单个映射班级。例如,考虑定义一个元数据类,而不是 Map<Item, Double> accessFrequency 和 Map<Item, Long> creationTime,例如:
class ItemMetadata {
double accessFrequency;
long creationTime;
}
并有一个Map<Item, ItemMetadata> 来存储所有元数据值。这比拥有多个地图更节省内存,并且还可以通过避免冗余地图查找来节省时间。
在某些情况下,为方便起见,您可能还希望在每个元数据对象中包含对其相应主对象的引用,以便您可以通过对元数据对象的单个引用来访问两者。这很自然地进入...
备选方案 4:使用装饰器。
作为前两种选择的组合,如果您不能直接将额外的元数据属性添加到关键对象中,请考虑使用可以保存额外值的decorators 包装它们。因此,例如,您可以简单地执行以下操作,而不是直接创建您自己的带有额外属性的点类:
class PointWrapper {
Point2D point;
boolean onSea;
// ...
}
如果您愿意,您甚至可以通过实现方法转发将这个包装器变成一个成熟的装饰器,但即使只是一个简单的“愚蠢”包装器也可能足以满足多种用途。
如果您可以安排仅存储和使用包装器,则此方法最有用,这样您就无需查找对应于未包装对象的包装器。当然,如果您确实需要偶尔这样做(例如,因为您只接收来自其他代码的解包对象),那么您可以使用单个 Map<Point2D, PointWrapper> 来执行此操作,但是您实际上又回到了之前的选择.