堆栈花园的Java学习备忘录——HashMap

本文深入解析HashMap的数据结构,包括其数组和单向链表结合的特点,以及底层实现原理,如put和get方法的工作流程。同时,文章探讨了哈希表的高效性,重写equals和hashCode方法的重要性,以及HashMap与HashTable的区别。
摘要由CSDN通过智能技术生成

一、HashMap的数据结构

HashMap是一个数组和单向链表的结合体。

  • 数组:在查询方面效率很高,随机增删方面效率很低。
  • 单向链表:在随机增删方面效率较高,在查询方面效率很低。

二、HashMap底层的实现原理

1. put(key,value)方法
  • 第一步:先将key和value封装到Node对象中。
  • 第二步:底层会调用key的hashCode()方法得到哈希值,然后通过哈希算法,将哈希值转换为数组的下标。
  • 第三步:如果该下标位置上没有任何元素,就把这个Node对象添加到这个位置上;
  • 第四步:如果该下标对应的位置上已经有链表了,此时会将要添加的Node对象中的key和链表上每个节点中的key进行equals,如果所有的equals方法返回结果都是false,那么要添加的Node对象就会被添加到链表的末尾。如果其中有一个equals返回了true,那么这个节点的value会被将要添加的Node对象中的value所覆盖。
2. get(key,value)方法
  • 第一步:先调用key的hashCode()方法得到哈希值。
  • 第二步:通过哈希算法将哈希值转换成数组下标,通过数组下标快速定位到某个链表。
  • 第三步:如果没有定位到任何链表,那么返回null。
  • 第四步:如果定位到了一个链表,那么会拿着key和链表上的每一个节点的key进行equals,如果所有equals方法返回的都是false,那么get方法返回null,只要其中有一个返回了true,那么这个节点的value就是我们需要的内容,get方法最终返回这个value。

三、为什么哈希表的增删查效率都很高?

因为增删是在链表上完成,查询也不需要扫描整个哈希表,只需要扫描部分哈希表,结合了链表和数组的优点,摒弃了二者的缺点。


  • 注意:
//Object中equals方法的源码
public boolean equals(Object obj) {
        return (this == obj);
    }
//Object中hashCode方法的源码
public native int hashCode();

(1)因为HashMap两个常用的方法put和get都会调用hashCode(),和equals()方法,所以这两个方法都要重写。

(2)如果只重写equals方法,而不重写hashCode方法,那该类new出来的对象,hashcode都会不一样(因为是内存地址),那根据put方法的原理,哈希表就会变成一个一维数组了,这样哈希表就没有存在的意义,无法满足散列分布均匀。而且会违反哈希表中“不可重复”的特点。比如new了两个Person类对象,Person类里面只有一个属性,身份证号码,如果这两个Person对象的身份证号码完全一模一样,我们肯定认为是同一个人,而且equals方法肯定也返回true,但是因为内存地址不一样,如果这两个对象作为key放入哈希表中,因为先调的hashcode方法,由于hashcode必不一样,所以equals不会被调用,所以这两个对象都会放到哈希表里,这不是我们期望的结果。

(3)重写equals要满足几个条件:
①自反性:对于任何非空引用值 x,x.equals(x) 都应返回 true。
②对称性:对于任何非空引用值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回 true。
③传递性:对于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么 x.equals(z) 应返回 true。
④一致性:对于任何非空引用值 x 和 y,多次调用 x.equals(y) 始终返回 true 或始终返回 false,前提是对象上 equals 比较中所用的信息没有被修改。
⑤对于任何非空引用值 x,x.equals(null) 都应返回 false。

四、哈希表的其他知识

  1. 初始化容量
    HashMap的默认初始化容量是16。在源码中是这样写的:
/**
* The default initial capacity - MUST be a power of two.
* 默认的初始化容量必须是2的次幂。
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
//1向左移四位,本来是1,那么变化后就是10000,就是16

重点:HashMap初始化容量必须是2的次幂,这是SUN推荐的,这是为了达到散列分布均匀,为了提高HashMap的存取效率。

//底层运用到了位运算来代替%运算,效率高。
static int indexFor(int h, int length) {  
    return h & (length-1);  
}  
  1. 默认加载因子
    HashMap的默认加载因子是0.75。这个默认加载因子是当HashMap集合底层数组到达总容量的75%的时候,数组开始扩容。

  2. 哈希表在1.8后的变化

static final int TREEIFY_THRESHOLD = 8;

如果单向链表上的元素超过八个,会把单向链表变成红黑树数据结构。
当红黑树上的节点小于6时,会重新把红黑树变成单向链表数据结构。

  1. HashMap和HashTable的区别
    ①HashMap的key和value都可以为null,HashTable都不可以为null。
    ②HashTable是线程安全的,HashMap不是线程安全的。
    ③HashTable的初始化容量是11,默认加载因子是0.75f,扩容是原容量* 2 + 1
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值