【2023年面试】HashMap

#【2023年面试】

1. hash表的简介

  • 核心是哈希桶(数组)和链表

  • O(1)平均查找和删除时间(最理想,最不理想的就是O(n))

  • hash的缺点:在于会出现hash碰撞,没有办法避免,只能尽量避免

  • 请问什么叫hash:hash也成散列,基本原理就是把任意长度的输入,通过hash算法变成固定长度的输出,

  • hash的特点:

    1.从hash值不可以反向推导出原始数据

    2.相同的数据一定会得到相同的hash值,不相同的数据也可能得到相同的hash值

    3.hash非常的高效,长的文本也可以很快的算出hash

    4.符合抽屉原理,把10个苹果放在九个抽屉里,至少有一个抽屉不少于两个苹果,所以hash冲突不可避免

    5.好的hash算法,算出来的hash值尽量分散

2. jdk1.7 hashMap的学习

2.1数据结构

  • 经典hash表的实现 数组+链表 ,也叫散列表,整合了数组的索引,整合了链表的动态插入删除,

2.2初始容量

  • 初始容量(桶的个数)初始容量其实是空,第一次put值的时候默认用16来作为第一次put值后的初始容量。

  • 初始容量必须是2的n次方,如果自定义构造方法初始化的容量不是2的幂的时候,put时,会改写成比自己大的最接近的2的幂

    比如,初始容量是7,那么会变成8。

2.3装载因子

  • 值为:0.75
  • 为什么是0.75,在时间和空间上提供了一个很好的折中!如果高于0.75那么减少了空间的消耗,但是减慢了查找的效率,如果负载因子低于0.75,那么就增加了空间的消耗,但也加快了查找的效率。
  • 装载因子的作用:为了算出装载因子

2.4size

  • 实际元素数量

2.5hashmap中的常量

// 1. 容量(capacity): HashMap中数组的长度
// a. 容量范围:必须是2的幂 & <最大容量(2的30次方)
// b. 初始容量 = 哈希表创建时的容量
  // 默认容量 = 16 = 1<<4 = 00001中的1向左移4位 = 10000 = 十进制的2^4=16
  static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
  // 最大容量 =  2的30次方(若传入的容量过大,将被最大值替换)
  static final int MAXIMUM_CAPACITY = 1 << 30;

// 2. 加载因子(Load factor):HashMap在其容量自动增加前可达到多满的一种尺度
// a. 加载因子越大、填满的元素越多 = 空间利用率高、但冲突的机会加大、查找效率变低(因为链表变长了)
// b. 加载因子越小、填满的元素越少 = 空间利用率小、冲突的机会减小、查找效率高(链表不长)
  // 实际加载因子
  final float loadFactor;
  // 默认加载因子 = 0.75
  static final float DEFAULT_LOAD_FACTOR = 0.75f;

// 3. 扩容阈值(threshold):当哈希表的大小 ≥ 扩容阈值时,就会扩容哈希表(即扩充HashMap的容量) 
// a. 扩容 = 对哈希表进行resize操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数
// b. 扩容阈值 = 容量 x 加载因子
  int threshold;

// 4. 其他
 // 存储数据的Entry类型 数组,长度 = 2的幂
 // HashMap的实现方式 = 拉链法,Entry数组上的每个元素本质上是一个单向链表
  transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;  
 // HashMap的大小,即 HashMap中存储的键值对的数量
  transient int size;

注意:

  • threshold 扩容阈值 = 容量 * 加载因子 不是size * 加载因子。
  • 当size>=扩容阈值时,哈希表进行扩容操作。

2.6hashcode如何转化为数组下标

  • 答:hash&(length-1) length为容量。

2.7为什么是2的n次方

在这里插入图片sadas描述

注意:&按位与的计算方法,链接

  • 原因:length 2的n次方无论任何值-1 2进制结果数都是全1值,这样做可以防止2进制出现零的情况,如果二进制出现0,0与任何在一起算都是0,那么就会出现某些桶一直为空情况。
    举例:(2-1) ———> 二进制 1
    ( 4-1) ———> 二进制 11
    ( 8-1) ———> 二进制 111
    (16-1)———> 二进制 1111
  • 目的:使计算出的数组下标理论上来说更均匀!
  • 注意:最终算出来的index 要满足两个条件1.在0-数组长度之间 2.要平均

2.8hashcode 与hash方法 与indexFor方法

  • hash方法是为了尽量减少hash经常碰撞,尽量的分散开来
  • indexFor是为了计算出数组下标
  • 顺序为:hashcode->hash方法->indexFor方法
    在这里插入图片描述

2.9扩容

  • resize负责扩容,transfer负责移动元素。

  • 每次扩容是原来的容量的两倍。

  • transfer步骤里面的操作是,遍历元素,rehash,计算数组下标。

    在这里插入图片描述

2.9 hashMap1.7bug1----- 扩容的死锁的问题

描述

  • 相关地址:链接
  • 原因:多线程操作的情况下,头插法插入入元素
  • 有人把这个问题报给了Sun,不过Sun不认为这个是一个问题。因为HashMap本来就不支持并发。要并发就用ConcurrentHashmap

2.10hashmap1.7bug2-----恶意攻击(请求数据为hashcode相同的数据)

  • 恶意请求服务器相同的key可造成hash碰撞,也就是某个桶的entry变成链表,查询时间复杂度从O(1)变到O(n)

2.11注意小点

  • 为什么扩容不用hashcode%容量-1的方式来计算数组下标~答1.负数对长度求余是负数. 2.计算较慢

3.JDK1.8的实现原理

3.1 JDK1.7与JDK1.8的区别

  • 从数组/链表 -> 数组/链表/红黑树
  • 扩容时插入顺序的改变,7是头插,8是尾插,虽然尾插不会出现死锁,官方也不建议多线程情况下用hashmap
  • 扩容是假如是链表情况,JKD8可以保证链表的顺序不变,JDK7的顺序会倒过来。
  • java8引入了函数方法、lamad表达式

3.2红黑树是什么?

  • 红黑树是一个可以通过左旋,右旋,变色操作防止变成链表的使自己始终保持接近二叉平衡树(O(logN)的一个数据结构
  • 什么是红黑树
  • 为什么不一开始就用红黑树而是,从链表变红黑树,因为防止浪费资源,树占的空间大

3.3为什么hashmap单个桶的节点超过8个,就从链表转变成红黑树?

  • 官方给的解释是:每个桶的节点数量符合参数为0.5的泊松分布,超过8的概率就非常非常小了
  • 注意:是大于8的时候
for (int binCount = 0; ; ++binCount) {
   
                    if ((e = p.next) == null) {
   
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }

3.4关键变量

/**

 * The default initial capacity - MUST be a power of two.
 * 初始容量大小 必须是2的次幂
   */
   static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

/**

 * The maximum capacity, used if a higher value is implicitly specified
 * by either of the constructors with arguments.
 * MUST be a power of two <= 1<<30.
 * 最大的容量大小
 * 超过这个值就将threshold修改为Integer.MAX_VALUE,数组不进行扩容
   */
   static final int MAXIMUM_CAPACITY = 1 << 30;

/**

 * The load factor used when none specified in constructor.
 * 负载因子
   */
   static final float DEFAULT_LOAD_FACTOR = 0.75f;

/**

 * The bin count threshold for using a tree rather than list for a
 * bin.  Bins are converted to trees when adding an element to a
 * bin with at least this many nodes. The value must be greater
 * than 2 and should be at least 8 to mesh with assumptions in
 * tree removal about conversion back to plain bins upon
 * shrinkage.
 * 链表大于这个值就会树化
 * 注意:树化并不是整个map链表,而是某一个大于此阈值的链表
 * 树化阈值
   */
   static final int TREEIFY_THRESHOLD = 8;

/**

 * The bin count threshold for untreeifying a (split) bin during a
 * resize operation. Should be less than TREEIFY_THRESHOLD, and at
 * most 6 to mesh with shrinkage detection under removal.
 * 小于这个值就会反树化
 * 树变为链表的阈值
   */
   static final int UNTREEIFY_THRESHOLD = 6;
   /**
    *树化的另外一个条件 size大于64才可以升级为tree
   * */
   static final int MIN_TREEIFY_CAPACITY = 64;
   /* ---------------- Fields -------------- */

   /**
    * The table, initialized on first use, and resized as
    * necessary. When allocated, length is always a power of two.
    * (We also tolerate length zero in some operations to allow
    * bootstrapping mechanics that are currently not needed.)
    * hash表中的数组
    */
   transient Node<K,V>[] table;

   /**
    * Holds cached entrySet(). Note that AbstractMap fields are used
    * for keySet() and values().
    */
   transient Set<Map.Entry<K,V>> entrySet;

   /**
    * The number of key-value mappings contained in this map.
    * 当前hash表中元素个数
    */
   transient int size;

   /**
    * The number of times this HashMap has been structurally modified
    * Structural modifications are those that change the number of mappings in
    * the HashMap or otherwise modify its internal structure (e.g.,
    * rehash).  This field is used to make iterators on Collection-views of
    * the HashMap fail-fast.  (See ConcurrentModificationException).
    * hash表修改的计数器
    */
   transient int modCount;

   /**
    * The next size value at which to resize (capacity * load factor).
    *
    * @serial
    */
   // (The javadoc description is true upon serialization.
   // Additionally, if the table array has not been allocated, this
   // field holds the initial array capacity, or zero signifying
   // DEFAULT_INITIAL_CAPACITY.)
   当hash表中元素超过这个阈值的时候,触发扩容
   int threshold;

   /**
    * The load factor for the hash table.
    *
    * @serial
    */
    负载因子
   final float loadFactor;

3.5四个构造方法

//构造方法1
//指定初始容量大小,负载因子
public HashMap(int initialCapacity, float loadFactor) {
   
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY) //1<<30 最大容量是 Integer.MAX_VALUE;
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值