前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >HashMap 源码解析-前序

HashMap 源码解析-前序

作者头像
热心的大肚皮
发布2023-02-28 13:38:25
2160
发布2023-02-28 13:38:25
举报
文章被收录于专栏:程序猿日常笔记

hashmap是我们常用的容器类,但是为什么会有这种数据结构出现呢?它是怎么操作的呢?本文会带你领略大神的编程。

为什么要有hashmap?

我们已经有了数组,ArrayList和LinkedList,为什么要有HashMap? 因为在之前的数据结构中,最好的搜索方法是有序数组的二分查找和AVL树搜索。它们的最坏情况所搜时间都是O(lgn)。是否有更快的算法?散列表数据结构提供了这样的保证——O(1)的平均搜索时间。

本文的内容结构为

1、HashMap 存储结构

2、HashMap 各常量、成员变量、内部类作用

3、HashMap 几种构造方法

4、HashMap put 及其相关方法

5、HashMap get 及其相关方法

6、HashMap remove 及其相关方法

7、HashMap 扩容方法resize()

一、HashMap 存储结构

  先来看一下 HashMap 的继承图:

HashMap 根据键的 hashCode 值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的。HashMap 最多只允许一条记录的键为 null ,允许多条记录的值为 null 。HashMap 非线程安全,即任一时刻可以有多个线程同时写 HashMap,可能会导致数据的不一致。

我们再来看一下HashMap的实现方式

  HashMap是数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的,如下图所示:

二、HashMap 各常量、成员变量作用 

常量

代码语言:javascript
复制
     //创建 HashMap 时未指定初始容量情况下的默认容量  
     static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
 
    //HashMap 的最大容量
     static final int MAXIMUM_CAPACITY = 1 << 30;
 
     //HashMap 默认的装载因子,当 HashMap 中元素数量超过 容量*装载因子 时,进行 resize() 操作
     static final float DEFAULT_LOAD_FACTOR = 0.75f;
 
     //用来确定何时将解决 hash 冲突的链表转变为红黑树
     static final int TREEIFY_THRESHOLD = 8;
 
     // 用来确定何时将解决 hash 冲突的红黑树转变为链表
     static final int UNTREEIFY_THRESHOLD = 6;
  
     //当需要将解决 hash 冲突的链表转变为红黑树时,需要判断下此时数组容量,若是由于数组容量太小(小于 MIN_TREEIFY_CAPACITY )导致的 hash 冲突太多,
     //则不进行链表转变为红黑树操作,转为利用 resize() 函数对 hashMap 扩容 
     static final int MIN_TREEIFY_CAPACITY = 64;

变量

代码语言:javascript
复制
     //保存Node<K,V>节点的数组
     transient Node<K,V>[] table;
    
     //由 hashMap 中 Node<K,V> 节点构成的 set
     transient Set<Map.Entry<K,V>> entrySet;
    
     //记录 hashMap 当前存储的元素的数量
     transient int size;
    
     //记录 hashMap 发生结构性变化的次数(注意 value 的覆盖不属于结构性变化)
     transient int modCount;
    
     //threshold的值应等于 table.length * loadFactor, size 超过这个值时进行 resize()扩容
     int threshold;
    
     //记录 hashMap 装载因子
     final float loadFactor;

内部类

代码语言:javascript
复制
// Node<K,V> 类用来实现数组及链表的数据结构
   static class Node<K,V> implements Map.Entry<K,V> {
         final int hash; //保存节点的 hash 值
         final K key; //保存节点的 key 值
         V value; //保存节点的 value 值
         Node<K,V> next; //指向链表结构下的当前节点的 next 节点,红黑树 TreeNode 节点中也有用到
 
         Node(int hash, K key, V value, Node<K,V> next) {
             this.hash = hash;
             this.key = key;
             this.value = value;
             this.next = next;
         }
     }
     
    // TreeNode<K,V> 继承 LinkedHashMap.Entry<K,V>,用来实现红黑树相关的存储结构
     static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
         TreeNode<K,V> parent;  // 存储当前节点的父节点
         TreeNode<K,V> left; //存储当前节点的左孩子
         TreeNode<K,V> right; //存储当前节点的右孩子
         TreeNode<K,V> prev;    // 存储当前节点的前一个节点
         boolean red; // 存储当前节点的颜色(红、黑)
         TreeNode(int hash, K key, V val, Node<K,V> next) {
             super(hash, key, val, next);
         }
      }    

三、HashMap 几种构造方法 hashmap初始化时为懒加载方式,即在初始化时仅声明容量及扩容阈值等, 不多说上代码。

代码语言:javascript
复制


//构造方法1,指定初始容量及装载因子
public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
       /**   tableSizeFor(initialCapacity) 方法返回的值是最接近 initialCapacity 的2的幂,
         *    若指定初始容量为9,则实际 hashMap 容量为16
         */
        this.threshold = tableSizeFor(initialCapacity);
}

//构造方法2,仅指定初始容量,装载因子的值采用默认的 0.75
public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

//构造方法3,所有参数均采用默认值
public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

重要说明 此方法意为传一个期望值返回一个最接近期望值的2的幂。

代码语言:javascript
复制

//tableSizeFor(initialCapacity) 方法返回的值是最接近 initialCapacity 的2的幂
static final int tableSizeFor(int cap) {
        int n = cap - 1;
        /**
         *    以下表达式为 n的二进制与 n的二进制数右移后x位的结果进行异或
         *    目的是保证高位1后的数据均为1,普及一下如果高位1后面全是1的话,
         *    即 0000 0000 0000 0000 0000 0000 0000 1111 
         *    这样的二进制表示为 15,可以推导出 为2的幂-1。
         *    至于为何 右移 1 + 2 + 4 + 8 + 16 = 31位
         *    是因为一个 Integer 类型占 4 字节,一个字节占 8 位二进制码,
         *    因此一个 Integer 总共占 32 位二进制码。去除第一位的符号位,
         *    剩下 31 位来表示数值。所以右移31位,以此保证高位1后面的数据均为1。    
         */
        
        
        
        
        n |= n >>> 1;// >>> 代表无符号右移
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        /**
         *    当n小于 最大值时
         *    返回 n+1,由上面我们知道 如果高位1后面全是1的话,
         *    用十进制表示为 2的n次方-1,所以返回的n+1 则为
         *    2的n次方,这就是为什么hashmap的容量必须会是2的n次方
         */
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

内容太多,更多精彩请期待

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-11-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序猿日常笔记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 为什么要有hashmap?
  • 一、HashMap 存储结构
  • 二、HashMap 各常量、成员变量作用 
  • 三、HashMap 几种构造方法 hashmap初始化时为懒加载方式,即在初始化时仅声明容量及扩容阈值等, 不多说上代码。
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档