HashMap 源码浅析

此博客源码基于jdk1.8

Hashmap 简介

  • Hashmap是一个散列表,存储内容是键值对(key-value).。
  • Hashmap继承于Abstractmap,实现Map,Cloneable,java.io.Serializable接口
  • HashMap 的实现不是同步的,这意味着它不是线程安全的。它的key、value都可以为null。此外,HashMap中的映射不是有序的。
  • HashMap有两个参数影响其性能:初始容量加载因子
    初始容量是HashMap桶的数目/加载因子是其达到多满的的一种衡量尺度。当HashMap中条目数超过容量和加载因子的乘积则扩容,进行rehash操作(即重构内部数据结构)

HashMap数据结构

HashMap继承于/实现的接口

在这里插入图片描述
继承于:
在这里插入图片描述
实现的接口:
在这里插入图片描述

HashMap数据结构

在jdk1.7中,HashMap采用数组+链表(拉链法)。因为数组是一组连续的内存空间,易查询,不易增删,而链表是不连续的内存空间,通过节点相互连接,易删除,不易查询。HashMap结合这两者的优秀之处来提高效率。
而在jdk1.8时,为了解决当hash碰撞过于频繁,而链表的查询效率(时间复杂度为O(n))过低时,当链表的长度达到一定值(默认是8)时,将链表转换成红黑树(时间复杂度为O(lg n)),极大的提高了查询效率。(什么是hash碰撞后面我慢慢解释)
在这里插入图片描述
HashMap中重要的成员变量:

  • table[ ]:HashMap中的节点就是在HashMap中定义的Node(key/value都存储在其中)(Node继承于Map.Entrty)。table[]是一个Entry的数组类型。
    在这里插入图片描述
  • **size:**是当前HashMap存储的键值对个数
  • **loadfactor:**加载因子

HashMap中重要的方法浅析/自我简化实现

put方法

自我用链表是实现:

//通过key得到索引位置
        int hash = hash(key);
        int index = table.length - 1 & hash;

        //判断该index位置是否存在节点
        Node<K, V> firstNode = table[index];
        if (firstNode == null) {
            table[index] = new Node(hash, key, value);
            size++;
        } else {
            //查找当前链表中key是否已经存在
            if (firstNode.key.equals(key)) {
                firstNode.value = value;
            } else {
                Node<K, V> currentNode = firstNode;
                while (currentNode.next != null && !currentNode.key.equals(key)) {
                    currentNode = currentNode.next;
                }
                if (currentNode.next == null) {
                    if (currentNode.key.equals(key)) {
                        currentNode.value = value;
                    } else {
                        currentNode.next = new Node(hash, key, value);
                        size++;
                    }
                } else {
                    currentNode.value = value;
                }

源码实现大家可以看一下jdk源码

为何要重写equals和hashcode

前面说到了,HashMap的很多函数要基于equal()函数和hashCode()函数。hashCode()用来定位要存放的位置,equal()用来判断是否相等。
那么,相等的概念是什么?
Object版本的equal只是简单地判断是不是同一个实例。但是有的时候,我们想要的的是逻辑上的相等。比如有一个学生类student,有一个属性studentID,只要studentID相等,不是同一个实例我们也认为是同一学生。当我们认为判定equals的相等应该是逻辑上的相等而不是只是判断是不是内存中的同一个东西的时候,就需要重写equal()。而涉及到HashMap的时候,重写了equals(),就需要重写hashCode()

我们总结一下几条基本原则

  1. 同一个对象(没有发生过修改)无论何时调用hashCode()得到的返回值必须一样。
    如果一个key对象在put的时候调用hashCode()决定了存放的位置,而在get的时候调用hashCode()得到了不一样的返回值,这个值映射到了一个和原来不一样的地方,那么肯定就找不到原来那个键值对了。

  2. hashCode()的返回值相等的对象不一定相等,通过hashCode()和equals()必须能唯一确定一个对象
    不相等的对象的hashCode()的结果可以相等。hashCode()在注意关注碰撞问题的时候,也要关注生成速度问题,完美hash不现实

  3. 一旦重写了equals()函数(重写equals的时候还要注意要满足自反性、对称性、传递性、一致性),就必须重写hashCode()函数。而且hashCode()的生成哈希值的依据应该是equals()中用来比较是否相等的字段
    如果两个由equals()规定相等的对象生成的hashCode不等,对于hashMap来说,他们很可能分别映射到不同位置,没有调用equals()比较是否相等的机会,两个实际上相等的对象可能被插入不同位置,出现错误。其他一些基于哈希方法的集合类可能也会有这个问题

因为HashMap需要通过equals和hashcode两个因素来决定HashMap键的唯一性,不重写equals和hashcode得不到唯一的键。。。

发布了43 篇原创文章 · 获赞 12 · 访问量 1412

猜你喜欢

转载自blog.csdn.net/qq_42411214/article/details/103207019