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的区别,希望本篇文章能帮助到大家,同时也欢迎评论指正,谢谢支持!
慕容田雨 发布了19 篇原创文章 · 获赞 25 · 访问量 1万+ 私信 关注