Записите keySet на WeakHashMap никога ли не са нулеви?

Ако повторя набора от ключове на WeakHashMap, трябва ли да проверявам за нулеви стойности?

WeakHashMap<MyObject, WeakReference<MyObject>> hm
    = new WeakHashMap<MyObject, WeakReference<MyObject>>();

for ( MyObject item : hm.keySet() ) {
    if ( item != null ) { // <- Is this test necessary?
        // Do something...
    } 
}

С други думи, могат ли да се събират елементи от WeakHashMap, докато ги итерирам?

РЕДАКТИРАНЕ

В името на този въпрос може да се приеме, че в хеш картата не са добавени null записи.


person Jérôme Verstrynge    schedule 28.05.2011    source източник


Отговори (5)


Отново от WeakHashMap javadoc:

Реализация на карта, базирана на хеш-таблица, със слаби ключове. Запис в WeakHashMap автоматично ще бъде премахнат, когато неговият ключ вече не се използва нормално. По-точно, наличието на съпоставяне за даден ключ няма да попречи на ключа да бъде изхвърлен от събирача на отпадъци, тоест да бъде направен финализиран, финализиран и след това възстановен. Когато даден ключ е изхвърлен, записът му ефективно се премахва от картата, така че този клас се държи малко по-различно от други реализации на карта.

Което прочетох като: Да... Когато няма останали външни препратки към ключ в WeakHaskMap, тогава този ключ може да бъде GC'd, което прави свързаната стойност недостъпна, така че да (приемайки, че има няма външни препратки директно към него) отговаря на изискванията за GC.

Ще тествам тази теория. Това е само моята интерпретация на doco... Нямам никакъв опит с WeakHashMap... но веднага виждам потенциала му като "безопасен за паметта" обектен кеш.

наздраве Кийт.


РЕДАКТИРАНЕ: Проучване на WeakHashMap... конкретно тестване на моята теория, че външни препратки към конкретния ключ биха довели до запазване на този ключ... което е чиста глупост ;-)

Моят тестов сбруя:

package forums;

import java.util.Set;
import java.util.Map;
import java.util.WeakHashMap;
import krc.utilz.Random;

public class WeakCache<K,V> extends WeakHashMap<K,V>
{
  private static final int NUM_ITEMS = 2000;
  private static final Random RANDOM = new Random();

  private static void runTest() {
    Map<String, String> cache = new WeakCache<String, String>();
    String key; // Let's retain a reference to the last key object
    for (int i=0; i<NUM_ITEMS; ++i ) {
      /*String*/ key = RANDOM.nextString();
      cache.put(key, RANDOM.nextString());
    }

    System.out.println("There are " + cache.size() + " items of " + NUM_ITEMS + " in the cache before GC.");

    // try holding a reference to the keys
    Set<String> keys = cache.keySet();
    System.out.println("There are " + keys.size() + " keys");

    // a hint that now would be a good time to run the GC. Note that this
    // does NOT guarantee that the Garbage Collector has actually run, or
    // that it's done anything if it did run!
    System.gc();

    System.out.println("There are " + cache.size() + " items of " + NUM_ITEMS + " remaining after GC");
    System.out.println("There are " + keys.size() + " keys");
  }

  public static void main(String[] args) {
    try {
      for (int i=0; i<20; ++i ) {
        runTest();
        System.out.println();
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

Резултатите (доста объркващи, според мен) от един тест:

There are 1912 items of 2000 in the cache before GC.
There are 1378 keys
There are 1378 items of 2000 remaining after GC
There are 909 keys

There are 2000 items of 2000 in the cache before GC.
There are 2000 keys
There are 1961 items of 2000 remaining after GC
There are 1588 keys

There are 2000 items of 2000 in the cache before GC.
There are 2000 keys
There are 1936 items of 2000 remaining after GC
There are 1471 keys

There are 2000 items of 2000 in the cache before GC.
There are 2000 keys
There are 2000 items of 2000 remaining after GC
There are 1669 keys

There are 2000 items of 2000 in the cache before GC.
There are 2000 keys
There are 2000 items of 2000 remaining after GC
There are 1264 keys

There are 2000 items of 2000 in the cache before GC.
There are 2000 keys
There are 2000 items of 2000 remaining after GC
There are 1770 keys

There are 2000 items of 2000 in the cache before GC.
There are 2000 keys
There are 2000 items of 2000 remaining after GC
There are 1679 keys

There are 2000 items of 2000 in the cache before GC.
There are 2000 keys
There are 2000 items of 2000 remaining after GC
There are 1774 keys

There are 2000 items of 2000 in the cache before GC.
There are 2000 keys
There are 2000 items of 2000 remaining after GC
There are 1668 keys

There are 2000 items of 2000 in the cache before GC.
There are 2000 keys
There are 2000 items of 2000 remaining after GC
There are 0 keys

There are 2000 items of 2000 in the cache before GC.
There are 2000 keys
There are 2000 items of 2000 remaining after GC
There are 1834 keys

There are 2000 items of 2000 in the cache before GC.
There are 2000 keys
There are 2000 items of 2000 remaining after GC
There are 0 keys

There are 2000 items of 2000 in the cache before GC.
There are 2000 keys
There are 2000 items of 2000 remaining after GC
There are 0 keys

There are 2000 items of 2000 in the cache before GC.
There are 2000 keys
There are 2000 items of 2000 remaining after GC
There are 0 keys

There are 2000 items of 2000 in the cache before GC.
There are 2000 keys
There are 2000 items of 2000 remaining after GC
There are 0 keys

There are 2000 items of 2000 in the cache before GC.
There are 2000 keys
There are 2000 items of 2000 remaining after GC
There are 0 keys

There are 2000 items of 2000 in the cache before GC.
There are 2000 keys
There are 2000 items of 2000 remaining after GC
There are 0 keys

There are 2000 items of 2000 in the cache before GC.
There are 2000 keys
There are 429 items of 2000 remaining after GC
There are 0 keys

There are 2000 items of 2000 in the cache before GC.
There are 2000 keys
There are 0 items of 2000 remaining after GC
There are 0 keys

There are 2000 items of 2000 in the cache before GC.
There are 2000 keys
There are 0 items of 2000 remaining after GC
There are 0 keys

Изглежда, че ключовете все още изчезват, ДОКАТО моят код се изпълнява... вероятно е необходим микро-заспиване след GC-подсказката... за да се даде време на GC да направи своите неща. Както и да е, тази "волатилност" е интересно поведение.


РЕДАКТИРАНЕ 2: Да, добавянето на ред try{Thread.sleep(10);}catch(Exception e){} директно след System.gc(); прави резултатите „по-предвидими“.

There are 1571 items of 2000 in the cache before GC.
There are 1359 keys
There are 0 items of 2000 remaining after GC
There are 0 keys

There are 2000 items of 2000 in the cache before GC.
There are 2000 keys
There are 0 items of 2000 remaining after GC
There are 0 keys

There are 2000 items of 2000 in the cache before GC.
There are 2000 keys
There are 0 items of 2000 remaining after GC
There are 0 keys

There are 2000 items of 2000 in the cache before GC.
There are 2000 keys
There are 0 items of 2000 remaining after GC
There are 0 keys

.... and so on for 20 runs ...

Хммм... Кеш, който просто изчезва напълно, когато GC стартира... в произволни моменти в реално приложение... без голяма полза... Хммм... Чудя се за какво е WeakHashMap? ;-)


Последна РЕДАКЦИЯ, обещавам

Ето моят krc/utilz/Random (използван в горния тест)

package krc.utilz;

import java.io.Serializable;
import java.nio.charset.Charset;

/**
 * Generates random values. Extends java.util.Random to do all that plus:<ul>
 * <li>generate random values in a given range, and
 * <li>generate Strings of random characters and random length.
 * </ul>
 * <p>
 * Motivation: I wanted to generate random Strings of random length for test 
 *  data in some jUnit tests, and was suprised to find no such ability in the
 *  standard libraries... so I googled it, and came up with Glen McCluskey's
 *  randomstring function at http://www.glenmccl.com/tip_010.htm. Then I thought
 *  aha, that's pretty cool, but if we just extended it a bit, and packaged it
 *  properly then it'd be useful, and reusable. Cool!
 * See: http://www.glenmccl.com/tip_010.htm
 * See: http://forum.java.sun.com/thread.jspa?threadID=5117756&messageID=9406164
 */
public class Random extends java.util.Random  implements Serializable
{

  private static final long serialVersionUID = 34324;
  public static final int DEFAULT_MIN_STRING_LENGTH = 5;
  public static final int DEFAULT_MAX_STRING_LENGTH = 25;

  public Random() {
    super();
  }

  public Random(long seed) {
    super(seed);
  }

  public double nextDouble(double lo, double hi) {
    double n = hi - lo;
    double i = super.nextDouble() % n;
    if (i < 0) i*=-1.0;
    return lo + i;
  }

  /**
   * @returns a random int between lo and hi, inclusive.
   */
  public int nextInt(int lo, int hi) 
    throws IllegalArgumentException
  {
    if(lo >= hi) throw new IllegalArgumentException("lo must be < hi");
    int n = hi - lo + 1;
    int i = super.nextInt() % n;
    if (i < 0) i = -i;
    return lo + i;
  }

  /**
   * @returns a random int between lo and hi (inclusive), but exluding values
   *  between xlo and xhi (inclusive).
   */
  public int nextInt(int lo, int hi, int xlo, int xhi) 
    throws IllegalArgumentException
  {
    if(xlo < lo) throw new IllegalArgumentException("xlo must be >= lo");
    if(xhi > hi) throw new IllegalArgumentException("xhi must be =< hi");
    if(xlo > xhi) throw new IllegalArgumentException("xlo must be >= xhi");
    int i;
    do {
      i = nextInt(lo, hi);
    } while(i>=xlo && i<=xhi);
    return(i);
  }

  /**
   * @returns a string (of between 5 and 25 characters, inclusive) 
   *  consisting of random alpha-characters [a-z]|[A-Z].
   */
  public String nextString()
    throws IllegalArgumentException
  {
    return(nextString(DEFAULT_MIN_STRING_LENGTH, DEFAULT_MAX_STRING_LENGTH));
  }

  /**
   * @returns a String (of between minLen and maxLen chars, inclusive) 
   *  which consists of random alpha-characters. The returned string matches
   *  the regex "[A-Za-z]{$minLen,$maxLan}". 
   * @nb: excludes the chars "[\]^_`" between 'Z' and 'a', ie chars (91..96).
   * @see: http://www.neurophys.wisc.edu/comp/docs/ascii.html
   */
  public String nextString(int minLen, int maxLen)
    throws IllegalArgumentException
  {
    if(minLen < 0) throw new IllegalArgumentException("minLen must be >= 0");
    if(minLen > maxLen) throw new IllegalArgumentException("minLen must be <= maxLen");
    return(nextString(minLen, maxLen, 'A', 'z', '[', '`'));
  }

  /**
   * @does: generates a String (of between minLen and maxLen chars, inclusive) 
   *  which consists of characters between lo and hi, inclusive.
   */
  public String nextString(int minLen, int maxLen, char lo, char hi)
    throws IllegalArgumentException
  {
    if(lo < 0) throw new IllegalArgumentException("lo must be >= 0");
    String retval = null;
    try {
      int n = minLen==maxLen ? maxLen : nextInt(minLen, maxLen);
      byte b[] = new byte[n];
      for (int i=0; i<n; i++)
        b[i] = (byte)nextInt((int)lo, (int)hi);
      retval = new String(b, Charset.defaultCharset().name());
    } catch (Exception e) {
      e.printStackTrace();
    }
    return retval;
  }

  /**
   * @does: generates a String (of between minLen and maxLen chars, inclusive) 
   *  which consists of characters between lo and hi, inclusive, but excluding
   *  character between 
   */
  public String nextString(int minLen, int maxLen, char lo, char hi, char xlo, char xhi) 
    throws IllegalArgumentException
  {
    if(lo < 0) throw new IllegalArgumentException("lo must be >= 0");
    String retval = null;
    try {
      int n = minLen==maxLen ? maxLen : nextInt(minLen, maxLen);
      byte b[] = new byte[n];
      for (int i=0; i<n; i++) {
        b[i] = (byte)nextInt((int)lo, (int)hi, (int)xlo, (int)xhi);
      }
      retval = new String(b, Charset.defaultCharset().name());
    } catch (Exception e) {
      e.printStackTrace();
    }
    return retval;
  }

}
person corlettk    schedule 28.05.2011
comment
Документът също така казва, че комплектът е подкрепен от картата и промените в картата се отразяват в комплекта. Така че това вероятно означава, че отговорът на въпроса ми е: да, тестът е необходим... - person Jérôme Verstrynge; 28.05.2011
comment
Добре, това доказва тезата. Тестът е необходим. Много благодаря за усилията ви!!! - person Jérôme Verstrynge; 28.05.2011
comment
Не, това не доказва тезата ви. Ако извадите ключ от вашата карта, той ще бъде един от ключовете, които сте поставили. Ако не сте поставили null като ключ, тогава няма да получите null обратно. - person JimN; 28.05.2011
comment
@Jimmy: Хммм... ДОБРА ТОЧКА!!! Не се бях сетил за това. Изглежда, че се нуждаем от тест, повтарящ кеша докато Garbage Collector работи... Все пак не бих очаквал да бъде върнат нулев ключ, но подозирам, че може да намеря по-малко записи/ключове отколкото там, където преди да започна итерацията (и GC се включи). Въздишка. - person corlettk; 28.05.2011
comment
@JimN Ако извадите ключ от вашата карта, той ще бъде един от ключовете, които сте поставили. Добре, но сигурен ли сте, че няма да бъде изхвърлян междувременно? Сигурни ли сте, че for each няма да върне нулева стойност, защото основният набор е бил модифициран? - person Jérôme Verstrynge; 28.05.2011
comment
WeakHashMap е много лош кеш. Обектите със слаби препратки се събират незабавно. Трябва да използвате меки препратки... - person Viliam; 22.03.2012

Не съм запознат с WeakHashMap, но може да имате един нулев обект. вижте този пример:

public static void main(String[] args)
{
    WeakHashMap<Object, WeakReference<Object>> hm
    = new WeakHashMap<Object, WeakReference<Object>>();
    hm.put(null, null);
    for ( Object item : hm.keySet() ) {
        if ( item == null ) { 
          System.out.println("null object exists");  
        } 
    }
}
person MByD    schedule 28.05.2011
comment
Добре, но какво ще стане, ако знаем, че към картата не се добавят нулеви записи? - person Jérôme Verstrynge; 28.05.2011
comment
Бих казал, че това е най-правилният отговор (а не приетият отговор по-горе). Някои карти позволяват null като ключ, а някои не (WeakHashMap го прави). Прочетете документацията, когато се съмнявате. Ако не поставите null в картата, няма да получите null обратно. - person JimN; 28.05.2011
comment
Съгласен съм с JimN. Това е най-добрият, най-пълният отговор на въпроса (публикуван досега)... особено ако човек си направи труда да чете коментарите. - person corlettk; 22.03.2012

От WeakHashMap документация, ключът, който се поставя в хеш картата, е от шаблонен тип, което означава, че е наследен от java.lang.object. В резултат на това може да е нула. Така че един ключ може да е нулев.

person Zach Rattner    schedule 28.05.2011
comment
@Zach Добре, но какво ще стане, ако знаем, че към картата не се добавят нулеви записи? - person Jérôme Verstrynge; 28.05.2011
comment
-1. Нямам представа какво имате предвид под шаблонен тип. Само защото типът на ключа е java.lang.Object (или някакъв подтип) не означава, че null е приемлив ключ. - person JimN; 28.05.2011
comment
@JVerstry: Ако знаете, че нито един ключ не е нулев, тогава keySet няма да ви даде никакви нулеви стойности. - person Zach Rattner; 28.05.2011
comment
@Zach 100% сигурен ли си, че върнатите ключове няма да са нулеви? Записите в WeakHashMap могат да бъдат премахнати по всяко време от GC. Въпросът е влияе ли върху ketSet и как? - person Jérôme Verstrynge; 28.05.2011
comment
@JVerstry: От download.oracle. com/javase/6/docs/api/java/util/, Наборът се поддържа от картата, така че промените в картата се отразяват в набора и обратно. - person Zach Rattner; 28.05.2011
comment
Абсолютно съм сигурен. GC може да премахне цял запис, но ако запис съществува (т.е. не е премахнат), той ще съдържа ключа и стойността, които са били вмъкнати. Разгледайте вътрешния интерфейс Map.Entry - person JimN; 28.05.2011

Ако приемем, че не вмъквате null стойност на ключ в WeakHashMap, не не трябва да проверявате дали стойността на ключа на итерацията е null, когато итерирате през набора ключове. Също така не трябва да проверявате дали getKey(), извикан на Map.Entry екземпляра на итерацията, е null, когато итерирате през набора за въвеждане. И двете са гарантирани от документацията, но това е донякъде косвено; това е договорът на Iterator.hasNext (), който предоставя тези гаранции.

JavaDoc за WeakHashMap гласи:

Всеки ключов обект в WeakHashMap се съхранява индиректно като референт на слаба препратка. Следователно даден ключ ще бъде премахнат автоматично само след като слабите препратки към него, както вътре, така и извън картата, бъдат изчистени от събирача на отпадъци.

JavaDoc за Iterator.hasNext() гласи:

Връща true, ако итерацията има повече елементи. (С други думи, връща true, ако next() ще върне елемент, вместо да хвърли изключение.)

Тъй като изгледите на набора ключове и набора за въвеждане удовлетворяват Set договора (както се изисква от Map договора, който WeakHashMap прилага), итераторите, върнати от Set.iterator() методът трябва да отговаря на Iterator договора.

Когато hasNext() върне true, договорът Iterator изисква следващото извикване на next() на екземпляра Iterator да върне валидна стойност. Единственият начин WeakHashMap да удовлетвори договора Iterator е внедряването hasNext() да поддържа силна препратка към следващия ключ, когато връща true, като по този начин предотвратява изчистването на слабата препратка към стойността на ключа, съхранявана от WeakHashMap от боклука колектор и, като следствие, предотвратяване на автоматично премахване на записа от WeakHashMap, така че next() да има стойност за връщане.

Наистина, ако погледнете източника на WeakHashMap, ще видите, че вътрешният клас HashIterator (използван от имплементациите на ключ, стойност и итератор на запис) има поле currentKey, което съдържа силна препратка към текущата стойност на ключ и поле nextKey който съдържа силна препратка към следващата ключова стойност. Полето currentKey позволява на HashIterator да внедри Итератор .remove() в пълно съответствие с договора на този метод. Полето nextKey позволява на HashIterator да изпълни договора на hasNext().

Като се има предвид това, да предположим, че искате да съберете всички ключови стойности в картата, като извикате toArray(), и след това да преминете през тази моментна снимка на ключовите стойности. Има няколко случая за разглеждане:

  1. Ако извикате метода без аргумент toArray(), който връща Object[] или предава масив с нулева дължина, както в:

    final Set<MyObject> items = hm.keySet();
    for (final MyObject item : items.toArray(new MyObject[0])) {
        // Do something...
    }
    

    .. тогава не трябва да проверявате дали item е null, тъй като и в двата случая върнатият масив ще бъде съкратен, за да съдържа точния брой елементи, върнати от итератора.

  2. #P12#
    final Set<MyObject> items = hm.keySet();
    for (final MyObject item : items.toArray(new MyObject[items.size()])) {
        if (null == item) {
            break;
        }
        // Do something...
    }
    
    #P13#
    #P14#
    #P15#
  3. Лек вариант на предишния случай, ако подадете масив с ненулева дължина, тогава ви е необходима проверката null поради причината, че случайът "просто място" може да се случи по време на изпълнение.

person Daniel Trebbien    schedule 11.10.2017

WeakHashMap позволява null като ключ и стойност. Можете да добавите нулев ключ и стойност. Така че в случай, че не сте вмъкнали нулеви записи, тогава не е необходимо да добавяте нулева проверка

person Vartika Sharma    schedule 04.06.2020