HashMap可以在同一个键下存储多个值

246

是否可以使用一个键和两个值来实现HashMap,例如HashMap<userId,clientID,timeStamp>?

如果不行,是否有其他方法来存储多个值,例如一个键和两个值?


2
可能是如何在Map中存储多个字符串?的重复问题。 - Joachim Sauer
谢谢朋友们...但是我在使用MultiHashMap时有一些限制。 - vidhya
可能是具有重复键的Map实现的重复问题。 - Steve Chambers
21个回答

311

你可以采取以下几种方式:

  1. 使用一个以列表为值的映射表,即 Map<KeyType, List<ValueType>>
  2. 创建一个新的包装类,并将该包装类的实例放入映射表中,即 Map<KeyType, WrapperType>
  3. 使用类似于元组的类(避免创建大量的包装类),即 Map<KeyType, Tuple<Value1Type, Value2Type>>
  4. 同时使用多个映射表。

示例

1. 使用列表作为值的映射表

// create our map
Map<String, List<Person>> peopleByForename = new HashMap<>();    

// populate it
List<Person> people = new ArrayList<>();
people.add(new Person("Bob Smith"));
people.add(new Person("Bob Jones"));
peopleByForename.put("Bob", people);

// read from it
List<Person> bobs = peopleByForename["Bob"];
Person bob1 = bobs[0];
Person bob2 = bobs[1];
这种方法的缺点是列表不仅限于两个值。

2. 使用包装类

// define our wrapper
class Wrapper {
    public Wrapper(Person person1, Person person2) {
       this.person1 = person1;
       this.person2 = person2;
    }

    public Person getPerson1() { return this.person1; }
    public Person getPerson2() { return this.person2; }

    private Person person1;
    private Person person2;
}

// create our map
Map<String, Wrapper> peopleByForename = new HashMap<>();

// populate it
peopleByForename.put("Bob", new Wrapper(new Person("Bob Smith"),
                                        new Person("Bob Jones"));

// read from it
Wrapper bobs = peopleByForename.get("Bob");
Person bob1 = bobs.getPerson1();
Person bob2 = bobs.getPerson2();
这种方法的缺点是,您必须为所有这些非常简单的容器类编写大量样板代码。 3. 使用元组
// you'll have to write or download a Tuple class in Java, (.NET ships with one)

// create our map
Map<String, Tuple2<Person, Person> peopleByForename = new HashMap<>();

// populate it
peopleByForename.put("Bob", new Tuple2(new Person("Bob Smith",
                                       new Person("Bob Jones"));

// read from it
Tuple<Person, Person> bobs = peopleByForename["Bob"];
Person bob1 = bobs.Item1;
Person bob2 = bobs.Item2;

我认为这是最好的解决方案。

4. 多个地图

// create our maps
Map<String, Person> firstPersonByForename = new HashMap<>();
Map<String, Person> secondPersonByForename = new HashMap<>();

// populate them
firstPersonByForename.put("Bob", new Person("Bob Smith"));
secondPersonByForename.put("Bob", new Person("Bob Jones"));

// read from them
Person bob1 = firstPersonByForename["Bob"];
Person bob2 = secondPersonByForename["Bob"];
这种解决方案的缺点在于不容易看出这两个映射是相关的,如果出现程序错误,这两个映射可能会失去同步。

嗨,保罗...你能举个例子让它更清晰一些吗...? - vidhya
示例将非常有帮助。 - Xonatron
@Paul,有没有关于#3的简单示例代码? Map<KeyType,Tuple<Value1Type,Value2Type>> - Joarder Kamal
@CoolMind 我相信人们可以解决错误:或者你可以纠正它们,也许? - Paul Ruane
@PaulRuane,抱歉批评了你。是的,你说得对,人们可以克服它们。 - CoolMind
显示剩余5条评论

67
不仅仅是一个HashMap。你需要一个从键到值集合的HashMap。如果您愿意使用外部库,Guava正好有这个概念,使用Multimap实现,例如ArrayListMultimap, HashMultimap, LinkedHashMultimap等。
Multimap<String, Integer> nameToNumbers = HashMultimap.create();

System.out.println(nameToNumbers.put("Ann", 5)); // true
System.out.println(nameToNumbers.put("Ann", 5)); // false
nameToNumbers.put("Ann", 6);
nameToNumbers.put("Sam", 7);

System.out.println(nameToNumbers.size()); // 3
System.out.println(nameToNumbers.keySet().size()); // 2

2
@Deepak:搜索guava multimap的示例,你会找到样本代码。 - Jon Skeet
1
@Deepak:基本上你可以自己去构建一个ArrayListMultimap类似的东西,或者使用一个HashMap<String, List<Integer>>之类的。你需要在第一次添加值时创建一个空列表。 - Jon Skeet
1
你有一个 HashMap<String, List<Integer>> 的工作示例吗? - Deepak
9
我建议你尝试自己创建一个例子,如果遇到困难,请提出包含你已经完成代码的问题。这样你会学到更多东西。 - Jon Skeet
1
Multimap<String, String> multimap = ArrayListMultimap.create(); multimap.put("key", "value"); - Bienvenido David
显示剩余2条评论

27
另一个不错的选择是使用 Apache Commons 中的 MultiValuedMap。请查看页面顶部的所有已知实现类以获取专用实现。
示例:
HashMap<K, ArrayList<String>> map = new HashMap<K, ArrayList<String>>()

可以被替换为

MultiValuedMap<K, String> map = new MultiValuedHashMap<K, String>();

所以,

map.put(key, "A");
map.put(key, "B");
map.put(key, "C");

Collection<String> coll = map.get(key);

会导致集合coll包含"A"、"B"和"C"。


15

看一下 guava-libraries 的 Multimap 及其实现方式 - HashMultimap

Multimap 是类似于Map的集合,但是可以将多个值与一个键相关联。如果您使用相同的键但不同的值两次调用put(K,V),则 multimap 包含从该键到这两个值的映射。


11

我在Map中使用Map<KeyType, Object[]>,以将多个值与一个键关联。这样,我就可以存储不同类型的多个值与一个键相关联。您需要注意维护插入和从Object[]检索的正确顺序。

例如: 考虑我们要存储学生信息。键是id,而我们希望将姓名、地址和电子邮件与学生关联存储。

       //To make entry into Map
        Map<Integer, String[]> studenMap = new HashMap<Integer, String[]>();
        String[] studentInformationArray = new String[]{"name", "address", "email"};
        int studenId = 1;
        studenMap.put(studenId, studentInformationArray);

        //To retrieve values from Map
        String name = studenMap.get(studenId)[1];
        String address = studenMap.get(studenId)[2];
        String email = studenMap.get(studenId)[3];

2
对我来说,这是最好的答案。它更简单、更简洁,也不那么抽象。 - Morey

6
如果你使用的是Spring框架,那么可以使用org.springframework.util.MultiValueMap。要创建一个不可修改的多值映射,请按如下方式操作:
Map<String,List<String>> map = ...
MultiValueMap<String, String> multiValueMap = CollectionUtils.toMultiValueMap(map);

或者使用org.springframework.util.LinkedMultiValueMap



6
HashMap<Integer,ArrayList<String>> map = new    HashMap<Integer,ArrayList<String>>();

ArrayList<String> list = new ArrayList<String>();
list.add("abc");
list.add("xyz");
map.put(100,list);

5

最简单的方法是使用 Google 的集合库:

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;

public class Test {

    public static void main(final String[] args) {

        // multimap can handle one key with a list of values
        final Multimap<String, String> cars = ArrayListMultimap.create();
        cars.put("Nissan", "Qashqai");
        cars.put("Nissan", "Juke");
        cars.put("Bmw", "M3");
        cars.put("Bmw", "330E");
        cars.put("Bmw", "X6");
        cars.put("Bmw", "X5");

        cars.get("Bmw").forEach(System.out::println);

        // It will print the:
        // M3
        // 330E
        // X6
        // X5
    }

}

maven链接:https://mvnrepository.com/artifact/com.google.collections/google-collections/1.0-rc2

更多信息请查阅:http://tomjefferys.blogspot.be/2011/09/multimaps-google-guava.html


4

只是为了记录,纯JDK8解决方案是使用Map::compute方法:

map.compute(key, (s, strings) -> strings == null ? new ArrayList<>() : strings).add(value);

例如
public static void main(String[] args) {
    Map<String, List<String>> map = new HashMap<>();

    put(map, "first", "hello");
    put(map, "first", "foo");
    put(map, "bar", "foo");
    put(map, "first", "hello");

    map.forEach((s, strings) -> {
        System.out.print(s + ": ");
        System.out.println(strings.stream().collect(Collectors.joining(", ")));
    });
}

private static <KEY, VALUE> void put(Map<KEY, List<VALUE>> map, KEY key, VALUE value) {
    map.compute(key, (s, strings) -> strings == null ? new ArrayList<>() : strings).add(value);
}

输出结果:

bar: foo
first: hello, foo, hello

请注意,为了确保多个线程访问此数据结构的一致性,例如需要使用ConcurrentHashMapCopyOnWriteArrayList


1
最好使用 computeIfAbsentmap.computeIfAbsent(key, k -> new ArrayList<>()).add(value); - user4910279

2
是的和不是的。解决方案是为您的值构建一个包装类,其中包含与您的键对应的2(3或更多)个值。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接