映射到相同值的两个键。 Java

Two keys mapped to the same value. Java

当两个键映射到同一个值时,是否有任何解决方案可以避免使用两个映射?问题:我们有一个服务器接收两种类型的请求 - getDataByUserID(UserID userId)getDataByNodeID(NodeID nodeId),其中 userIdnodeId 具有一对一的映射。没有使用数据库,所有数据都存储在内存中。有一个直接的解决方案——使用两个地图,一个 UserID/data 和第二个 NodeID/data,但我想避免对两个表进行操作。 服务器接口为:

void put (InetSocketAddress nodeId, String clientId, Data data);
Data get (InetSocketAddress nodeId);
Data get (String clientId);

欢迎提出任何建议。

当然可以。大量的解决方案。

自己制作class

做一个class。这个 class 内部有 2 个字段:Map<UserId, Data> userIdToDataMap<NodeId, Data> nodeIdToData。这些是私人领域。 class 本身并不实现 java.util.Map,但它有许多方法,例如 size()(它只是实现 return userIdToData.size();)。它还具有:

public void put(UserId userId, NodeId nodeId, Data data) {
    userIdToData.put(userId, data);
    nodeIdToData.put(nodeId, data);
}

public Data getByUserId(UserId userId) {
    return userIdToData.get(userId);
}

// ... and getByNodeId, and getByUserIdOrDefault, etcetera.

代码会弄乱 2 个地图,需要确保它们保持同步,但您可以轻松地为此代码和整个项目中的所有其他代码编写大量测试那就不用担心了。

将 UserId 映射到 NodeId,然后将 NodeId 映射到数据

有 2 张地图:

Map<UserId, NodeId> userToNode;
Map<NodeId, Data> nodeToData;

和一个方法:

public Data getByUserId(UserId id) {
    NodeId node = userToNode.get(id);
    if (node == null) return null;
    return nodeToData.get(node);
}

或许可以为 UserId 和 NodeId 添加一个接口。

像这样:

void test()
{
    UserId userId = new UserId();
    NodeId nodeId = new NodeId();
    String userData = "xyz";

    HashMap<IdValue, String> idToDataMap = new HashMap<>();
    idToDataMap.put(userId,userData);
    idToDataMap.put(nodeId,userData);
}

class UserId implements IdValue {
    //...
}

class NodeId implements IdValue {
    //...
}

interface IdValue {
}

没有智能数据结构可以做到这一点。正如@JavaMan 建议的那样,如果键具有共同的超类型,则可以将一个 HashMap 与两种类型的键一起使用。但是,如果您需要并发解决方案,那么在没有竞争条件的情况下这将很难实现。 (您不能自动添加或删除两个条目...)

有一个 讨厌的 解决方案可以避免这种情况。您可以重新定义 NodeIdUserId 类 以实现通用接口;说 CommonId。然后你需要重新定义 equals(Object)hashCode() 方法,使它们将这两种标识符视为等价的。

例如:

UserId a = ...
NodeId b = ... // representing the same user as 'a'

然后

a.equals(b) => true
b.equals(a) => true
a.hashCode() == b.hashCode()

除了equals/hashcode契约的其他方面。

重要提示:这假设有一种高效方法来实现不依赖于地图的上述语义我们正在定义。

然后您可以使用 UserId 实例或 NodeId 实例作为键将映射更改为 HashMap<CommonId, YourValueClass>putget .

为什么这么讨厌?

  • 因为 equalshashCode 的重新定义适用于这两个 类 的所有用途,而不仅仅是这张地图。

  • 因为它违反了 equals(Object) 的文档语义。 javadocs 说 this.equals(other) 应该 return false 如果 thisother 有不同的 类。

但是,这确实提出了一些替代解决方案:

  1. 您可以使用 TreeMap 而不是 HashMap,并提供提供一致排序的 Comparator<CommonId>,并将任一类型的等效标识符视为等于。

    • 尚不清楚实施排序是否可行。
    • 对于 getput 操作,TreeMap 是 O(logN) 而不是 O(1)
  2. 您可以尝试找到允许您提供散列和等于函数的第 3 方散列映射实现;即类似于将 Comparator 提供给 TreeMap.