Java: Composite key in hashmaps

前端 未结 9 650
太阳男子
太阳男子 2020-12-24 12:53

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 concate

相关标签:
9条回答
  • 2020-12-24 13:34

    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.

    0 讨论(0)
  • 2020-12-24 13:35

    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.

    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.

    0 讨论(0)
  • 2020-12-24 13:35
    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
    0 讨论(0)
  • 2020-12-24 13:36

    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();
        }
    }
    
    0 讨论(0)
  • 2020-12-24 13:36
    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.

    0 讨论(0)
  • 2020-12-24 13:36

    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.

    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. 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.

    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. 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.

    0 讨论(0)
提交回复
热议问题