使用哈希映射时,将密钥均匀分布在存储桶上非常重要。
如果所有密钥都在同一个存储桶中结束,那么您最终会得到一个列表。
有没有办法在Java中“审核”HashMap以查看密钥的分发情况?
我尝试对其进行子类型化并迭代Entry<K,V>[] table
,但它不可见。
答案 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中返回相同的桶。
现在的问题是如何查看密钥的分配。
方法:
table
数组元素(在本例中为5),key
对应e1,而value
对应10(point1)next
变量在这里指向链接列表的下一个节点,即下一个Entry对象,在我们的例子中是(e2,200)。因此,您可以通过这种方式检查hashmap。
我还建议你仔细阅读hashmap的内部实现来理解HashMap。
希望它有所帮助......