2016-10-22 2 views
0

У меня есть hashmap, который берет String и HashSet как ключ и значения. Я пытаюсь обновить карту и добавить в нее значения.Confused about HashMap в Java8

Я не могу понять, какие из следующих способов потребительных

  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);

В выходе я вижу тот же ключ добавляется дважды с начальное значение и обновленное значение.

Кто-нибудь, пожалуйста, расскажите, как использовать эти методы.

+1

Что такое NPE? Это 'str',' map' или return '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

Там нет причин, чтобы выбрать между putIfAbsent и computeIfPresent. В частности, computeIfPresent совершенно неуместно, так как он, как следует из его названия, вычисляет только новое значение, когда уже есть старое, а (k,v)->v даже делает это вычисление не-оператором.

Есть несколько вариантов

  1. containsKey, put и get. Это самый популярный предварительно Java 8 один, хотя его самый неэффективный из этого списка, поскольку она включает в себя до трех хэш-запросов для того же ключа

    String key=str.substring(i, j); 
    if(!map.containsKey(key)) 
        map.put(key, new HashSet<>()); 
    map.get(key).add(str); 
    
  2. get и put. Лучше, чем первый, хотя он все еще может включать два поиска. Для обычных Map с, это был лучший выбор, прежде чем 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.

    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.Этот 8 метод Java позволяет наиболее краткую и наиболее эффективную работу:

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

    Это будет только оценить функцию, если нет старого значения, и в отличие от putIfAbsent, этот метод возвращает новое значение, если не было старых Другими словами, он возвращает право Set в любом случае, поэтому мы можем напрямую add. Тем не менее, операция add выполняется за пределами операции Map, поэтому нет безопасности нитей, даже если Map является потокобезопасным. Но для обычных Map с, то есть если безопасность потока не является проблемой, это наиболее эффективный вариант.

  5. compute. Этот Java-метод всегда будет оценивать функцию и может использоваться двумя способами. Первый

    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; 
    }); 
    

    выполнит Set обновления в соответствии с политикой безопасности нити в Map «с, так что в случае ConcurrentHashMap, это будет поточно обновление, поэтому использование compute вместо computeIfAbsent имеет действительный случай использования когда нить безопасность - это проблема.

+0

Будет ли многопотоковая многопоточность потоком также требовать, чтобы внутренние коллекции были потокобезопасными? В противном случае состояние коллекции, возвращаемое операциями 'get', не будет потокобезопасным, так как карта не дает никакой оболочки для значений, которые она хранит. – 4castle

+0

У вас есть конкретная реализация потоковой безопасности? Как правило, если значение, переданное с помощью карты безопасности потока, является изменяемым, должна быть другая политика, касающаяся того, как обеспечить безопасность потока для значения. Это не должна быть сама коллекция, реализующая политику, но все ее потоки должны ее соблюдать. – Holger

+0

Я думал о реализации, которая использовала [новый набор, созданный таким образом] (http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ConcurrentHashMap.html#newKeySet--) , так что значения имеют параллельную поддержку. Это хороший момент, хотя, в зависимости от того, является ли метод 'compute' или' computeIfPresent' единственным способом изменения мутаций, будет безопасность потоков. – 4castle