Java中HashMap的奇怪行为

Java中HashMap的奇怪行为,java,hashmap,hashcode,Java,Hashmap,Hashcode,我在下面的类中发现hashmap有一些奇怪的行为 class Employee { private String a; private int b; public Employee(String a, int b) { this.a = a; this.b = b; } @Override public int hashCode() { final int prime = 31; int result = 1; result

我在下面的类中发现hashmap有一些奇怪的行为

class Employee {

  private String a;
  private int b;

  public Employee(String a, int b) {
    this.a = a;
    this.b = b;
  }

  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((a == null) ? 0 : a.hashCode());
    result = prime * result + b;
    return result;
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (getClass() != obj.getClass())
        return false;
    Employee other = (Employee) obj;
    if (a == null) {
        if (other.a != null)
            return false;
    } else if (!a.equals(other.a))
        return false;
    if (b != other.b)
        return false;

    return true;
  }

  public static void main(String[] args) {
    HashMap<Employee,Integer> map = new HashMap<>();

    for(int i = 0; i < 13; i++) {
        map.put(new Employee( i + "", i), i + i);
    }
  }
}
class员工{
私人字符串a;
私人INTB;
公共雇员(字符串a,整数b){
这个a=a;
这个.b=b;
}
@凌驾
公共int hashCode(){
最终整数素数=31;
int结果=1;
result=prime*result+((a==null)?0:a.hashCode();
结果=素数*结果+b;
返回结果;
}
@凌驾
公共布尔等于(对象obj){
if(this==obj)
返回true;
if(obj==null)
返回false;
如果(getClass()!=obj.getClass())
返回false;
员工其他=(员工)obj;
如果(a==null){
if(other.a!=null)
返回false;
}如果(!a.equals(other.a))
返回false;
如果(b!=其他.b)
返回false;
返回true;
}
公共静态void main(字符串[]args){
HashMap=newHashMap();
对于(int i=0;i<13;i++){
map.put(新员工(i+“”,i),i+i);
}
}
}
当我使用newemployee(“,i”)作为在map中存储数据的键时,它工作正常,并在插入第12个节点后调整map的大小。但是使用 新员工(i+“”,i)作为键,它显示出奇怪的行为,使用此键添加第10个元素时,它将映射从16调整为32,添加第11个元素时,它再次将映射从32调整为64。 如果您发现此行为的任何原因,请提供帮助。

如前所述,我相信您指的是地图的内部结构,可以通过调试器看到。 HashMap使用hashCode()方法将对象分组到“bucket”中

重写的hashCode()方法使用字符串成员a的值。当a为“”时,其哈希代码为0,因此与将a设置为具有更高哈希代码的更有意义的字符串时相比,要插入的元素的哈希代码彼此相对更接近

由于某种原因(取决于哈希桶是如何精确实现的),当散列代码值进一步分离时,HashMap对象决定更快地扩展其内部结构


对于插入的元素,请查看它们的哈希代码值,这将是有意义的。

我尝试重写代码的has方法。使用您的实现(使用反射获取地图细节)

改写如下:

public int hashCode() {
    final int prime = 31;
    return prime * this.b;
}
那么尺寸的增加就如预期的那样

Loop 0 : Capacity : 16, Factor : 12, Current Size : 1
Loop 1 : Capacity : 16, Factor : 12, Current Size : 2
Loop 2 : Capacity : 16, Factor : 12, Current Size : 3
Loop 3 : Capacity : 16, Factor : 12, Current Size : 4
Loop 4 : Capacity : 16, Factor : 12, Current Size : 5
Loop 5 : Capacity : 16, Factor : 12, Current Size : 6
Loop 6 : Capacity : 16, Factor : 12, Current Size : 7
Loop 7 : Capacity : 16, Factor : 12, Current Size : 8
Loop 8 : Capacity : 16, Factor : 12, Current Size : 9
Loop 9 : Capacity : 16, Factor : 12, Current Size : 10
Loop 10 : Capacity : 16, Factor : 12, Current Size : 11
Loop 11 : Capacity : 16, Factor : 12, Current Size : 12
Loop 12 : Capacity : 32, Factor : 24, Current Size : 13
虽然我不能肯定地解释,但下面来自HashMap实现的代码片段表明,哈希计算值可能会增加映射的大小

void More addEntry(int hash, K key, V value, int **bucketIndex**) {
    Entry<K,V> e = table[bucketIndex];
    table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
    if (size++ >= threshold)
        resize(2 * table.length);
}
void More addEntry(int散列、K键、V值、int**bucketinex**){
条目e=表[bucketIndex];
表[bucketIndex]=新条目(散列、键、值、e);
如果(大小+>=阈值)
调整大小(2*表格长度);
}

原因-Java8中HashMap的新组织。当特定bin中的列表变得太长时,
HashMap
会将该列表迁移到树而不是链表中-此过程称为treeifiting

TREEIFY_THRESHOLD=8表示当给定bin中有8个条目时,给定bin应在二叉树中存储冲突值,而不是链表(从而将此bin的搜索复杂度从O(n)更改为O(log n)

if (binCount >= TREEIFY_THRESHOLD - 1)
                treeifyBin(tab, hash);
方法treeifyBin替换bin中哈希的所有链接节点,除非表太小,在这种情况下,它会调整表的大小

因此,在您的例子中,您得到了64个大小(这段代码使resize变为两倍,将tab大小增加到32,并且得到64个大小(MIN_TREEIFY_CAPACITY)):

if(tab==null | |(n=tab.length)
您是如何观察这种行为的?使用调试器?您可能在谈论内部大小,而不是映射中的实际条目数,对吗?是的,在调试模式下,您可以检查添加第10个元素时,它的大小是否从16调整到32…它实际上是“排列更好的元素”(如string
a==i+)
)这会导致地图两次调整大小。正如@Olga发现的,这是由于树引用开始,但我想知道为什么所有这些值都会导致地图将它们放在一个箱子中。
if (binCount >= TREEIFY_THRESHOLD - 1)
                treeifyBin(tab, hash);
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();