JDK1.7HashMap底层解析

底层数据结构:数组 链表
key—》hashcode&key --》寻址
新元素进来插入到链表头部,新元素的引用存在数组里,next里边存着老元素的地址。
在这里插入图片描述
初始化


    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 初始容量 16
     * 最大容量
    static final int MAXIMUM_CAPACITY = 1 << 30;
     * 加载因子 3/4
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
     * 当表没有膨胀时,要共享的空表实例
    static final Entry<?, ?>[] EMPTY_TABLE = {};
     * 根据需要调整表的大小。长度必须是2的次幂。数组
    transient Entry<K, V>[] table = (Entry<K, V>[]) EMPTY_TABLE;
     * 此映射中包含的键-值映射的数目。
    transient int size;
     * 调整大小的下一个大小值(容量*加载因子)。阈值
    int threshold;
     //默认负载因子,是0.75的原因主要是“哈希冲突”和“空间利用率”矛盾的一个折中
	//加载因子越大,填满的元素越多,空间利用率越高,但冲突的机会加大了。
	//从源码中的注释可以知道hash桶中元素个数遵循泊松分布,在负载因子为0.75的时候
	//桶中元素个数超过8个几乎是不可能的,所以0.75是解决“哈希冲突”和“空间利用率”矛盾比较优的一个值。是一个最优解
    final float loadFactor;
    *构造方法
    public HashMap(int initialCapacity, float loadFactor) {
        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;
        threshold = initialCapacity;
        init();
    }

	
    


初始化时只是初始了各种数据,但是没创建数组

添加元素

//添加元素
    public V put(K key, V value) {
        //第一次向map中添加元素
        if (table == EMPTY_TABLE) {
            //初始化
            inflateTable(threshold);
        }
        //如果key为null,存储位置为table[0]或table[0]的冲突链上
        if (key == null)
            //保存null值,放入链表首位
            return putForNullKey(value);
        //将key计算hash值,尽量散列均匀分布
        int hash = hash(key);
        //计算key的位置,保证下标不会越界
        int i = indexFor(hash, table.length);
        //hash冲突解决
        for (Entry<K, V> e = table[i]; e != null; e = e.next) {//遍历链表
            Object k;
            //判断hash和equals   
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                //后传进来的覆盖之前的节点 
                e.value = value;
                //调用value的回调函数,其实这个函数也为空实现 linkedHashMap里面实现了此方法
                e.recordAccess(this);
                return oldValue;//返回旧的值
            }
        }
        //保证并发访问时,若HashMap内部结构发生变化,快速响应失败
        modCount++;
        //插入链表的头部
        addEntry(hash, key, value, i);
        return null;
    }
	/**
     * Inflates the table.
     * 开始添加
     */
    private void inflateTable(int toSize) {
        // Find a power of 2 >= toSize
        //求2的幂>= toSize, 如果是10,则返回16
        int capacity = roundUpToPowerOf2(toSize);
        //计算扩容值 12 阈值
        threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
        //初始化数组长度 16
        table = new Entry[capacity];
        initHashSeedAsNeeded(capacity);
    }2的幂>= toSize
    private static int roundUpToPowerOf2(int number) {
        // assert number >= 0 : "number must be non-negative";
        return number >= MAXIMUM_CAPACITY ? MAXIMUM_CAPACITY
                //1 number - 1 先将原有的数字-1,目的是防止16/32/64..经过计算返回32/64/128
                //2 << 1右移1位相当于乘以2 目的是经过计算得到的是>=number
                //3 highestOneBit方法计算
                : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
    }
    /**
     * 将空值放入链表首位  也会去遍历 数组存放的位置  可以有其他元素
     */
    private V putForNullKey(V value) {
        for (Entry<K, V> e = table[0]; e != null; e = e.next) {
            if (e.key == null) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;
        addEntry(0, null, value, 0);
        return null;
    }
    void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold) && (null != table[bucketIndex])) {
            //扩容原先的两倍
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }

        createEntry(hash, key, value, bucketIndex);
    }
    把当前数组下标上的那个元素 换成新的元素  这个e是之前元素的地址  在新的元素中 作为next
	 void createEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K, V> e = table[bucketIndex];//获取数组中的entry
        table[bucketIndex] = new Entry<>(hash, key, value, e);//重新new 个entry,将旧的e作为next
        size++;//size +1
    }
	对key的计算 
    final int hash(Object k) {
        int h = hashSeed;
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }
        h ^= k.hashCode();
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

    /**
     * 可以保证在数组大小范围内 而且可以保证平均
     */
    static int indexFor(int h, int length) {
        // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
        //位与运算,保证当前数组不会越界,减1是为了让高位全是0,低位全是1,不会越界
        return h & (length - 1);
    }


HashMap 会在 put 元素时,通过元素的 hash 值以及当前数组的长度,来确定一个下标来存放元素。
h & (length - 1); 是控制hash值长度在数组范围内
h ^= k.hashCode(); 是让数值更加平均
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4); 加上这个会更加散列

扩容

void resize(int newCapacity) {
        Entry[] oldTable = table;//旧的数组
        int oldCapacity = oldTable.length;//旧的数组长度
        if (oldCapacity == MAXIMUM_CAPACITY) {//判断是否等于Integer.max
            threshold = Integer.MAX_VALUE;//不再扩容
            return;
        }
        Entry[] newTable = new Entry[newCapacity];//创建一个新的数组,是原来的两倍
        //将旧的集合放入新的集合,会重新计算hash值
        transfer(newTable, initHashSeedAsNeeded(newCapacity));
        table = newTable;
        threshold = (int) Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }
  // 将老的数组转移到新的数组
    void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;//新的数组大小
        for (Entry<K, V> e : table) {//双重循环
            while (null != e) {//加入链表顺序为1->2->3
                Entry<K, V> next = e.next;//next=2
                if (rehash) {//是否计算hash,一般是false
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                int i = indexFor(e.hash, newCapacity);//计算数组下标,两种结果,要么在原有位置,要么原有的下标+oldTable.length
                e.next = newTable[i];//= null 或=2
                newTable[i] = e; // =1
                e = next;
            }
        }
    }

1、resize发生在table初始化, 或者table中的节点数超过threshold值的时候, threshold的值一般为负载因子乘以容量大小.
2、每次扩容都会新建一个table, 新建的table的大小为原大小的2倍.
3、扩容时,会将原table中的节点re-hash到新的table中, 但节点在新旧table中的位置存在一定联系: 要么下标相同, 要么相差一个原table的大小.
4 由于HashMap的扩容需要遍历整个map重新调整每个Entry的位置,比较消耗性能,所以在开发中使用HashMap时,如果已知需要存储的总的Entry数量,我们可以在实例化HashMap时,给定一个合理的初始容量,从而减少map的resize和rehash操作,从而提高程序的运行效率和map的安全性;
5 旧数组中同一条Entry链上的元素,通过重新计算索引位置后,有可能被放到了新数组的不同位置上
6 ModCount作用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值