缘起
在看HashMap的时候想debug跟踪一下逻辑执行,那就需要冲突的hashCode,那怎么找呢?
HashMap原理
先了解了一下HashMap何时冲突
- HashMap中用一个Node[] table数组来储存数据,Node实现了Map.Entry接口。Map.Entry是KV键值对,Node则扩充了K的hash值,和next下一个节点。
- 当i = hash & table.length-1相等时,即为哈希冲突。
- 冲突时,node会以链表的形式,也就是next指向下一个Node来储存。
- 当链表长度>8时,链表会转换为红黑树,也就是Node的子类TreeNode。
字符串的hashCode原理
字符串对象的哈希码根据以下公式计算:
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
如何找到8个以上的字符串,使其hashCode一样(本文重点)
由以上公式,我们可以轻松构造一个长度为2的字符串,使其hashCode相同,例如:
String a = new String(new char[]{2, 0});
String b = new String(new char[]{0, 62});
System.out.println(a);
System.out.println(b);
System.out.println(a.hashCode());
System.out.println(b.hashCode());
那我们如何找到更多呢?
方法:
从上面我们找到了两个相等的字符串 a 和 b,即 a b可以相互替换
那字符串 a+a 、a + b 哈希值肯定也相同。我们简化a + a 字符串为 aa
那 aa ab ba bb 四个字符串的哈希值也相同。
最终代码:
public static void main(String[] args) {
HashMap<String, String> map = new HashMap<>();
// 根据原理,举例构造两个hashCode相等的字符串
String a = new String(new char[]{2, 0});
String b = new String(new char[]{0, 62});
System.out.println(a);
System.out.println(b);
System.out.println(a.hashCode());
System.out.println(b.hashCode());
// 以此两个字符串为基础构造多个hashCode相等的字符串
List<String> list = new ArrayList<String>();
list.add(a);
list.add(b);
for (int i = 0; i < 3; i++) {
List<String> tmp = new ArrayList<String>();
list.forEach(s -> {
tmp.add(s + a);
tmp.add(s + b);
});
list = tmp;
}
// 字符串依次放入map中,可断点跟踪代码验证了。
for (int i = 0; i < 8; i++) {
String s = list.get(i);
System.out.println(s + "|" + s.hashCode());
map.put(s, "index" + i);
}
map.put(list.get(8), "index8888");
System.out.println(map);
}