说到HashMap就得说一下1.7和1.8进行的区别了 1.7之前使用的是数组加链表的形式,他的数据节点是一个Entry节点,数据插入的过程是头插法,在HashMap使用头插法的时候里面会有一个resize方法里面再进行调用了一个transfer方法,把里面的Entry进行了一个reHash,使用过程可能会在下一次Get的时候出现一个死循环,还有可能因为没有加锁,多个线程并发的时候数据并不能保证是安全的,把push进去的值去出来之后还是push进去的值 1.8之后是数组加链表加红黑树的一个结构把原来的Entry节点变成一个Node节点,整个put流程也进行了一个优化。 扩容机制是capacity这个节点在初始化HashMap的时候如果没有设置,他初始化节点是16,负载因子是0.75然后会计算出一个threshold一个阈值,意味着这个数组最多只能存放12,我进行put的时候会进行一个判断,我 当前的size要不要大于这个阈值,如果大于的话默认扩容原来的2倍,将原来的Entry进行了一个resize的过程 扩容这个操作可以调高一点,比如调整为1,那么hashmap到16的时候进行扩容,可以进行这个操作但是不推荐,负载因子调高之后意味着哈希冲突的概率也会高同样会耗时(查找的速度变慢了)1.8之后变成了尾插法 因为采用了尾插法没有改变数据插入的一个顺序,不会出现一个链表循环的一个过程 我们把元素放进HashMap时算出这个元素所在的位置,在HashMap利用位运算取模,更高效的算出元素所在的位置
为什么Hashmap只能2次幂 因为只有大小为2次的时候才能合理运用位运算取模 在put元素的时候如何算出hash值的 先算出正常的哈希值,然后在高16位做异或运算,增加了随机性,减少了碰撞冲突的可能性 日常开发中如何保证线程安全的 hashMap不是线程安全的,在多线程环境下HashMap有可能数据丢失和获取不了最新数据的问题,比如说线程Aput进去了线程Bget不出来,线程安全一般使用ConcurrentHashMap,除此以外还有HashTable,也可以使用Collections包装出来一个线程安全的 map使用synchronized或者lock,但是无论是哪一种都得死效率比较低的(因为都是在外面直接嵌套synchronized) ConcurrentHashMap并发度是更高的,HashTable是直接对里面的方法进行了一个Synchronized加了一个对象锁 jdk1.6之后进行了一个锁升级的过程,效率更高并发度更高,在get的时候没有加锁,Node都使用了volatie来修饰, 在扩容时会给每个线程分配对应的区间,并且为了防止putval导致数据不一致,给线程所负责的区间加锁。 put和get方法的实现 在put的时候先对key做hash运算,计算出key所在的index 如果没有产生碰撞,直接放到数据中去,如果产生了碰撞需要判断目前数据结构是链表还是红黑树,根据不同情况进行插入 假设key是一样的则替换之前的值。最后判断表是否满了(当前hash表大小*负载因子)如果满了则进行扩容。 在get的时候,对key做hash运算计算出该key所在的index判断是否有hash冲突,如果没有的话直接返回,有冲突则判断当前数据结构是链表还是红黑树以不同的数据结构中取出。 HashMap怎么判断一个元素是否相同 比较hash值,然后用==或者equals()比较是否相同,如果hash值相同说明遇到hash冲突如果都相同说明元素是同一个。 什么情况下用到红黑树 数组大于64,且链表大于8时链表改为红黑树。当红黑树大小为6时会退化为链表。 红黑树和链表转换的原因是因为出于查询和插入性能考虑 链表查询o(n)插入n(1),红黑树查询和插入时间复杂度为o(logn) 锁升级的过程 我们这个锁是支持偏向锁,当前获取锁资源的这个线程我会优先的让他再去获取到这个锁,如果没有获取到这个锁就升级成为一个轻量级的一个CAS的锁就是一个乐观锁CAS是一个比较和交换的锁,如果这个CAS没有设置成功的话,他会进行一个锁自旋,自旋到一定次数的时候会在无锁的一个状态,上来先去判断一下然后再锁升级成为一个Synchronized的一个这样的重量级的锁,这样保证了一个性能的问题(这里学的不是很好小小整理一下)