Getting the current key of a map entry given another equivalent key object

时间:2017-12-18 04:55:01

标签: java dictionary hashmap

Suppose I have a HashMap<K, V> and two objects of type K that are equal to each other but not the same object, and the map has an entry for key k1.

Given k2, can I get a reference to k1 using only methods from HashMap (no external data structures) that executes in constant time, ie O(1) time complexity?

In code:

K k1, k2;
k1.equals(k2) // true
k1.hashCode() == k2.hashCode() // true
k1 == k2 // false
myMap.put(k1, someValue);

K existingKey = getExistingKey(myMap, k2);
existingKey == k1 // true <- this is the goal

<K> K getExistingKey(HashMap<K, V> map, K k) {
    // What impl goes here?
}

I was hoping to use one of the various methods added with java 8, such as compute() to "sniff" the existing key within the lambda, but they all (seem to) pass the new key object to the lambda, not the existing key.

Iterating through the entrySet() would find the existing key, but not in constant time.

I could use a Map<K, K> to store the keys and I could keep it in sync, but that doesn't answer the question.

2 个答案:

答案 0 :(得分:1)

You are looking for something like

Map.Entry<K,V> getEntry(K key)

At first I thought it would be easy to make a custom subclass of HashMap to return this, as get(K key) is just

public V get(Object key) {
     Node<K,V> e;
     return (e = getNode(hash(key), key)) == null ? null : e.value;
}

where Node implements Map.Entry. This would look like:

public class MyHashMap<K,V> extends HashMap<K,V>
{
    public MyHashMap() {}
    // Other constructors as needed

    public Map.Entry<K, V> getEntry(K key)
    {
        Map.Entry<K, V> e = getNode(hash(key),key);
        return e;
    }
}

Unfortunately, getNode() and hash() are both package private and therefore not visible to subclasses.

The next step was to put the class in java.util but this fails in Java 9 with

The package java.util conflicts with a package accessible from another module: java.base

I think you're out of luck here.

I actually think a getEntry() method would be a useful addition to the API, you might consider filing an enhancement request.

答案 1 :(得分:0)

我不知道您对内存使用情况的限制,但是如果您可以使用LinkedHashMap代替HashMapLinkedHashMap使用额外引用来保持广告订单顺序),那么你可以利用它的removeEldestEntry方法:

public class HackedMap<K, V> extends LinkedHashMap<K, V> {

    K lastKey;

    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        lastKey = eldest.getKey();
        return false;
    }

    K getLastKey() {
        return lastKey;
    }
}

我认为代码是不言自明的。我们保留对原始密钥的引用,我们从removeEldestEntry方法的参数中获取该密钥。 关于removeEldestEntry方法的返回值,它是false,因此我们不允许删除最年长的条目(毕竟,我们不想要地图作为缓存工作。)

现在,使用常见的广告订单LinkedHashMapremoveEldestEntryput(来自putAll方法文档)会自动调用removeEldestEntry方法:

  

在将新条目插入地图后,put和putAll将调用此方法。

所以我们现在需要做的就是实现你的getExistingKey方法,使其调用put而不修改地图,你可以按照以下方式执行:

<K, V> K getExistingKey(HackedMap<K, V> map, K k) {
    if (k == null) return null;
    V v = map.get(k);
    if (v == null) return null;
    map.put(k, v);
    return map.getLastKey();
}

这是有效的,因为当地图已经包含映射到给定键的条目时,put方法会替换该值而不会触及该键。

我不确定我做过的空检查,也许你需要改进它。当然,HackedMap并不支持并发访问,但HashMapLinkedHashMap也不支持。

您可以安全地使用HackedMap代替HashMap。这是测试代码:

Key k1 = new Key(10, "KEY 1");
Key k2 = new Key(10, "KEY 2");
Key k3 = new Key(10, "KEY 3");

HackedMap<Key, String> myMap = new HackedMap<>();

System.out.println(k1.equals(k2)); // true
System.out.println(k1.equals(k3)); // true
System.out.println(k2.equals(k3)); // true

System.out.println(k1.hashCode() == k2.hashCode()); // true
System.out.println(k1.hashCode() == k3.hashCode()); // true
System.out.println(k2.hashCode() == k3.hashCode()); // true

System.out.println(k1 == k2); // false
System.out.println(k1 == k3); // false
System.out.println(k2 == k3); // false

myMap.put(k1, "k1 value");
System.out.println(myMap); // {Key{k=10, d='KEY 1'}=k1 value}

myMap.put(k3, "k3 value"); // Key k1 (the one with its field d='KEY 1') remains in
                           // the map but value is now 'k3 value' instead of 'k1 value'

System.out.println(myMap); // {Key{k=10, d='KEY 1'}=k3 value}

Key existingKey = getExistingKey(myMap, k2);

System.out.println(existingKey == k1); // true
System.out.println(existingKey == k2); // false
System.out.println(existingKey == k3); // false

// Just to be sure
System.out.println(myMap); // {Key{k=10, d='KEY 1'}=k3 value}

以下是我使用的Key课程:

public class Key {

    private final int k;

    private final String d;

    Key(int k, String d) {
        this.k = k;
        this.d = d;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Key key1 = (Key) o;
        return k == key1.k;
    }

    @Override
    public int hashCode() {
        return Objects.hash(k);
    }

    @Override
    public String toString() {
        return "Key{" +
                "k=" + k +
                ", d='" + d + '\'' +
                '}';
    }
}