Являются ли записи набора ключей WeakHashMap пустыми? - PullRequest
9 голосов
/ 28 мая 2011

Если я перебираю набор ключей 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 записей.

Ответы [ 4 ]

6 голосов
/ 28 мая 2011

Я не знаком с 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");  
        } 
    }
}
3 голосов
/ 28 мая 2011

Опять из Слабый хэп-мэп javadoc :

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

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

Я собираюсь проверить эту теорию. Это только моя интерпретация документа ... У меня нет опыта работы с WeakHashMap ... но я сразу вижу его потенциал как "безопасный для памяти" объектный кеш.

Приветствия. Кит.


РЕДАКТИРОВАТЬ: Изучение WeakHashMap ... специально проверить мою теорию, что внешние ссылки на конкретный ключ приведет к тому, что этот ключ будет сохранен ... который является pure bunkum; -)

Мой тестовый жгут:

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

}
0 голосов
/ 12 октября 2017

Предполагая, что вы не вставляете значение ключа 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 реализовать Iterator.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. Если вы передадите массив длины> = текущий размер WeakHashMap, например:

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

    .. тогда null check является необходимым. Причина в том, что во время, когда size () вернул значение (которое используется для создания массива для хранения ключей), и toArray () заканчивает итерацию по ключам WeakHashMap, запись может быть автоматически удален. Это случай «места для резервирования», упомянутый в JavaDoc для Collection.toArray () :

    Если эта коллекция помещается в указанный массив с достаточным пространством для резервирования (т. Е. Массив содержит больше элементов, чем эта коллекция), элемент в массиве, следующий сразу за концом коллекции, устанавливается на null. (Это полезно при определении длины этой коллекции только , если вызывающая сторона знает, что эта коллекция не содержит элементов null.)

    Поскольку вы знаете, что не вставили значение ключа null в WeakHashMap, вы можете прерваться, увидев первое значение null (если вы видите null).

  3. Небольшой вариант предыдущего случая, если вы передадите массив ненулевой длины, тогда вам понадобится проверка null по той причине, что случай "комната для резервирования" может происходит во время выполнения.

0 голосов
/ 28 мая 2011

Из документации WeakHashMap ключ, помещаемый в хэш-карту, имеет шаблонный тип, что означает, что он унаследован от java.lang.object. В результате оно может быть нулевым. Таким образом, ключ может быть нулевым.

...