jdk1.7-hashMap源码学习

前言

  • hashMap在数据结构上叫做散列表,其本意就是将元素尽量均匀分布到每个位置。

  • jdk1.7中的hashMap底层实现: 一个数组 + 多个单向链表

  • 影戏hashMap性能的主要是两个参数:数组初始化长度 + 加载因子。由这两个参数
    的合理设置,在时间和空间上保持最好的平衡。

  • fail-fast机制,即迭代器创建后,如果还去更新hashmap,再去操作迭代器时会抛出异常ConcurrentModificationException

在这里插入图片描述

代码中的常量和变量

//默认初始化长度
static final int DEFAULT_INITIAL_CAPACITY = 16;

//最大长度
static final int MAXIMUM_CAPACITY = 1 << 30;

//默认加载因子     size/capacity = loadFactor
static final float DEFAULT_LOAD_FACTOR = 0.75f;

//放元素的数组   要求长度是2的n次幂
 transient Entry<K,V>[] table;

//已经放入的元素的个数
transient int size;

// capacity * load factor
int threshold;

//加载因子
final float loadFactor;

//map被改变的次数
transient int modCount;

构造器

构造器参数都是围绕capacity(数组长度)和loadFactor(加载因子)

 public HashMap(int initialCapacity, float loadFactor) {
	// 找一最靠近initialCapacity的2的次幂数
        int capacity = 1;
        while (capacity < initialCapacity)
            capacity <<= 1;

        this.loadFactor = loadFactor;
        threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
        table = new Entry[capacity];
		//  sun.misc.VM.isBooted默认是true
		//  Holder.ALTERNATIVE_HASHING_THRESHOLD  默认是Integer.MAX_VALUE
		// 所以这里useAltHashing 默认是false
		useAltHashing = sun.misc.VM.isBooted() &&
                (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
 }
 
public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
 }

public HashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}

public HashMap(Map<? extends K, ? extends V> m) {
       // 这里+1 因为 第一个构造器里面   capactity < initcapacity 没等号
        this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
                      DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
        putAllForCreate(m);
}

几个重要的方法

hash
这个方法是用来获取加入进来的键值对  key的hash值
final int hash(Object k) {
        int h = 0;
        // 如果JVM没有特别设置  useAltHashing默认false
        // 所以一般不会走进去
        if (useAltHashing) {
            if (k instanceof String) {
                return sun.misc.Hashing.stringHash32((String) k);
            }
            // 默认为0
            h = hashSeed;
        }
		
		//这三步获取key的hash值
		//为什么要重新计算hashcode值,而不是将它直接去取模
		// 因为length太小了,导致hashcode只有低位参与运算
		// 而将它右移,让高位参与到运算,减少碰撞概率
        h ^= k.hashCode();
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }
indexFor
用来获取键值对    即将放入数组的哪一个下标
static int indexFor(int h, int length) {
       // 将键值对 key的hash值 和 (数组长度 - 1) 取模获取下标

	   // 本来取模是 % 这里用按位与& 是因为
       // 当length = 2 的 n 次幂
       // h % length  =  h & (length - 1)
       // & 的效率肯定比 % 要高
        return h & (length-1);
}
get
public V get(Object key) {
        if (key == null)
            // hashmap中将key==null的键值对总是放在table[0]中
            return getForNullKey();
         
        Entry<K,V> entry = getEntry(key);

        return null == entry ? null : entry.getValue();
}

final Entry<K,V> getEntry(Object key) {
        int hash = (key == null) ? 0 : hash(key);
        // 循环链表
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
        // hash值相等 且  (== 或 equals)
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    }
put
public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        int i = indexFor(hash, table.length);
        // 已经有该key了,就覆盖原来的value,并返回原来的value
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                // recordAccess是一个空方法,只要出现覆盖,就调用
                e.recordAccess(this); 
                return oldValue;
            }
        }
        // 修改次数+1
        modCount++;
        // 如果之前map中没有该key,添加一个键值对
        addEntry(hash, key, value, i);
        return null;
    }
    
    void addEntry(int hash, K key, V value, int bucketIndex) {
        //如果map里面已经装入的元素的个数  >= 数组长度 * 加载因子
        //并且数组该下标已经有一个键值对的情况下
        //对table扩容, 数组新的长度是原来的2倍
        // 这种扩容的条件其实就是保证数组有元素的 每个位置放入的元素个数差不多
        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);
    }
    
    void createEntry(int hash, K key, V value, int bucketIndex) {
    //hashmap采用的是头插法
    //意思就是新加入的元素,会被放在链表最前面,成为新的head
        Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;
    }
resize
单独看这个扩容方法,是因为多线程环境操作该方法,扩容后的数组,可能会有一个循环链表
即     a ⇄ b -> null, 这样的后果是遍历该链表时,会死循环
void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }
        // 创建一个新的数组
        Entry[] newTable = new Entry[newCapacity];
        boolean oldAltHashing = useAltHashing;
        useAltHashing |= sun.misc.VM.isBooted() &&
                (newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
        // 在没有特殊情况下,rehash还是false
        boolean rehash = oldAltHashing ^ useAltHashing;
        transfer(newTable, rehash);
        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) {
                Entry<K,V> next = e.next;
                // *******关键位置*********
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                int i = indexFor(e.hash, newCapacity);
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            }
        }
    }
   假如有一条链表是这样的,里面有2个节点对象
   Entry<key, value> A   =>   Entry<key, value> B
   A.key = A                  B.key = B
   A.value = A                B.value = B
   A.next = B                 B.next = null
 
   1.x线程执行到关键位置停止,切换到y线程。x中next = e.next = B。
   
   2.y线程执行完transfer方法停止,切换回x线程,此时节点A,B对象已经发生变化
   假如新的table中它们还是在一个下标位置上,那么此时
   		 B.next = A , A.next = null; B => A => null
   		 
   3.x线程继续执行,第一次循环,此时x线程中的newTable中还没有值
      e.next = A.next = newTable[i] = null
      newTable[i] = e = A (此时A是head 链表是 A =>null)
      e = next = B
      
   4.第二次循环
      next = B.next = A (因为y线程,B对象发生变化,如果没被修改应该是null)
      e.next = B.next = newTable[i](head) = A
      newtable[i] = B  (此时B变为head)
      e = next = A
   正常情况下 e = null, 这里就结束了
   
   5.第三次循环
      next = A.next = null
      e.next = A.next = newTable[i](head) = B
      newTable[i] = A
      e = next = null
     结束 此时链表变成  A ⇄ B
      
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值