HashMap

一、HashMap使用

HashMap也是我们使用非常多的Collection,它是基于哈希表的 Map 接口的实现,以key-value的形式存在。
哈希表也称散列表,根据关键码值key 进行访问的数据结构,也就是说,能够将关键码映射到表中一个位置我们就可以去访问其记录value,加快查找的速度,这个映射函数叫做散列函数,存放记录的数组叫做散列表。
在HashMap中,key-value总是会当做一个整体来处理,系统会根据hash算法来来计算key-value的存储位置,我们总是可以通过key快速地存、取value。
HashMap实现了Map接口,继承AbstractMap。
数组:寻址容易O(1),插入和删除困难O(N)
链表:寻址困难O(N),插入和删除容易O(1)
综合两者的特性,变成了一个寻址容易,插入删除也容易的数据结构
哈希表有多种不同的结构(实现),我们介绍的是最常用的结构-拉链法(链地址法)

遍历方式

1.在for循环中使用entries实现Map的遍历

for (Map.Entry<Object, Object> entries : map.entrySet()) {
 System .out .println(next .getKey() +":"+next .getValue() );
}

2.通过Iterator遍历

Iterator<Map.Entry<Object, Object>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
    Map.Entry<Object, Object> entry = iterator.next();
   System .out .println(next .getKey() +":"+next .getValue() );
}
        
        

二、 HashMap源码jdk1.8

  • HashMap允许空值和空键
  • HashMap是非线程安全
  • HashMap元素是无序 LinkedHashMap TreeMap
  • (HashTable不允许为空 线程安全)

属性

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {

    private static final long serialVersionUID = 362498820763181265L;

    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
    static final int MAXIMUM_CAPACITY = 1 << 30;
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    static final int TREEIFY_THRESHOLD = 8;
    static final int UNTREEIFY_THRESHOLD = 6;
    static final int MIN_TREEIFY_CAPACITY = 64;
    
    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;
        }

初始容量,加载因子。这两个参数是影响HashMap性能的重要参数,其中容量表示哈希表中桶的数量,初始容量是创建哈希表时的容量,加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度,它衡量的是一个散列表的空间的使用程度,负载因子越大表示散列表的装填程度越高,反之愈小。

对于使用链表法的散列表来说,查找一个元素的平均时间是O(1+a),因此如果负载因子越大,对空间的利用更充分,然而后果是查找效率的降低;如果负载因子太小,那么散列表的数据将过于稀疏,对空间造成严重浪费。系统默认负载因子为0.75,一般情况下我们是无需修改的。

get()

 public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }
 final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        //table不为空  且 table长度>0 且 table索引位置(table.length - 1&hash)的节点不为空
        if ((tab = table) != null && (n = tab.length) > 0 &&(first = tab[(n - 1) & hash]) != null) {
        // 若first节点的hash值和key是否和入参的一样,如果一样则first即为目标节点,直接返回first节点
            if (first.hash == hash && ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
              // first的next节点不为空则继续遍历    
            if ((e = first.next) != null) {
                 // 如果是红黑树节点,则调用红黑树的查找目标节点方法getTreeNode
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                  // 向下遍历链表, 直至找到节点的key和入参的key相等时,返回该节点
                    if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }
   

流程:1、若table不为null 且 table的长度>0 且 索引节点first(table.length - 1&hash)不为null,则查找
2、若该索引的节点hash 和key值==传参,则返回first节点
3、若first.next不为null继续遍历
若该节点为红黑树类型,则getTreeNode()查找
若为普通链表、则遍历链表、找到与传参相等的节点,返回该节点

put()

public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
         // table是否为空||length==0,是resize()进行初始化
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
      // 将该索引位置的头节点赋值给p,如果p为空则直接在该索引位置新增一个节点
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {        // table表该索引位置不为空,则进行查找
                Node<K,V> e; K k;
   // 判断p节点的key和hash值是否跟传入的相等,如果相等, 则p节点即为要查找的目标节点,将p节点赋值给e节点
            if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
 // 判断p节点是否为TreeNode, 如果是putTreeVal()查找目标节点
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
   //p节点为普通链表节点,使用binCount统计链表的节点数
            else {
                for (int binCount = 0; ; ++binCount) {
               //如果p的next节点为空时,则代表找不到目标节点,则新增一个节点并插入链表尾部
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
         // 节点数是否超过8个,是treeifyBin()将链表节点转为红黑树节点,
                    // -1是因为循环是从p节点的下一个节点开始的
                        if (binCount >= TREEIFY_THRESHOLD - 1) 
                            treeifyBin(tab, hash);
                        break;
                    }
        // 如果e节点存在hash值和key值都与传入的相同,则e节点即为目标节点,跳出循环
                    if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
       // e节点不为空,则代表目标节点存在,使用传入的value覆盖该节点的value,并返回oldValue
            if (e != null) {
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
  // 如果插入节点后节点数超过阈值,则调用resize方法进行扩容
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

流程:1、若table 为空 || tab.length0,resize()进行初始化
2、找到传参的索引位置,如果索引头节点p
null,则在该索引新增一个节点
否则:
(1)如果p节点的key和hash值跟传入的相等, 则p节点为要查找的目标节点,将p节点赋值给e节点
(2) 判断p节点是否为TreeNode类型, 是则putTreeVal()查找目标节点
(3)判断p节点是否为普通链表节点,是使用binCount统计链表的节点数
如果p.next==null时,则找不到目标节点,新增一个节点并插入链表尾部
如果e节点hash值和key值都与传入的相同,则e节点即为目标节点,跳出循环
3、e节点不为空,则代表目标节点存在,使用传入的value覆盖该节点的value,并返回oldValue、
4、如果size大于阈值、resize()扩容

resize()

resize时机
1)table==null
2) table需要扩容的时候
过程
1)table进行扩容
2)table原先节点进行重哈希
a.HashMap的扩容指的是数组的扩容,因为数组的空间是连续的,所以需要数组的扩容即开辟一个更大空间的数组,将老数组上的元素全部转移到新数组上来
b.在HashMap中扩容,先新建一个2倍原数组大小的新数组,然后遍历原数组的每一个位置,如果这个位置存在节点,则将该位置的链表转移到新数组
c.在jdk1.8中,因为涉及到红黑树,jdk1.8实际上还会用到一个双向链表去维护一个红黑树中的与元素,所以jdk1.8在转移某个位置的元素时,首先会判断该节点是否是一个红黑树节点,然后遍历该红黑树所对应的双向链表,将链表中的节点放到新数组的位置当中
d.最后元素转移完之后,会将新数组的对象赋值给HashMap中table属性,老数组会被回收

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值