简体   繁体   English

Java:哈希图中的复合键

[英]Java: Composite key in hashmaps

I would like to store a group of objects in a hashmap, where the key shall be a composite of two string values.我想将一组对象存储在哈希图中,其中键应该是两个字符串值的组合。 is there a way to achieve this?有没有办法做到这一点?

i can simply concatenate the two strings, but im sure there is a better way to do this.我可以简单地连接两个字符串,但我确信有更好的方法来做到这一点。

You could have a custom object containing the two strings:您可以有一个包含两个字符串的自定义对象:

class StringKey {
    private String str1;
    private String str2;
}

Problem is, you need to determine the equality test and the hash code for two such objects.问题是,您需要确定两个此类对象的相等性测试和哈希码。

Equality could be the match on both strings and the hashcode could be the hashcode of the concatenated members (this is debatable):相等性可以是两个字符串的匹配,哈希码可以是串联成员的哈希码(这是有争议的):

class StringKey {
    private String str1;
    private String str2;

    @Override
    public boolean equals(Object obj) {
        if(obj != null && obj instanceof StringKey) {
            StringKey s = (StringKey)obj;
            return str1.equals(s.str1) && str2.equals(s.str2);
        }
        return false;
    }

    @Override
    public int hashCode() {
        return (str1 + str2).hashCode();
    }
}

You don't need to reinvent the wheel.您无需重新发明轮子。 Simply use the Guava 's HashBasedTable<R,C,V> implementation of Table<R,C,V> interface, for your need.根据您的需要,只需使用Table<R,C,V>接口的GuavaHashBasedTable<R,C,V>实现。 Here is an example这是一个例子

Table<String, String, Integer> table = HashBasedTable.create();

table.put("key-1", "lock-1", 50);
table.put("lock-1", "key-1", 100);

System.out.println(table.get("key-1", "lock-1")); //prints 50
System.out.println(table.get("lock-1", "key-1")); //prints 100

table.put("key-1", "lock-1", 150); //replaces 50 with 150

Happy coding!编码愉快!

public int hashCode() {
    return (str1 + str2).hashCode();
}

This seems to be a terrible way to generate the hashCode: Creating a new string instance every time the hash code is computed is terrible.这似乎是一种生成哈希代码的糟糕方法:每次计算哈希代码时都创建一个新的字符串实例是糟糕的。 (Even generating the string instance once and caching the result is poor practice.) (即使生成一次字符串实例并缓存结果也是不好的做法。)

There are a lot of suggestions here:这里有很多建议:

How do I calculate a good hash code for a list of strings? 如何为字符串列表计算一个好的哈希码?

public int hashCode() {
    final int prime = 31;
    int result = 1;
    for ( String s : strings ) {
        result = result * prime + s.hashCode();
    }
    return result;
}

For a pair of strings, that becomes:对于一对字符串,它变为:

return string1.hashCode() * 31 + string2.hashCode();

That is a very basic implementation.这是一个非常基本的实现。 Lots of advice through the link to suggest better tuned strategies.通过链接提供很多建议,以建议更好的调整策略。

Why not create a (say) Pair object, which contains the two strings as members, and then use this as the key?为什么不创建一个(比如) Pair对象,其中包含两个字符串作为成员,然后将其用作键?

eg例如

public class Pair {
   private final String str1;
   private final String str2;

   // this object should be immutable to reliably perform subsequent lookups
}

Don't forget about equals() and hashCode() .不要忘记equals()hashCode() See this blog entry for more on HashMaps and keys, including a background on the immutability requirements.有关 HashMap 和键的更多信息,包括不变性要求的背景,请参阅此博客条目 If your key isn't immutable, then you can change its components and a subsequent lookup will fail to locate it (this is why immutable objects such as String are good candidates for a key)如果您的密钥不是不可变的,那么您可以更改其组件,随后的查找将无法找到它(这就是为什么不可变对象(例如String是密钥的良好候选者)

You're right that concatenation isn't ideal.你是对的,串联并不理想。 For some circumstances it'll work, but it's often an unreliable and fragile solution (eg is AB/C a different key from A/BC ?).在某些情况下它会起作用,但它通常是一个不可靠且脆弱的解决方案(例如, AB/C是与A/BC不同的密钥吗?)。

I have a similar case.我有一个类似的案例。 All I do is concatenate the two strings separated by a tilde ( ~ ).我所做的就是连接两个由波浪号 (~) 分隔的字符串。

So when the client calls the service function to get the object from the map, it looks like this:所以当客户端调用服务函数从地图中获取对象时,它看起来是这样的:

MyObject getMyObject(String key1, String key2) {
    String cacheKey = key1 + "~" + key2;
    return map.get(cachekey);
}

It is simple, but it works.这很简单,但很管用。

I see that many people use nested maps.我看到很多人使用嵌套地图。 That is, to map Key1 -> Key2 -> Value (I use the computer science/ aka haskell curring notation for (Key1 x Key2) -> Value mapping which has two arguments and produces a value), you first supply the first key -- this returns you a (partial) map Key2 -> Value , which you unfold in the next step.也就是说,要映射Key1 -> Key2 -> Value (我使用计算机科学/ aka haskell curring notation for (Key1 x Key2) -> Value mapping 有两个参数并产生一个值),你首先提供第一个键 - - 这会返回一个(部分)映射Key2 -> Value ,您将在下一步中展开它。

For instance,例如,

Map<File, Map<Integer, String>> table = new HashMap(); // maps (File, Int) -> Distance

add(k1, k2, value) {
  table2 = table1.get(k1);
  if (table2 == null) table2 = table1.add(k1, new HashMap())
  table2.add(k2, value)
}

get(k1, k2) {
  table2 = table1.get(k1);
  return table2.get(k2)
}

I am not sure that it is better or not than the plain composite key construction.我不确定它是否比普通复合键结构更好。 You may comment on that.你可以对此发表评论。

Reading about the spaguetti/cactus stack I came up with a variant which may serve for this purpose, including the possibility of mapping your keys in any order so that map.lookup("a","b") and map.lookup("b","a") returns the same element.阅读有关 spaguetti/cactus 堆栈的信息,我想出了一个可能用于此目的的变体,包括以任何顺序映射您的键的可能性,以便 map.lookup("a","b") 和 map.lookup(" b","a") 返回相同的元素。 It also works with any number of keys not just two.它还适用于任意数量的键,而不仅仅是两个。

I use it as a stack for experimenting with dataflow programming but here is a quick and dirty version which works as a multi key map (it should be improved: Sets instead of arrays should be used to avoid looking up duplicated ocurrences of a key)我将它用作堆栈来试验数据流编程,但这里有一个快速而肮脏的版本,它用作多键映射(应该改进:应该使用集合而不是数组来避免查找键的重复出现)

public class MultiKeyMap <K,E> {
    class Mapping {
        E element;
        int numKeys;
        public Mapping(E element,int numKeys){
            this.element = element;
            this.numKeys = numKeys;
        }
    }
    class KeySlot{
        Mapping parent;
        public KeySlot(Mapping mapping) {
            parent = mapping;
        }
    }
    class KeySlotList extends LinkedList<KeySlot>{}
    class MultiMap extends HashMap<K,KeySlotList>{}
    class MappingTrackMap extends HashMap<Mapping,Integer>{}

    MultiMap map = new MultiMap();

    public void put(E element, K ...keys){
        Mapping mapping = new Mapping(element,keys.length);
        for(int i=0;i<keys.length;i++){
            KeySlot k = new KeySlot(mapping);
            KeySlotList l = map.get(keys[i]);
            if(l==null){
                l = new KeySlotList();
                map.put(keys[i], l);
            }
            l.add(k);
        }
    }
    public E lookup(K ...keys){
        MappingTrackMap tmp  = new MappingTrackMap();
        for(K key:keys){
            KeySlotList l = map.get(key);
            if(l==null)return null;
            for(KeySlot keySlot:l){
                Mapping parent = keySlot.parent;
                Integer count = tmp.get(parent);
                if(parent.numKeys!=keys.length)continue;
                if(count == null){
                    count = parent.numKeys-1;
                }else{
                    count--;
                }
                if(count == 0){
                    return parent.element;
                }else{
                    tmp.put(parent, count);
                }               
            }
        }
        return null;
    }
    public static void main(String[] args) {
        MultiKeyMap<String,String> m = new MultiKeyMap<String,String>();
        m.put("brazil", "yellow", "green");
        m.put("canada", "red", "white");
        m.put("USA", "red" ,"white" ,"blue");
        m.put("argentina", "white","blue");

        System.out.println(m.lookup("red","white"));  // canada
        System.out.println(m.lookup("white","red"));  // canada
        System.out.println(m.lookup("white","red","blue")); // USA
    }
}
public static String fakeMapKey(final String... arrayKey) {
    String[] keys = arrayKey;

    if (keys == null || keys.length == 0)
        return null;

    if (keys.length == 1)
        return keys[0];

    String key = "";
    for (int i = 0; i < keys.length; i++)
        key += "{" + i + "}" + (i == keys.length - 1 ? "" : "{" + keys.length + "}");

    keys = Arrays.copyOf(keys, keys.length + 1);

    keys[keys.length - 1] = FAKE_KEY_SEPARATOR;

    return  MessageFormat.format(key, (Object[]) keys);}
public static string FAKE_KEY_SEPARATOR = "~";

INPUT: fakeMapKey("keyPart1","keyPart2","keyPart3");
OUTPUT: keyPart1~keyPart2~keyPart3

I'd like to mention two options that I don't think were covered in the other answers.我想提一下我认为其他答案中没有涵盖的两个选项。 Whether they are good for your purpose you will have to decide yourself.它们是否适合您的目的,您必须自己决定。

Map<String, Map<String, YourObject>>地图<字符串,地图<字符串,你的对象>>

You may use a map of maps, using string 1 as key in the outer map and string 2 as key in each inner map.您可以使用地图的地图,在外部地图中使用字符串 1 作为键,在每个内部地图中使用字符串 2 作为键。

I do not think it's a very nice solution syntax-wise, but it's simple and I have seen it used in some places.我不认为它在语法方面是一个很好的解决方案,但它很简单而且我已经看到它在某些地方使用过。 It's also supposed to be efficient in time and memory, while this shouldn't be the main reason in 99 % of cases.它在时间和内存方面也应该是高效的,但在 99% 的情况下这不应该是主要原因。 What I don't like about it is that we've lost the explicit information about the type of the key: it's only inferred from the code that the effective key is two strings, it's not clear to read.我不喜欢的是我们丢失了关于密钥类型的明确信息:它只是从代码中推断出有效密钥是两个字符串,阅读起来并不清晰。

Map<YourObject, YourObject>地图<你的对象,你的对象>

This is for a special case.这是针对特殊情况的。 I have had this situation more than once, so it's not more special than that.这种情况我遇到过不止一次,所以没有比这更特别的了。 If your objects contain the two strings used as key and it makes sense to define object equality based on the two, then define equals and hashCode in accordance and use the object as both key and value.如果您的对象包含用作键的两个字符串,并且基于这两者定义对象相等性是有意义的,则相应地定义equalshashCode并将该对象用作键和值。

One would have wished to use a Set rather than a Map in this case, but a Java HashSet doesn't provide any method to retrieve an object form a set based on an equal object.在这种情况下,人们可能希望使用Set而不是Map ,但是 Java HashSet不提供任何方法来从基于相等对象的集合中检索对象。 So we do need the map.所以我们确实需要地图。

One liability is that you need to create a new object in order to do lookup.一个责任是您需要创建一个新对象才能进行查找。 This goes for the solutions in many of the other answers too.这也适用于许多其他答案中的解决方案。

Link关联

Jerónimo López: Composite key in HashMaps on the efficiency of the map of maps. Jerónimo López:HashMaps 中关于 map of maps 效率的 Composite key

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM