JDK1.7-HashMap的源码

1.写在前面

前面的Redis的系列的博客已经结束了,今天我打算讲一下HashMap的源码,今天我打算讲一下1.7的HashMap的源码,因为1.7和1.8的源码的变化还是挺大,所以今天我打算讲一下1.7的HashMap的源码,因为1.8的就已经完全不一样了。

2.Map接口

因为HashMap的源码是实现Map的接口,我们平时也都用Map这个接口来接收,所以我们直接看下Map这个接口有哪些方法,具体的如下:

public interface Map<K,V> {
  
  //获取Map的长度
  int size();
  
  //map的长度是否为空
  boolean isEmpty();

  //是否包含键
  boolean containsKey(Object key);
  
  //是否包含对应的值
  boolean containsValue(Object value);

  //根据对应的键获取对应的值
  V get(Object key);

  //设置对应的键和值
  V put(K key, V value);
  
  //根据键删除对应的值
  V remove(Object key);
  
  //将map设置到map中去
  void putAll(Map<? extends K, ? extends V> m);

  //清空map
  void clear();

  //获取对应的键的set
  Set<K> keySet();

  //获取对应值的set
  Collection<V> values();
	
  //获取对应的键值对的set
  Set<Map.Entry<K, V>> entrySet();

  //Map中的Entry
  interface Entry<K,V> {
    
    K getKey();
  
    V getValue();
    
    V setValue(V value);
    
    boolean equals(Object o);
    
    int hashCode();
  }

}

Entry的结构

对于Map中存储的是Entry,所以我们要看下Entry的结构,具体的代码如下:

static class Entry<K,V> implements Map.Entry<K,V> {
  
  //键
  final K key;
  
  //值
  V value;
  
  //键值对
  Entry<K,V> next;
  
  //hash值
  int hash;
  
  //构造函数
  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;
  }

  //设置值
  public final V setValue(V newValue) {
    V oldValue = value;
    value = newValue;
    return oldValue;
  }

  //比较两个Entry是否相等
  public final boolean equals(Object o) {
    if (!(o instanceof Map.Entry))
      return false;
    Map.Entry e = (Map.Entry)o;
    Object k1 = getKey();
    Object k2 = e.getKey();
    if (k1 == k2 || (k1 != null && k1.equals(k2))) {
      Object v1 = getValue();
      Object v2 = e.getValue();
      if (v1 == v2 || (v1 != null && v1.equals(v2)))
        return true;
    }
    return false;
  }

  //计算hashcode
  public final int hashCode() {
    return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
  }

  
  public final String toString() {
    return getKey() + "=" + getValue();
  }

}

初始化方法

上面差不多就是Map中的每一个Entry的差不多存的就是一个键值对,然后我们先来看下对应的HashMap的构造方法,对应的源码如下:

//initialCapacity = 16
//loadFactor = 0.75
//threshold = initialCapacity
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();
}

上面的代码主要赋值了三个值,一个initialCapacity初始化的值(16),loadFactor负载因子(0.75),threshold的值等于16。

put方法

这个时候我们再来看下put的方法,具体的代码如下:

public V put(K key, V value) {
  //第一次的时候一定是成立
  if (table == EMPTY_TABLE) {
    //threshold = 16
    inflateTable(threshold);
  }
  //如果key的值为空,直接put一个空的Key
  if (key == null)
    return putForNullKey(value);
  int hash = hash(key);
  int i = indexFor(hash, table.length);
  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;
      e.recordAccess(this);
      return oldValue;
    }
  }

  modCount++;
  addEntry(hash, key, value, i);
  return null;
}

//初始化数组了
private void inflateTable(int toSize) {
  //15->16 四舍五入容量成为2次方幂
  int capacity = roundUpToPowerOf2(toSize);
  
  //容量*0.75
  threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
  //创建指定容量的Entry(这儿的容量是四舍五入的2次方幂)
  table = new Entry[capacity];
  initHashSeedAsNeeded(capacity);
}

final boolean initHashSeedAsNeeded(int capacity) {
  //默认的情况下 currentAltHashing 是false
  boolean currentAltHashing = hashSeed != 0;
  //第一项判断jvm是否启动了 true
  //capacity=16第一次的情况下,
  //Holder.ALTERNATIVE_HASHING_THRESHOLD是Integer的最大值,如果传了值就是指定的值
  //第二项判断是false
  //useAltHashing就是false
  boolean useAltHashing = sun.misc.VM.isBooted() &&
    (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
  //异或成为false
  boolean switching = currentAltHashing ^ useAltHashing;
  //不进这个循环
  if (switching) {
    hashSeed = useAltHashing
      ? sun.misc.Hashing.randomHashSeed(this)
      : 0;
  }
  return switching;
}

上面的代码先初始化Entry数组的类,然后设置threshold的值,是为初始化的值*0.75,然后初始化指定容量的Entry,然后设置hashSeed的值,默认的情况的下0,一般的情况下是不会改变,可以具体看下我上面的注释。然后如果key为空,这个时候会调用putForNullKey(value);方法,具体的代码如下:

//设置默认的空键的值
private V putForNullKey(V value) {
  //遍历所有的Entry,设置好对应的Entry
  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++;
  //第一次为空的情况下,添加对应Entry
  addEntry(0, null, value, 0);
  return null;
}

上面的代码就是遍历所有的table的数组,然后设置table的值,一般的情况下,第一次进来的时候是空的,然后就会调用addEntry(0, null, value, 0);具体的代码如下:

// 0 null value 0
void addEntry(int hash, K key, V value, int bucketIndex) {
  //长度大于初始化长度*0.75,同时这项不等于null
  //会调用扩容 这个时候不会进入
  if ((size >= threshold) && (null != table[bucketIndex])) {
    resize(2 * table.length);
    hash = (null != key) ? hash(key) : 0;
    bucketIndex = indexFor(hash, table.length);
  }
  //创建Entry的方法
  createEntry(hash, key, value, bucketIndex);
}

上面的代码的是先判断是否需要扩容,扩容的条件:**长度大于初始化长度*0.75,同时插入的下标在数组不为null。**然后创建Entry,具体的代码如下:

//创建Entry
void createEntry(int hash, K key, V value, int bucketIndex) {
  Entry<K,V> e = table[bucketIndex];
  table[bucketIndex] = new Entry<>(hash, key, value, e);
  size++;
}

创建好的Entry,然后把size的值++,具体的我们来看下扩容的机制,具体的代码如下:

//扩容的长度是原来的长度*2
void resize(int newCapacity) {
  //先将原来的值赋值给oldTable
  Entry[] oldTable = table;
  //老的长度
  int oldCapacity = oldTable.length;
  //如果的长度等于最大的长度,就直接用最新的
  if (oldCapacity == MAXIMUM_CAPACITY) {
    threshold = Integer.MAX_VALUE;
    return;
  }

  //根据新的长度创建好的newTable
  Entry[] newTable = new Entry[newCapacity];
  
  //initHashSeedAsNeeded(newCapacity)这个返回值为false
  //转移
  transfer(newTable, initHashSeedAsNeeded(newCapacity));
  table = newTable;
  //计算容量*负载因子
  threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}

上面的代码就是扩容的容量是原来的2倍,然后主要是调用转移的方法transfer(newTable, initHashSeedAsNeeded(newCapacity));,具体的代码如下:

//rehash = false
void transfer(Entry[] newTable, boolean rehash) {
  int newCapacity = newTable.length;
  //遍历原来的数组table
  for (Entry<K,V> e : table) {
    //遍历数组下面的链表
    while(null != e) {
      Entry<K,V> next = e.next;
      //不需要重新rehash
      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;
    }
  }
}

这块的扩容和转移的代码后面我会画一个流程的图,给你们看看,这个时候我继续看put方法的后面的代码,具体的如下:

public V put(K key, V value) {
  //第一次的时候一定是成立
  if (table == EMPTY_TABLE) {
    //threshold = 16
    inflateTable(threshold);
  }
  //如果key的值为空,直接put一个空的Key
  if (key == null)
    return putForNullKey(value);
  //根据key 计算hash的值
  int hash = hash(key);
  //计算出来位置
  int i = indexFor(hash, table.length);
  //遍历所有这个位置的链表,找到相同的值,然后替换掉。
  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;
      e.recordAccess(this);
      return oldValue;
    }
    
    //记录操作次数
    modCount++;
    //添加Entry,和刚才的putForNullKey(value);一样
    addEntry(hash, key, value, i);
    return null;
  }

讲到这个时候,put的方法差不多讲完了,差不多的逻辑,就是判断是否是空的,然后如果是空的,初始化这个table,如果这个键是null,然后就put空的null的键,这个时候也是插入数据,先判断需不需要扩容,如果需要扩容,就先扩容,然后再添加。再然后是计算hash的值,以及计算出来的位置,然后遍历这个数组的位置的链表,找到相同的替换,如果没有,直接添加这个Entry,这个时候也是判断是不是需要扩容,如果需要扩容,就先扩容,如果再添加。这儿后面会画个流程图。

get方法

public V get(Object key) {
  if (key == null)
    return getForNullKey();
  Entry<K,V> entry = getEntry(key);
  return null == entry ? null : entry.getValue();
}

如果这个key为空的话,直接调用getForNullKey()方法,具体的代码如下:

private V getForNullKey() {
  //如果长度为0,那么就直接返回null
  if (size == 0) {
    return null;
  }
  //遍历数组0号下标的链表,找的键为null的值,直接返回对应的值
  for (Entry<K,V> e = table[0]; e != null; e = e.next) {
    if (e.key == null)
      return e.value;
  }
  return null;
}

如果长度为0,然后直接返回null,然后遍历数组的0号下标的链表,然后如果找到对应的key为空,就直接返回这个值。继续看getEntry(key)的方法,具体的代码如下:

final Entry<K,V> getEntry(Object key) {
  //size的长度为空,直接返回null
  if (size == 0) {
    return null;
  }

  //计算hash的值
  int hash = (key == null) ? 0 : hash(key);
  //遍历这个位置的链表,找到就直接返回这个entry
  for (Entry<K,V> e = table[indexFor(hash, table.length)];
       e != null;
       e = e.next) {
    Object k;
    f (e.hash == hash &&
       ((k = e.key) == key || (key != null && key.equals(k))))
      return e;
  }
  return null;
}

get的方法还是比较简单,具体的流程可以看后面的图。

remove方法

具体的代码如下:

public V remove(Object key) {
  Entry<K,V> e = removeEntryForKey(key);
  return (e == null ? null : e.value);
}

主要调用的是removeEntryForKey(key)方法,具体的代码如下:

final Entry<K,V> removeEntryForKey(Object key) {
  //如果size的长度等于0,直接返回null
  if (size == 0) {
    return null;
  }
  //计算好hash的值
  int hash = (key == null) ? 0 : hash(key);
  //根据这个hash计算出位置
  int i = indexFor(hash, table.length);
  //指向头结点
  Entry<K,V> prev = table[i];
  //e表示头结点
  Entry<K,V> e = prev;

  //遍历对应的链表,找到相等的key,然后删除
  while (e != null) {
    Entry<K,V> next = e.next;
    Object k;
    if (e.hash == hash &&
        ((k = e.key) == key || (key != null && key.equals(k)))) {
      modCount++;
      size--;
      if (prev == e)
        table[i] = next;
      else
        prev.next = next;
      e.recordRemoval(this);
      return e;
    }
    prev = e;
    e = next;
  }

  return e;
}

上面的代码就是大概的删除的流程,后面会画一个具体的流程图,对熟悉链表的操作的一些小伙伴,这部分的代码应该是比较简单。我这儿就不细说了,后面直接上流程图。

其他的方法就暂时不介绍了,因为其他的方法都是依赖这几个方法,也就是维护几个变量。

3.具体的流程图

put的流程

前面常用的方法就直接介绍完了,大概的源码已经介绍完了,具体的流程的如下,我们先来看下具体的流程图,put的流程图如下:

在这里插入图片描述

看完了put的流程,我们现在看看get的流程。

get的流程

在这里插入图片描述

我们还需要看下remove的流程,具体的如下

remove的流程

在这里插入图片描述

还有其他的链表的操作的方法,我这儿就不做过多的介绍了,因为这是数据结构和算法方面的知识,记住1.7的HashMap的源码的链表是头插法的方式。

4.写在最后

本篇博客大概的介绍了下HashMap(1.7)的源码,后面会介绍HashMap(1.8)的源码,至于头插法和尾插法操作链表的算法,后面的博客会介绍。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值