Являются ли записи 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:

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

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

Я собираюсь проверить эту теорию. Это только моя интерпретация документа... У меня нет опыта работы с 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 ...

Хммм... Кэш, который просто полностью исчезает, когда срабатывает сборщик мусора... в произвольное время в реальном приложении... мало толку... Хммм... Интересно, для чего нужен 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: Хммм... ПОЛОЖИТЕЛЬНОЕ ЗАМЕЧАНИЕ!!! Я об этом не подумал. Казалось бы, нам нужен тест, итерирующий кеш во время работы сборщика мусора... Я все еще не ожидал, что будет возвращен нулевой ключ, но я подозреваю, что могу найти меньше записей/ключей чем там, где до того, как я начал итерацию (и включился сборщик мусора). Вздох. - 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 могут быть удалены в любое время сборщиком мусора. Вопрос в том, влияет ли это на 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
Я абсолютно уверен. Сборщик мусора может удалить всю запись, но если запись существует (т. е. не была удалена), она будет содержать вставленные ключ и значение. Взгляните на внутренний интерфейс 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