WeakHashMap的keySet条目永远不会为空吗?

时间:2011-05-28 00:45:19

标签: java null iteration weakhashmap keyset

如果我遍历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条目。

5 个答案:

答案 0 :(得分:6)

我不熟悉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");  
        } 
    }
}

答案 1 :(得分:3)

再次来自WeakHashMap javadoc

  

具有弱键的基于哈希表的Map实现。当其密钥不再正常使用时,WeakHashMap中的条目将自动被删除。更准确地说,给定密钥的映射的存在不会阻止密钥被垃圾收集器丢弃,即,可以最终化,最终化,然后回收。当一个键被丢弃时,它的条目将被有效地从地图中删除,因此该类的行为与其他Map实现略有不同。

我读到的内容:是的......当WeakHaskMap中没有剩余的外部引用时,那个键可能是GC,使相关的值无法访问,所以它(假设没有直接参考的外部参考)对于GC来说是合格的。

我要测试这个理论。这只是我对doco的解释......我对WeakHashMap没有任何经验......但我立刻发现它具有“内存安全”对象缓存的潜力。

干杯。基思。


编辑:探索WeakHashMap ...专门测试我的理论,即对特定键的外部引用会导致该键被保留...这是纯粹的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;
  }

}

答案 2 :(得分:0)

WeakHashMap文档中,放入哈希映射的键是模板化类型,这意味着它是从java.lang.object继承的。因此,它可能为空。因此,密钥可能为空。

答案 3 :(得分:0)

假设您没有在null中插入WeakHashMap键值,那么需要检查迭代的键值是否为{{1迭代键集时。您还需要在迭代条目集时检查迭代的null实例上调用的getKey()是否为Map.Entry。两者都由文档保证,但它有点间接; Iterator.hasNext()的合同提供了这些保证。

JavaDoc for WeakHashMap州:

  

null中的每个关键对象都间接存储为弱引用的引用对象。因此,只有在垃圾收集器清除了对映射内部和外部的弱引用之后,才会自动删除密钥。

Iterator.hasNext()的JavaDoc声明:

  

如果迭代包含更多元素,则返回WeakHashMap。 (换句话说,如果next()将返回一个元素而不是抛出异常,则返回true。)

因为键集和条目集视图满足true契约(Set实现的Map契约的要求),Set.iterator()方法返回的迭代器必须满足WeakHashMap合同。

当hasNext()返回Iterator时,true契约要求Iterator实例上对next()的下一次调用必须返回有效值。 Iterator满足WeakHashMap约定的唯一方法是让hasNext()实现在返回Iterator时保持对下一个键的强引用,从而防止弱引用由true保存的键值被垃圾收集器清除,因此,阻止条目自动从WeakHashMap中删除,以便next()具有返回值。 / p>

实际上,如果查看WeakHashMap的源代码,您会看到WeakHashMap内部类(由键,值和条目迭代器实现使用)具有HashIterator包含对当前键值的强引用的字段以及包含对下一个键值的强引用的currentKey字段。 nextKey字段允许currentKey完全符合该方法的合同Iterator.remove()HashIterator字段允许nextKey满足hasNext()的合同。

话虽如此,假设您想通过调用toArray()来收集地图中的所有键值,然后遍历关键值的快照。有几种情况需要考虑:

  1. 如果调用无参数toArray()方法返回HashIterator或传入零长度数组,如:

    Object[]

    ..然后你需要检查final Set<MyObject> items = hm.keySet(); for (final MyObject item : items.toArray(new MyObject[0])) { // Do something... } 是否item,因为在这两种情况下,返回的数组都会被修剪以保存确切的元素数量由迭代器返回。

  2. 如果你传入一个长度为&gt; = null的当前大小的数组,如:

    WeakHashMap

    ..然后需要final Set<MyObject> items = hm.keySet(); for (final MyObject item : items.toArray(new MyObject[items.size()])) { if (null == item) { break; } // Do something... } 检查 。原因是,在size()返回一个值(用于创建存储键的数组)和toArray()完成迭代null的键之间的时间内,一个条目可能有已被自动删除。这是JavaDoc for Collection.toArray()中提到的“空余空间”案例:

      

    如果此集合适合指定的数组,并且有空余空间(即,数组的元素多于此集合),则紧跟集合结尾的数组中的元素将设置为WeakHashMap。 (如果调用者知道此集合不包含任何null元素,则此选项仅用于确定此集合的长度 。)

    因为您知道尚未在null中插入null键值,所以您可以看到第一个WeakHashMap值(如果您看到null )。

  3. 上一种情况的一个略微变体,如果你传入一个非零长度的数组,那么你需要null检查“备用空间”案例的原因可能在运行时发生。

答案 4 :(得分:0)

WeakHashMap允许将null作为键和值。您可以添加空键和值。 因此,如果您没有插入空条目,则无需添加空检查