9

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

5 回答 5

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");  
        } 
    }
}
于 2011-05-28T00:51:19.770 回答
3

再次来自WeakHashMap javadoc

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

我读为:是的...当WeakHaskMap 中没有对 Key 的剩余外部引用时,那么该 Key 可能会被 GC 处理,从而使关联的 Value 无法访问,因此它(假设没有直接指向它的外部引用)符合 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;
  }

}
于 2011-05-28T01:06:35.367 回答
1

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

状态的JavaDocWeakHashMap

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

Iterator.hasNext() 的 JavaDoc 指出:

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

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

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

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

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

  1. 如果调用返回Object[]或传入零长度数组的无参数 toArray() 方法,如下所示:

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

    .. 那么您不需要检查是否itemnull因为在这两种情况下,返回的数组都将被修剪以保存迭代器返回的元素的确切数量。

  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检查必要的。原因是,在 size() 返回一个值(用于创建用于存储键的数组)和 toArray() 完成对 的键的迭代之间WeakHashMap,一个条目可能已被自动删除。这是JavaDoc for Collection.toArray()中提到的“备用空间”案例:

    如果此集合适合指定的数组并有剩余空间(即,数组的元素比此集合多),则数组中紧跟集合末尾的元素设置为null(仅当调用者知道此集合不包含任何null元素时,这在确定此集合的长度时才有用。)

    因为您知道您没有在 中插入null键值WeakHashMap,所以您可以在看到第一个null值时中断(如果您看到 a null)。

  3. 前一种情况的一个轻微变体,如果您传入一个非零长度的数组,那么您需要null检查“剩余空间”情况可能在运行时发生的原因。

于 2017-10-11T23:30:39.420 回答
1

WeakHashMap 允许 null 作为键和值。您可以添加空键和值。所以如果你没有插入空条目,那么你不需要添加空检查

于 2020-06-04T12:42:51.880 回答
0

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

于 2011-05-28T00:50:04.117 回答