数据结构学习之HashMap

本文详细解析了Java 8 HashMap的工作原理,包括元素数据结构、初始化方式、扩容机制、链表与红黑树转换策略,以及线程安全性的改进。重点阐述了JDK1.8中如何通过 Compare 接口提升查找效率。
摘要由CSDN通过智能技术生成

     HashMap可以理解为一个数组,数据根据计算出来的hash值存放,遍历顺序是不确定的,Java8之前,每个相同hashCode数组元素的值是用链表存放的,为单向链表,查找元素时间复杂度为O(N),Java8之后,如果同一个hashCode超过了8个值,则为了提高查找效率,在链表长度超过8时,则会将链表转换为红黑树,将查找元素时间复杂度提升到O(logN)。但是真正想要利用 JDK1.8 的好处,有一个限制: key的对象,必须正确的实现了 Compare 接口 如果没有实现 Compare 接口,或者实现得不正确(比方说所有 Compare 方法都返回0) 那 JDK1.8 的 HashMap 其实还是慢于 JDK1.7 的。

一、每个元素的数据结构:

HashMap中定义了一个实现了Map.Entry<k,v>的静态Entry<K,V>类,此类定义了如下(只摘录了最主要的部分):
  

static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        final int hash;

        /**
         * Creates new entry.
         */
        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }

        public final K getKey() {
            return key;
        }

        public final V getValue() {
            return value;
        }
……略
}  

可以看出该类是用来存放键值对的,并且通过next指向下一个实例实现链表结构。

二、HashMap的初始化方式:

//第一种
Map map = new HashMap();
// 第二种
Map map = new HashMap(int initialCapacity);
 
// 第三种
Map map = new HashMap(int initialCapacity, float loadFactor);

HashMap其实是一个存储数据的容器,涉及到容器初始化相关的,就会有相关大小的配置参数:

1. capacity:当前数组容量,始终保持 2^n,可以扩容,扩容后数组大小为当前的 2 倍,初始化为16。

2. loadFactor:负载因子,默认为 0.75,选择0.75作为默认的加载因子,完全是时间和空间成本上寻求的一种折衷选择。在理想情况下,使用随机哈希码,在扩容阈值(加载因子)为0.75的情况下,节点出现在频率在Hash桶(表)中遵循参数平均为0.5的泊松分布。可参考:https://blog.csdn.net/NYfor2017/article/details/105454097

3.threshold:扩容的阈值,等于 capacity * loadFactor

三、扩容

为了减少冲突的概率,当hashMap的数组长度到了一个临界值就会触发扩容,把所有元素rehash再放到扩容后的容器中。扩容的临界值由【加载因子】和当前容器的容量大小来确定:DEFAULT_INITIAL_CAPACITY*DEFAULT_LOAD_FACTOR ,即默认情况下是16x0.75=12时,就会触发扩容操作。

map的扩容,第一次是默认的容量(16),默认的扩容极限值(16 * 0.75 = 12);如果size达到12时,进行下次扩容,容量和极限值均扩容成原来的两倍,32,24;64,48.....,当然也有极限值,容量最大值为MAXiMUM_CAPACITY = 1 <<30,扩容临界值threshold = Integer.MAX_VALUE

四、链表和红黑树转换关系

若桶中链表元素超过8时,会自动转化成红黑树;若桶中元素小于等于6时,树结构还原成链表形式。

原因:

  1、红黑树的平均查找长度是log(n),长度为8,查找长度为log(8)=3,链表的平均查找长度为n/2,当长度为8时,平均查找长度为8/2=4,这才有转换成树的必要;链表长度如果是小于等于6,6/2=3,虽然速度也很快的,但是转化为树结构和生成树的时间并不会太短。

  2、6和8中间有个差值7可以防止链表和树之间频繁的转换。假设一下,如果设计成链表个数超过8则链表转换成树结构,链表个数小于8则树结构转换成链表,如果一个HashMap不停的插入、删除元素,链表个数在8左右徘徊,就会频繁的发生树转链表、链表转树,效率会很低。

五、线程安全

死循环和数据丢失是在JDK1.7中出现的问题,在JDK1.8中已经得到解决,然而1.8中仍会有数据覆盖这样的问题:【jdk 1.8插入是从尾部插入造成数据覆盖,jdk 1.7是从头部插入导致数据丢失或者死循环】

可参看:https://blog.csdn.net/swpu_ocean/article/details/88917958

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值