其他人已经提出了一些原始值映射的第三方实现。为了完整起见,我想提一些您可能要考虑的完全摆脱地图的方法。这些解决方案并不总是可能的,但是当它们成为可能时,它们通常比任何地图都更快,内存效率更高。
备选方案 1:使用普通的旧阵列。
一个简单的数组可能不像花哨的地图那样性感,但很少有数组可以在紧凑性和访问速度上击败它。double[]
当然,数组有一堆限制:它们的大小是固定的(尽管你总是可以创建一个新数组并将旧数组的内容复制到其中),它们的键只能是小的正整数,为了提高效率,它们应该是相当密集的(即,使用的键的总数应该是最高键值的合理大一部分)。但是,如果您的键碰巧是这种情况,或者您可以安排这种情况,那么基元值数组可以非常有效。
特别是,如果可以为每个键对象分配一个唯一的小整数 ID,则可以将该 ID 用作数组的索引。同样,如果您已经将对象存储在数组中(例如,作为某个更复杂的数据结构的一部分)并按索引查找它们,那么您可以简单地使用相同的索引来查找另一个数组中的任何其他元数据值。
如果您实现了某种冲突处理机制,您甚至可以免除ID唯一性要求,但是在这一点上,您正在实现自己的哈希表。在某些情况下,这可能实际上有意义,但通常在这一点上,使用现有的第三方实现可能更容易。
备选方案 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;
}
,然后向包含连接到该节点的任何边的每个节点添加一个 (or ) 属性。Edge[]
Vector<Edge>
备选方案 3:将多个地图合并为一个。
如果您有多个具有相同键的映射,并且不能像上面建议的那样将这些值转换为键对象的新属性,请考虑将它们分组到单个元数据类中,并从键创建单个映射到该类的对象。例如,考虑定义单个元数据类,而不是 a 和 a,如下所示:Map<Item, Double> accessFrequency
Map<Item, Long> creationTime
class ItemMetadata {
double accessFrequency;
long creationTime;
}
并有一个存储所有元数据值的单个。这比拥有多个映射更节省内存,并且还可以通过避免冗余映射查找来节省时间。Map<Item, ItemMetadata>
在某些情况下,为方便起见,您可能还希望在每个元数据对象中包括对其相应主对象的引用,以便您可以通过对元数据对象的单个引用来访问这两个对象。这自然而然地演变成...
备选方案 4:使用装饰器。
作为前两种替代方法的组合,如果无法直接将额外的元数据属性添加到关键对象中,请考虑使用可以保存额外值的装饰器包装它们。因此,例如,与其直接创建具有额外属性的点类,不如简单地执行以下操作:
class PointWrapper {
Point2D point;
boolean onSea;
// ...
}
如果您愿意,您甚至可以通过实现方法转发将此包装器变成一个完整的装饰器,但即使只是一个简单的“哑”包装器也可能足以满足许多目的。
如果随后可以安排仅存储和使用包装器,则此方法最有用,这样您就永远不需要查找与未包装对象对应的包装器。当然,如果你确实需要偶尔这样做(例如,因为你只从其他代码接收未包装的对象),那么你可以用单个,但这样你就可以有效地回到以前的替代方案。Map<Point2D, PointWrapper>