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

问题描述

| 如果我迭代WeakHashMap的键集,是否需要检查null值?
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的元素时可以收集它们吗? 编辑 出于这个问题,我们可以假设在哈希图中未添加任何“ 1”项。     

解决方法

        再次从WeakHashMap javadoc:   具有弱键的基于哈希表的Map实现。如果WeakHashMap中的条目不再是普通使用的键,它将自动被删除。更确切地说,给定键的映射的存在不会阻止该键被垃圾收集器丢弃,即被终结化,终结和回收。丢弃键后,其条目会从映射中有效删除,因此此类的行为与其他Map实现有所不同。 我读为:是的...如果在WeakHaskMap中没有剩余的外部引用,那么该密钥可能是GC \',从而使关联的Value无法访问,因此(假设没有直接的外部引用) )可用于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:是的,在
System.gc();
之后直接添加
try{Thread.sleep(10);}catch(Exception e){}
行使结果“更可预测”。
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,but excluding
   *  character between 
   */
  public String nextString(int minLen,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;
  }

}
    ,        我不熟悉
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\");  
        } 
    }
}
    ,        根据WeakHashMap文档,放置在哈希图中的键是模板化的类型,这意味着它是从java.lang.object继承的。结果,它可能为空。因此,键可以为空。     ,假设您没有将
null
键值插入
WeakHashMap
中,则在遍历键集时无需检查迭代的键值是否为
null
。遍历条目集时,也不需要检查在迭代的
Map.Entry
实例上调用的getKey()是否为
null
。两者都由文档保证,但是有些间接。提供这些保证的是Iterator.hasNext()合同。 Doc8ѭ的JavaDoc指出:   “ 8”中的每个关键对象都间接存储为弱引用的参考。因此,只有在垃圾回收器清除了映射内部和外部的弱引用之后,才会自动删除密钥。 用于Iterator.hasNext()的JavaDoc指出:   如果迭代中包含更多元素,则返回
true
。 (换句话说,如果next()将返回一个元素而不是引发异常,则返回
true
。) 因为键集和条目集视图满足ѭ19合同(按ѭ8实施的ѭ20合同的要求),所以Set.iterator()方法返回的迭代器必须满足ѭ22合同。 当hasNext()返回
true
时,the22ѭ契约要求在
Iterator
实例上对next()的下一次调用必须返回有效值。
WeakHashMap
满足
Iterator
合同的唯一方法是hasNext()实现在返回returns17ѭ时保持对下一个键的强引用,从而防止对
WeakHashMap
持有的键值的弱引用被垃圾清除。收集器,因此防止该条目自动从
WeakHashMap
中删除,以便next()具有要返回的值。 确实,如果查看
WeakHashMap
的源代码,您将看到
HashIterator
内部类(由键,值和条目迭代器实现使用)具有一个
currentKey
字段,该字段对当前键值和
nextKey
字段具有强引用对下一个键值具有很强的参考意义。
currentKey
字段允许
HashIterator
完全遵守该方法的合同来实现Iterator.remove()。
nextKey
字段允许
HashIterator
满足hasNext()的约定。 话虽如此,假设您想通过调用toArray()来收集映射中的所有键值,然后遍历此键值快照。有几种情况需要考虑: 如果调用无参数toArray()方法,该方法返回ѭ39或传递零长度数组,如下所示:
final Set<MyObject> items = hm.keySet();
for (final MyObject item : items.toArray(new MyObject[0])) {
    // Do something...
}
..那么您就不必检查
item
是否为
null
,因为在两种情况下,返回的数组都将被修剪以保留迭代器返回的元素的确切数目。 如果您传递的长度> =
WeakHashMap
\ n的当前大小的数组,如下所示:
final Set<MyObject> items = hm.keySet();
for (final MyObject item : items.toArray(new MyObject[items.size()])) {
    if (null == item) {
        break;
    }
    // Do something...
}
..然后必须进行
null
检查。原因是,在size()返回一个值(用于创建用于存储键的数组)与toArray()完成对
WeakHashMap
的键的迭代之间的时间之间,某个条目可能已被自动删除。这是JavaDoc for Collection.toArray()中提到的“备用空间”情况:   如果此集合适合指定的数组并有剩余空间(即,数组比该集合具有更多的元素),则紧接集合结束后的数组中的元素将设置为“ 1”。 (仅当调用者知道此集合不包含任何“ 1”元素时,此方法才可用于确定此集合的长度。) 因为您知道尚未将
null
键值插入
WeakHashMap
中,所以看到第一个
null
值(如果看到
null
)时可能会中断。 与前一种情况略有不同,如果传入一个非零长度的数组,则您需要进行“ 1”检查,原因是在运行时可能会发生“剩余空间”的情况。