hashmap如何实现的?

本文详细介绍了HashMap在Java中的实现,包括其基于哈希值的存储机制、懒加载策略、数组与链表/红黑树的转换,以及源码中的关键操作如put、扩容和树化过程。
摘要由CSDN通过智能技术生成

1、概述

hashmap是集合框架中比较常用的集合,它实现了map接口,根据key的 hash 值存储数据,具有很快的访问速度(数组的存在),可以存储null key和null value,null key只能存储一个,并且存储在数组的第一个下标中。不支持线程同步。HashMap 是无序的,即不会记录插入的顺序。

2、实现

在jdk1.8中,使用了懒加载,所以当我们实例化一个hashmap,他并不会立马初始化数组。只有当使用put方法添加第一个键值对的时候,底层会初始化一个大小为16名为table的Node类型的数组,之后会进行key的两次hash,得到一个hash值。使用此hash值再通过某种算法(hash & n-1))得到数组下标的位置。如果下标中不存在元素,则直接插入成功。如果数组下标有元素,就存在hash冲突。此时就要进行两个hash的比较,如果hash不相同,则会插入成功。如果hash相同,则会使用equals方法进行比较,如果不相同,则插入成功。如果相同,此时put方法就是一个修改操作。随着put操作元素越来越多,当达到某一个阈值(容量*加载因子)数组就会2倍的扩容。当某一个数组下标的链表的节点个数大于8并且数组容量大于64的时候,链表的效率较低,链表就会进行树化,提高检索的效率。链表的时间复杂度为O(n),红黑树时间复杂度为O(logn)。当红黑色的节点个数小于6时,红黑树就会退化成链表,因为小于6时,链表检索效率并不是很低,并且同一个节点存储数据,红黑树占用存储空间大于链表的占用的空间,具体来说是2倍。

3、源码

3.1、构造

//使用懒加载
 public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
 }

3.2 put操作

 public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
 }
 //两次hash操作
 static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
 }
 
 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                boolean evict) {
     //tab为数组,p为节点,n为数组长度,i为数组的下标
     Node<K,V>[] tab; Node<K,V> p; int n, i;
     //如果tab为空或者tab长度为0
     if ((tab = table) == null || (n = tab.length) == 0)
     	 //实例化一个长度为16的数组
         n = (tab = resize()).length;
     // tab[i = (n - 1) & hash])计算数组下标
     // 如果下标为null 
     if ((p = tab[i = (n - 1) & hash]) == null)
     	 //创建一个Node类型的节点,使用尾插法,next为null
         tab[i] = newNode(hash, key, value, null);
     else {
         Node<K,V> e; K k;
         //第一个节点的hash和刚才的两次hash是否相等
         //第一个节点的kye和刚才的key是否相等
         //key不为空并且key是否等于第一个节点的key
         if (p.hash == hash &&
             ((k = p.key) == key || (key != null && key.equals(k))))
             //e为第一个节点
             e = p;
         //判断是否是一个树节点(Node链表 和 TreeNode红黑树)    
         else if (p instanceof TreeNode)
            //如果树结点中,有key重复的,就返回那个重复的结点用e接收,即e!=null
            //如果树结点中,没有key重复的,就把新结点放到树中,并且返回null,即e=null
             e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
         	else {
         	//不是树节点,key不重复,binCount记录了数组索引的节点个数
	             for (int binCount = 0; ; ++binCount) {
	             	//使用尾插,寻找最后一个节点
	                 if ((e = p.next) == null) {
	                 	 //最后一个节点next指向新节点
	                     p.next = newNode(hash, key, value, null);
	                     //当binCount>=7
	                     if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
	                     	 //先会考虑扩容,在会考虑树化,就是树化的条件
	                         treeifyBin(tab, hash);
	                     break;
	                 }
	                 //还在循环中,如果hash相等,key重复了
	                 if (e.hash == hash &&
	                     ((k = e.key) == key || (key != null && key.equals(k))))
	                     break;
	                 p = e;
	             }
	         }
         if (e != null) { // existing mapping for key
             V oldValue = e.value;
             if (!onlyIfAbsent || oldValue == null)
                 e.value = value;
             afterNodeAccess(e);
             return oldValue;
         }
     }
     //快速失败机制
     ++modCount;
     //如果数组容量大于阈值,进行扩容
     if (++size > threshold)
         resize();
     afterNodeInsertion(evict);
     return null;
 }
  • 7
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值