Java:Map(二)——equals和hashCode

Map原理

HashMap根据Key得到Value的原理,是它在内部通过空间换时间,根据Key直接计算出Value所在的索引。

Map内部,对Key作比较是通过equals()实现的,只要两个Keyequals()比较的结果相同,这两个Key就指向同一个Value

所以,正确使用Map必须保证:作为Key的对象必须正确覆写equals()方法。也就是说,如果我们放入Key的如果是我们自己写的类,就要保证这个类正确覆写equals()方法。

通过Key计算Value索引是通过Key对象的hashCode()方法,它返回一个intHashMap就是用这个方法定位到Value索引,继而查找到Value

因此,正确使用Map需要保证:

  1. Key类正确覆写equals()方法,两个相同的Key调用该方法返回true
  2. Key类正确覆写hashCode()方法,且该方法也有两个规范:
    • 两个相同KeyhashCode()必须相同;
    • 两个不同KeyhashCode()尽量不同;

第一条规范equals()必须遵守,否则HashMap不能正常工作。

编写equals()

和List一节所说的equals()编写方法相同

2021.6.17:List——编写equals方法 - ShineLe - 博客园

编写hashCode()

在正确实现equals()的基础上实现hashCode(),对于相同的实例,应当返回相同的hashCode()

public class Person {
    String firstName;
    String lastName;
    int age;

    @Override
    int hashCode() {
        int h = 0;
        h = 31 * h + firstName.hashCode();
        h = 31 * h + lastName.hashCode();
        h = 31 * h + age;
        return h;
    }
}

注意上文对三个字段firstName、lastName、age,都要参与hashCode的计算,并不是每个字段各返回一个h,而是它们共同参与一个h的构建。

由于String已经正确实现了hashCode(),所以我们在构建PersonhashCode()时,可以利用它,对它乘一个固定值,就能起到把PersonhashCode()均匀分布到整个int范围。

hashCode()与null值

不过,如果传入的字段中有null,上述代码工作起来会抛出NullPointerException,为了解决该问题,在计算hashCode时,常借用Objects.hash()计算:

int hashCode(){
    return Objects.hash(firstName,lastName,age);
}

equals()用到的每个字段,都必须在hashCode()中参与计算;没用到的字段,不能在hashCode()中参与计算。

 

Map内部空间组织形式

初始大小与扩容

HashMap初始化时,默认数组大小只有16,对于任何Key,无论其hashCode()有多大,都可以简单通过:

int index = key.hashCode(() & 0xf; // 0xf = 15

把索引值确定在0~15。

当添加超过了一定数量的K-V后,HashMap会在内部自动扩容,每次扩容一倍。

扩容后,数组大小为32,此时对索引的计算,是通过:

int index = key.hashCode() & 0x1f; //0x1f=31

创建指定容量的HashMap

Map <String,Integer> map = new HashMap<>(10000);

虽然指定容量为10000,但是HashMap内部数组长总是2n,所以,实际数组被初始化为比10000大的16384(214

冲突避免

如果不同的Key指向同一个索引,那么在该索引处就不是单个Value,而是包含了这两个ValueList——List< Entry<String,Person> >,实际查找时,查找到的是这个List,它还会遍历这个List,直到找到这个List中的正确的Key-Value

尽量要减小碰撞——尽量使不同的Key的hashCode()不同hashCode()方法编写的越好,HashMap效率越高。

 

posted @ 2021-06-17 16:21  ShineLe  阅读(937)  评论(0)    收藏  举报