HashMap知识小结

一、常用方法

map.put();            //向集合中插入键值对
map.get();			  //根据key获取value	
map.size();			  //获取集合存储键值对个数	
map.clear();		  //清除集合中所有键值对
map.isEmpty();		  //判断集合中是否为null,返回布尔值	
map.remove();		  //根据key删除键值对并返回value值
map.containsKey();	  //判断键值对中是否存在给定的key值的键值对
map.containsValue();  //判断键值对中是否存在给定的value值的键值对
map.entrySet();		  //返回所有的键值对
map.replace();		  //根据传入的key将传入的value替换	

二、结构

HashMap在jdk8之前采用数组+链表的数据结构,在jdk8时采用数组+链表+红黑树的数据结构。

在这里插入图片描述

3、源码显示

hashmap的初始容量是16;
在这里插入图片描述
最大容量是2的三十次方
在这里插入图片描述
每个键值对传入进来后都会转化成node存储,其中包含key、value,next、hash。
在这里插入图片描述
负载因子是0.75,当键值对个数达到当前容量的75%时,进行扩容,容量*2。

在这里插入图片描述


重点来了,扩容过程如下:

首先创建一个容量为原来2倍的数组,将原来数组的数据拷贝过去,那么拷贝的过程是怎么样的呢?这个过程分为三种情况:

1.如果node的next为null,则重新计算在新的数组中的位置,也就是它本来后面就没有链表/红黑树。
2.如果是红黑树,就用split方法处理,原理是将红黑树拆分成两个TreeNode链表,判断每个链表是否小于等于6,如果是则转换成链表插入到新的数组中,否则成为一个新的红黑树插入数组。
3.如果是链表则直接插入新的数组中,确保顺序不变。

//扩容源码:
final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

四、put()和get()实现细节

put键值对的方法的过程是:
在这里插入图片描述

①判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容;
②根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向⑥,如果table[i]不为空,转向③;
③判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向④,这里的相同指的是hashCode以及equals;
④判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向⑤;
⑤遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可;
⑥插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容。

get值方法的过程是:

1、指定key 通过hash函数得到key的hash值
int hash=key.hashCode();

2、调用内部方法 getNode(),得到桶号(一般都为hash值对桶数求模)
int index =hash%Entry[].length;

3、比较桶的内部元素是否与key相等,若都不相等,则没有找到。相等,则取出相等记录的value。

4、如果得到 key 所在的桶的头结点恰好是红黑树节点,就调用红黑树节点的 getTreeNode() 方法,否则就遍历链表节点。getTreeNode 方法使通过调用树形节点的 find()方法进行查找。由于之前添加时已经保证这个树是有序的,因此查找时基本就是折半查找,效率很高。

5、如果对比节点的哈希值和要查找的哈希值相等,就会判断 key 是否相等,相等就直接返回;不相等就从子树中递归查找。

HashMap中直接地址用hash函数生成;解决冲突,用比较函数解决。如果每个桶内部只有一个元素,那么查找的时候只有一次比较。当许多桶内没有值时,许多查询就会更快了(指查不到的时候)。

标题四内容取自:HashMap原理的深入理解

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
HashMap是Java中的一个数据结构,它实现了Map接口,提供了键值对的存储和检索功能。下面是HashMap的一些知识点: 1. HashMap的特点:HashMap允许存储键值对,其中键是唯一的,值可以重复。它是无序的,不保证元素的顺序。 2. 存储结构:HashMap内部使用数组和链表(或红黑树)的结合来实现存储。数组用来存储元素,链表或红黑树用来解决哈希冲突。 3. 哈希函数:HashMap使用哈希函数将键映射到数组索引上。哈希函数应该具有均匀分布性,尽量避免冲突。 4. 哈希冲突:当两个不同的键通过哈希函数映射到了同一个数组索引上时,就发生了哈希冲突。HashMap使用链表或红黑树解决冲突,如果链表长度较长时,会转换为红黑树以提高性能。 5. 存取操作:通过put(key, value)方法可以将键值对存入HashMap,通过get(key)方法可以根据键获取对应的值。HashMap还提供了remove(key)方法用于删除指定键值对。 6. 容量和负载因子:HashMap有一个初始容量和负载因子。如果容量不够,会自动扩容。负载因子用于控制数组的填充程度,当元素数量超过容量乘以负载因子时,会触发扩容。 7. 线程安全性:HashMap是非线程安全的,如果多个线程同时操作HashMap,可能会导致数据不一致。可以使用Collections工具类的synchronizedMap方法将HashMap包装成线程安全的Map。 8. 性能分析:HashMap的插入、删除和查找操作的时间复杂度都是O(1),在大部分情况下具有很高的性能。 这些是关于HashMap的基本知识点,还有其他更深入的细节和用法可以进一步学习和了解。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值