2016-10-22 59 views
0

我有一个将String和HashSet作为键和值的hashmap。 我正在尝试更新地图并在其中添加值。对Java8中的HashMap感到困惑

我无法理解其下面的方法来使用 -

  1. map.putIfAbsent(str.substring(i,j),new HashSet<String>).add(str); //this method gives nullpointerexception

  2. map.computeIfPresent(str.substring(i,j),(k,v)->v).add(str);

在输出I可以看到相同的密钥被与添加的两次初始值和更新值。

有人请告诉我如何使用这些方法。

+1

什么是NPE来自哪里?它是'str','map'还是'putIfAbsent'的返回? – 4castle

+2

快速浏览一下javadoc说'putIfAbsent'返回null时,该键还没有在地图中。在调用add()之前你有没有考虑过这种可能性? –

+0

为什么你认为,你必须在这两种方法之间进行选择?至少有四种方法可用于此目的,不包括您列出的不适当方法。 – Holger

回答

3

最好的办法是用Map#computeIfAbsent。这样不会不必要地创建新的HashSet,并且之后它将返回值。

map.computeIfAbsent(str.substring(i, j), k -> new HashSet<>()).add(str); 
+1

这会解决你的问题。如果密钥不存在于地图中,那么'putIfAbsent'返回null,所以NPE就有可能。 'computeIfAbsent'只返回null,如果它们的键不存在,并且传入的值提供程序返回一个空值,否则它不会,因为它是一个构造函数调用。 –

2

没有理由putIfAbsentcomputeIfPresent之间做出选择。最值得注意的是,computeIfPresent完全不合适,因为它正如其名称所暗示的那样,只计算一个新值,当已经存在旧值时,甚至使得该计算成为无操作。

有几个选项

  1. containsKeyputget。这是最流行的前8的Java之一,但其效率最低的这份名单的,因为它集成了多达三个哈希查找对于同一个密钥

    String key=str.substring(i, j); 
    if(!map.containsKey(key)) 
        map.put(key, new HashSet<>()); 
    map.get(key).add(str); 
    
  2. getput。比第一个更好,但它仍然可以包含两个查找。对于普通Map S,这是Java的前8的最佳选择:

    String key=str.substring(i, j); 
    Set<String> set=map.get(key); 
    if(set==null) 
        map.put(key, set=new HashSet<>()); 
    set.add(str); 
    
  3. putIfAbsent。在Java 8之前,此选项仅适用于ConcurrentMap s。

    String key=str.substring(i, j); 
    Set<String> set=new HashSet<>(), old=map.putIfAbsent(key, set); 
    (old!=null? old: set).add(str); 
    

    这只能携带一个哈希查找,但需要无条件创造的新HashSet,即使我们并不需要它。在这里,首先执行get以推迟创建可能是值得的,特别是当使用ConcurrentMap时,因为get可以无锁地执行并且可以使得后续更昂贵的putIfAbsent不必要。

    另一方面,必须强调的是,这个构造不是线程安全的,因为对值Set的操纵不受任何东西的保护。

  4. computeIfAbsent。这个Java 8方法允许最简洁和最有效的操作:

    map.computeIfAbsent(str.substring(i, j), k -> new HashSet<>()).add(str); 
    

    这只会评价功能,如果没有旧值,而不像putIfAbsent,这个方法返回新值,如果没有老值,换句话说,无论在哪种情况下它都返回正确的Set,所以我们可以直接add到它。但是,add操作在Map操作之外执行,因此即使Map是线程安全的,也没有线程安全性。但是对于普通的Maps,即如果线程安全不是问题,这是最有效的变体。

  5. compute。这个Java 8方法将总是评估函数并且可以以两种方式使用。第一个

    map.compute(str.substring(i, j), (k,v) -> v==null? new HashSet<>(): v).add(str); 
    

    只是computeIfAbsent更详细的变种。第二

    map.compute(str.substring(i, j), (k,v) -> { 
        if(v==null) v=new HashSet<>(); 
        v.add(str); 
        return v; 
    }); 
    

    Map的线程安全政策下执行Set更新,所以在ConcurrentHashMap情况下,这将是一个线程安全的更新,因此使用compute代替computeIfAbsent有一个有效的使用情况下,当线安全是一个问题。

+0

一个线程安全的multimap是否也需要内部集合是线程安全的?否则,'get'操作返回的集合的状态将不会是线程安全的,因为映射不会为其存储的值提供任何包装。 – 4castle

+0

您是否有特定的“线程安全多映射”实现?一般来说,如果线程安全映射发布的值是可变的,那么就必须有另一个关于如何确保值的线程安全的策略。它不一定是集合本身实现策略,但所有访问它的线程都必须遵守它。 – Holger

+0

我正在考虑使用[以此方式创建的新集合]的实现(http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ConcurrentHashMap.html#newKeySet--) ,以便这些值具有并发支持。这是一个很好的观点,取决于如果'compute'或'computeIfPresent'是值被突变的唯一方式,那么线程安全。 – 4castle