1、Java HashMap的工做原理node
jdk1.7下HashMap数据结构:数组加链表,链表长度没有8的限制;算法
jdk1.8 HashMap数据结构:数组+链表+红黑树;链表超过8会转存为红黑树;数组
1.jdk1.8 中HashMap的put工做原理:缓存
1)、对key作null检查。若是key是null,会被存储到table[0],由于null的hash值老是0。安全
2)、判断当前桶是否为空,空的就须要初始化(resize 中会判断是否进行初始化)数据结构
给定的默认容量为 16,负载因子为 0.75。Map 在使用过程当中不断的往里面存放数据,当数量达到了 16 * 0.75 = 12 就须要将当前 16 的容量进行扩容,而扩容这个过程涉及到 rehash、复制数据等操做,因此很是消耗性能。并发
3)、获取key的hashCode值(能够理解为内存地址位置,如:3254239),并对该32位hashCode值进行高低16位的异或运算;获得hash值(如:3812);(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);异步
4)、将获取到的hash值进行一个求模运算(至关于3812%16),获得该key对应的索引位置;if ((p = tab[i = (n - 1) & hash]) == null);该位置确定不会超过16位;函数
5)、根据当前 key 的 hashcode 定位到具体的桶中并判断是否为空,为空代表没有 Hash 冲突就直接在当前位置建立一个新桶便可。性能
6)、若是当前桶有值( Hash 冲突),那么就要比较当前桶中的 key、key 的 hashcode 与写入的 key 是否相等,相等就赋值给 e,在第 8 步的时候会统一进行赋值及返回。
7)、若是当前桶为红黑树,那就要按照红黑树的方式写入数据。
8)、若是是个链表,就须要将当前的 key、value 封装成一个新节点写入到当前桶的后面(造成链表)。
9)、接着判断当前链表的大小是否大于预设的阈值,大于时就要转换为红黑树。
10)、若是在遍历过程当中找到 key 相同时直接退出遍历。
11)、若是 e != null 就至关于存在相同的 key,那就须要将值覆盖。
12)、最后判断是否须要进行扩容。
2.jdk1.8 中HashMap的get工做原理:
1)、计算hash部分跟put相同;
2)、首先将 key hash 以后取得所定位的桶。
3)、若是桶为空则直接返回 null 。
4)、不然判断桶的第一个位置(有多是链表、红黑树)的 key 是否为查询的 key,是就直接返回 value。
5)、若是第一个不匹配,则判断它的下一个是红黑树仍是链表。
6)、红黑树就按照树的查找方式返回值。
7)、否则就按照链表的方式遍历匹配返回值。
问题1:HashMap为什么要进行hash计算?hash函数是怎么运算的?
尽可能让node落点分布均匀,减小碰撞的一个几率,若是碰撞几率高了,就势必致使数组下标下的链表长度太长;
运算方式:将获取到的32位hash值,分红高16位和低16位,再将高16位移到低16位进行异或运算;
32位:3254239,经过异或运算后,3812,
table[3812]越界,再作个求模运算3812%16=,必定不会超过16位;hash%n===(n-1)&hash,速度快,效率高;
问题2:数组扩容,2倍扩容;为何是2的N次幂;就是由于hash算法这(n-1)&hash;要符合这算法;必须是这样;
resize()方法中的扩容:newThr = oldThr << 1; // double threshold
问题3:可是 HashMap 原有的问题也都存在,好比在并发场景下使用时容易出现死循环。
HashMap 扩容的时候会调用 resize() 方法,就是这里的并发操做容易在一个桶上造成环形链表;这样当获取一个不存在的 key 时,计算出的 index 正好是环形链表的下标就会出现死循环。
2、Hashtable、LinkedHashMap、TreeMap、SortedMap、WeakHashMap、IdentityHashMap、ConcurrentHashMap的区别:
(1) HashMap与HashTable的区别:
a.Hashtable中的对象是线程安全的。而HashMap则是异步的,所以HashMap中的对象并非线程安全的。由于同步的要求会影响执行的效率,因此若是你不须要线程安全的集合那么使用
HashMap是一个很好的选择,这样能够避免因为同步带来的没必要要的性能开销,从而提升效率。
b.值:HashMap可让你将空值做为一个表的条目的key或value,可是Hashtable是不能放入空值的。HashMap最多只有一个key值为null,但能够有无数多个value值为null。
注意:
一、用做key的对象必须实现hashCode和equals方法。
二、不能保证其中的键值对的顺序
三、尽可能不要使用可变对象做为它们的key值。
(2) LinkedHashMap:
它的父类是HashMap,使用双向链表来维护键值对的次序,迭代顺序与键值对的插入顺序保持一致。LinkedHashMap须要维护元素的插入顺序,插入性能略低于HashMap,但在迭代访问元
素时有很好的性能,由于它是以链表来维护内部顺序。
(3) TreeMap和SortedMap:
Map接口派生了一个SortMap子接口,SortMap的实现类为TreeMap。TreeMap也是基于红黑树对全部的key进行排序,有两种排序方式:天然排序和定制排序。HashMap一般比TreeMap快一点(树
和哈希表的数据结构使然),建议多使用HashMap,在须要排序的Map时候才用TreeMap。
(4) WeakHashMap:
WeakHashMap与HashMap的用法基本相同,区别在于:后者的key保留对象的强引用,即只要HashMap对象不被销毁,其对象全部key所引用的对象不会被垃圾回收;WeakHashMap适合短期内就过时的缓存时最好使用weakHashMap,它包含了一个自动调用的方法expungeStaleEntries,这样就会在值被引用后直接执行这个隐含的方法,将不用的键清除掉。
(5) IdentityHashMap类:
IdentityHashMap与HashMap基本类似,只是当两个key严格相等时,即key1==key2时,它才认为两个key是相等的 。IdentityHashMap也容许使用null,但不保证键值对之间的顺序。
(6) EnumMap类:
一、EnumMap中全部key都必须是单个枚举类的枚举值,建立EnumMap时必须显示或隐式指定它对应的枚举类。
二、EnumMap根据key的天然顺序,即枚举值在枚举类中定义的顺序,来维护键值对的次序。
三、EnumMap不容许使用null做为key值,但value能够。
(7) ConcurrentHashMap:
1.ConcurrentHashMap对整个桶数组进行了分段,而HashMap则没有
2.ConcurrentHashMap在每个分段上都用锁进行保护,从而让锁的粒度更精细一些,并发性能更好,而HashMap没有锁机制,不是线程安全的。
ConcurrentHashMap如何进行扩容的?
当往hashMap中成功插入一个key/value节点时,有可能触发扩容动做:
一、若是新增节点以后,所在链表的元素个数达到了阈值 8,则会调用treeifyBin
方法把链表转换成红黑树,不过在结构转换以前,会对数组长度进行判断
若是数组长度n小于阈值MIN_TREEIFY_CAPACITY
,默认是64,则会调用tryPresize
方法把数组长度扩大到原来的两倍,并触发transfer
方法,从新调整节点的位置。
二、新增节点以后,会调用addCount方法记录元素个数,并检查是否须要进行扩容,当数组元素个数达到阈值时,会触发transfer方法,从新调整节点的位置。
3、红黑树的理解? 红黑树是一种自平衡二叉查找树,红黑树是一种颇有意思的平衡检索树;每次插入的时候都要进行计算,保证二叉树的平衡;若是有2的N次方数据量级,查询的时候只须要查询N次便可。 咱们对任何有效的红黑树加以以下增补要求: 1.节点是红色或黑色。 2.根是黑色。 3.全部叶子(外部节点)都是黑色。 4.每一个红色节点的两个子节点都是黑色。(从每一个叶子到根的全部路径上不能有两个连续的红色节点) 5.从每一个叶子到根的全部路径都包含相同数目的黑色节点。 这些约束强制了红黑树的关键属性: 从根到叶子的最长的可能路径很少于最短的可能路径的两倍长。结果是这个树大体上是平衡的。