如何在HashMap中查看键的分布?

时间:2015-04-11 18:52:54

标签: java hashmap

使用哈希映射时,将密钥均匀分布在存储桶上非常重要。

如果所有密钥都在同一个存储桶中结束,那么您最终会得到一个列表。

有没有办法在Java中“审核”HashMap以查看密钥的分发情况?

我尝试对其进行子类型化并迭代Entry<K,V>[] table,但它不可见。

4 个答案:

答案 0 :(得分:12)

  

我尝试对其进行子类型化并迭代Entry []表,但它不可见

使用Reflection API!

public class Main {
    //This is to simulate instances which are not equal but go to the same bucket.
    static class A {
            @Override
            public boolean equals(Object obj) { return false;}

            @Override
            public int hashCode() {return 42; }
        }

    public static void main(String[] args) {
            //Test data  
            HashMap<A, String> map = new HashMap<A, String>(4);
            map.put(new A(), "abc");
            map.put(new A(), "def");

            //Access to the internal table  
            Class clazz = map.getClass();
            Field table = clazz.getDeclaredField("table");
            table.setAccessible(true);
            Map.Entry<Integer, String>[] realTable = (Map.Entry<Integer, String>[]) table.get(map);

            //Iterate and do pretty printing
            for (int i = 0; i < realTable.length; i++) {
                System.out.println(String.format("Bucket : %d, Entry: %s", i, bucketToString(realTable[i])));
            }
    }

    private static String bucketToString(Map.Entry<Integer, String> entry) throws Exception {
            if (entry == null) return null;
            StringBuilder sb = new StringBuilder();

            //Access to the "next" filed of HashMap$Node
            Class clazz = entry.getClass();
            Field next = clazz.getDeclaredField("next");
            next.setAccessible(true); 

            //going through the bucket
            while (entry != null) {
                sb.append(entry);
                entry = (Map.Entry<Integer, String>) next.get(entry);
                if (null != entry) sb.append(" -> ");
            }
            return sb.toString();
        }
}

最后,您会在STDOUT中看到类似的内容:

 Bucket : 0, Entry: null 
 Bucket : 1, Entry: null 
 Bucket : 2, Entry: Main$A@2a=abc -> Main$A@2a=def 
 Bucket : 3, Entry: null

答案 1 :(得分:5)

HashMap使用密钥对象的hashCode()方法生成的密钥,所以我猜你真的在问这些哈希代码值是如何均匀分布的。您可以使用Map.keySet()来获取关键对象。

现在,HashMap的OpenJDK和Oracle实现不直接使用密钥哈希码,而是在将它们分配到存储桶之前对提供的哈希应用另一个哈希函数。但是你不应该依赖或使用这个实现细节。所以你应该忽略它。因此,您应该确保密钥值的hashCode()方法分布均匀。

检查某些示例键值对象的实际哈希码不太可能告诉您任何有用的内容,除非您的哈希值方法非常差。您最好对哈希码方法进行基本的理论分析。这并不像听起来那么可怕。您可能(实际上别无选择)假设所提供的Java类的哈希代码方法分布均匀。然后,您只需要检查用于组合数据成员的哈希码的方法是否适合数据成员的预期值。只有当您的数据成员具有以特殊方式高度相关的值时,这可能是一个问题。

答案 2 :(得分:3)

您可以使用反射来访问隐藏字段:

HashMap map = ...;

// get the HashMap#table field
Field tableField = HashMap.class.getDeclaredField("table");
tableField.setAccessible(true);

Object[] table = (Object[]) tableField.get(map);
int[] counts = new int[table.length];

// get the HashMap.Node#next field
Class<?> entryClass = table.getClass().getComponentType();
Field nextField = entryClass.getDeclaredField("next");
nextField.setAccessible(true);

for (int i = 0; i < table.length; i++) {
    Object e = table[i];
    int count = 0;
    if (e != null) {
        do {
            count++;
        } while ((e = nextField.get(e)) != null);
    }
    counts[i] = count;
}

现在你有一个每个桶的入口数量数组。

答案 3 :(得分:2)

Client.java

public class Client{
        public static void main(String[] args) {

            Map<Example, Number> m = new HashMap<>();
            Example e1  = new Example(100);  //point 1
            Example e2  = new Example(200);  //point2
            Example e3  = new Example(300);  //point3
            m.put(e1, 10);
            m.put(e2, 20);
            m.put(e3, 30);
            System.out.println(m);//point4
        }
    }

<强> Example.java

public class Example {
    int s;
    Example(int s) {
        this.s =s;
    }
    @Override
    public int hashCode() {
        // TODO Auto-generated method stub
        return 5;
    }
}

现在在Client.java中的第1点,第2点和第3点,我们在hashmap m中插入3个类型为Example的键。由于在Example.java中重写了hashcode(),因此所有三个键e1,e2,e3将返回相同的哈希码,因此在hashmap中返回相同的桶。

现在的问题是如何查看密钥的分配。

方法:

  1. 在Client.java中的point4处插入调试点。
  2. 调试java应用程序。
  3. 检查m。
  4. 在m中,您将找到HashMap $ Node类型和大小为16的表数组。
  5. 这就是哈希表。每个索引都包含插入到hashmap中的Entry对象的链接列表。每个非null索引都有一个哈希变量,它对应于Hashmap的hash()方法返回的哈希值。然后将此哈希值发送到HashMap的indexFor()方法,以找出表数组的索引,其中将插入Entry对象。 (请参阅评论中的@ Rahul链接以了解hash和indexFor的概念。)
  6. 对于上面的情况,如果我们检查表,你会发现除了一个键之外的所有键。
  7. 我们插入了三个键,但我们只能看到一个,即所有三个键都插入到同一个桶中,即表的相同索引。
  8. 检查table数组元素(在本例中为5),key对应e1,而value对应10(point1)
  9. next变量在这里指向链接列表的下一个节点,即下一个Entry对象,在我们的例子中是(e2,200)。
  10. 因此,您可以通过这种方式检查hashmap。

    我还建议你仔细阅读hashmap的内部实现来理解HashMap。

    希望它有所帮助......