集合框架之HashMap(一)

半城伤御伤魂 提交于 2020-03-01 08:33:16

HashMap是非常重要的数据结构,并且大部分面试都会问到,优秀的java程序员应当要对HashMap进行深入的了解,今天我们就来剖析一下它。

目录

  • HashMap简介
  • 成员变量
  • get和put的流程
  • hashMap相关的面试题
  • 总结

一.简介

首先,HashMap是一个无序key,value集合,它的底层存储是由数组加链表和红黑树结构组成的的。在进行添加,删除和查找时,效率非常高,如果不考虑哈希碰撞,一次定位就能完成操作,时间复杂度为O(1)。
下面是一个默认长度的hashMap。

在这里插入图片描述

二.hashMap的成员变量

1.初始大小

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

这个是值是数组长度,也就是数组的初始最大行数,当然如果碰撞比较多,也有可能hashMap存了200个值,但是容量还是16。当然这不是理想状态。

2.最大值

static final int MAXIMUM_CAPACITY = 1 << 30;

这个没有什么好说的,hashMap最大容量。

3.增长因子

static final float DEFAULT_LOAD_FACTOR = 0.75f;

增长因子,就是如果数组被占用的大小超过当前大小的75%时,就进行扩容,按照当前容量二倍创建一个新的Entry,把旧Entry里的值重新hash计算,放到新的Entry里。

4.变树阈值

static final int TREEIFY_THRESHOLD = 8;

jdk8对hashMap做的优化,当链表长度大于8时,链表转换成红黑树,因为红黑树的查询效率是O(logn),
而链表的查询效率是O(n)

5.变链阈值

static final int UNTREEIFY_THRESHOLD = 6;

当红黑树总元素数小于6时,再转换成链表。因为红黑树元素太少时,效率比较低,所以转换成链表

三.HashMap的构造器

HashMap 提供了四个构造器,下面来一一看下。

1.HashMap()

使用默认的构造函数,构造一个初始容量为16,增长因子为0.75的空HashMap;

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
static final float DEFAULT_LOAD_FACTOR = 0.75f;

public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; 
}

2.HashMap(int initialCapacity, float loadFactor)

第一个参数是初始大小,第二个参数是增长因子,通过这两个参数来构造一个空的HashMap。

public HashMap(int initialCapacity, float loadFactor) {
    //判断容量是否小于0
    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;
    //计算下一次resize的阈值
    this.threshold = tableSizeFor(initialCapacity);
}

3.HashMap(int initialCapacity)

一个参数的构造,通过初始增长因子构造一个空的HashMap,调用

HashMap(int initialCapacity, float loadFactor)构造方法。
public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

4.HashMap(Map<? extends K, ? extends V> m)

根据已有的Map接口创建一个元素相同的hashMap,使用默认的初始容量和增长因子。
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}

四.get和put方法

1.put方法

在HashMap插入值时,会判断是否已经初始化,如果没有初始化,就会创建一个长度为16的Entry,设置增长因子,默认为0.75。
然后判断key是否是Null,如果是Null则放到0处,如果不是,则对其key进行hash,实际操作是高位与低位混淆后做求余操作,然后,如果hash求出来的值与数组上的key值进行匹配,如果没有匹配上,则放到对应的数组bucket位置;如果与之前可以匹配上了(即为hash碰撞),则一一对比链表上的Key值是否相同,如果匹配上了则对当前位置的值进行修改,如果没有匹配的key,则放到该链表的尾部。

2.get方法

在hashMap进行get操作时,先判断key是否是null,如果是null则,直接取0处的bucket值,
如果不是则对key的hash值和数组上的每一个bucket的key的hash值对比没如果相同,则循环链表并对比key值是否相同,如果都相同,则返回该位置的value,如果没有相同的,则返回空。

五.HashMap相关的面试题

1.hashMap和hashtable区别
hashMap和Hashtable的内部结构基本一致,
1)hashMap支持null 作为key,但hashtable不支持;
2)HashMap是非线程安全的数据结构,所以相对来说,存取速度比较快;而Hashtable是线程安全的,因为hashtable的get、put等方法上添加了synchrounized的参数,对大部分方法上了排他锁,也就是说,在一个线程访问时其他线程都要等待。
但是如果数据量增大以后,HashTable的效率下降的比较快,所以如果你需要保证线程安全又要兼顾效率的话,从jdk1.6版本开始,java给提供了ConcurrentHashMap,它相对于hashtable的性能,做了优化,
它提出了分段锁机制, 它默认分8个段,操作开始时,先判断在那段内,然后操作时只锁定该段的数据,这样的话,只有访问同一个段的线程才回排队等待,访问不同段的线程之间互不冲突,这样来说,相对于hashtable对多线程的操作的效率提升了至少8倍,多线程并发操作效率得到了极大的提高。
2.Map<String,Object> map = new HashMap<>(20),问,该HashMap扩容了几次?
答:扩容了0次,因为它调用了HashMap的初始化方法,直接创建了一个长度为32的HashMap,而不是一个一个添加值,添加了20次,所以它只扩容了1次。
3.Map<String,Object> map = new HashMap<>(28),问,这条语句java虚拟机做了什么?
答:该语句创建了一个初始容量为64的空格的HashMap,为什么是64而不是32呢,因为它没有传加载因子,所以他的加载因子是0.75,28/32=0.875 ,已超过了0.75的阈值,所以自动扩容为当前的二倍,创建了一个长度为64的hashMap。

总结

今天对HashMap的实现原理进行了讲解,并对源码进行了分析,最后对比了hashMap和hashtable的区别,希望本篇文章能帮助到大家,同时也欢迎评论指正,谢谢支持!

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!