HashMap的get方法与put方法

前言:HashMap的源码先前是看过了的(我自以为),直到昨天贝壳面试官让我详细讲讲HashMap的get和put方法我才知道,这知识,他不进脑子啊,于是又看了一遍,很多细节的地方还是没能有耐心去细读,但大概流程还是走了一遍

1.注释

允许keyvalue为null

和HashTable的区别在于非线程安全和允许null

所以需要安全的HashMap有三种选择:

  1. Collections.synchronizedMap(map)
  2. HashTable
  3. ConcurrentHashMap

默认装载因子(0.75)在空间和时间开销上提供了一个很好的平衡

使用分布良好的hash函数时,红黑树的结构是很难出现的;当使用随机分布的hash函数时,红黑树出现的频率服从泊松分布,当装载因子为0.75,树化阈值为8时,概率为6*10^-8^


2.底层结构

Node数组

  transient Node<K,V>[] table;

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

      Node(int hash, K key, V value, Node<K,V> next) {
          this.hash = hash;
          this.key = key;
          this.value = value;
          this.next = next;
      }

      public final K getKey()        { return key; }
      public final V getValue()      { return value; }
      public final String toString() { return key + "=" + value; }

      public final int hashCode() {
          //会需要用到整个Node的hashCode吗?
          //put,get是没用到的
          return Objects.hashCode(key) ^ Objects.hashCode(value);
      }

      public final V setValue(V newValue) {
          V oldValue = value;
          value = newValue;
          return oldValue;
      }

      public final boolean equals(Object o) {
          if (o == this)
              return true;
          if (o instanceof Map.Entry) {
              //Map.Entry<?,?>是一个接口,所以这里应该是多态的表现了,但是接口还能这么. 的?
              Map.Entry<?,?> e = (Map.Entry<?,?>)o;
              if (Objects.equals(key, e.getKey()) &&
                  Objects.equals(value, e.getValue()))
                  return true;
          }
          return false;
      }
  }

可以看到Node有一个next属性,表述一下就是:

数组实现,但是数组中存放的Node除了key,value之外,还有一个next属性,可以作为链表的头结点


3.常量

  static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

  static final float DEFAULT_LOAD_FACTOR = 0.75f;

  static final int MAXIMUM_CAPACITY = 1 << 30;

  static final int MIN_TREEIFY_CAPACITY = 64;

  static final int TREEIFY_THRESHOLD = 8;

  static final int UNTREEIFY_THRESHOLD = 6;

4.构造方法

  //无参
  //final float loadFactor;
  //其余使用默认值,等下分析put方法时就可以看到是怎样使用默认值的了
  //不过为什么无参的一定要赋值默认装载因子呢?
  public HashMap() {
      this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
  }

  //调用public HashMap(int initialCapacity, float loadFactor)
  public HashMap(int initialCapacity) {
      this(initialCapacity, DEFAULT_LOAD_FACTOR);
  }

  //三个if语句
  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;
      //tableSizeFor返回比当前initialCapacity大的最小的二次幂
      //但是为什么赋值给了threshold,这个threshold后面起的也是初始容量的作用啊?
      //可能是为了和threshold为0的情况区分开来
      this.threshold = tableSizeFor(initialCapacity);
  }

  //输入一个Map,通过遍历转为HashMap
  public HashMap(Map<? extends K, ? extends V> m) {
      this.loadFactor = DEFAULT_LOAD_FACTOR;
      putMapEntries(m, false);
  }

四个构造函数,无参,<int>,<int,float>,<Map>


5.put

  //实际使用过程中我们使用的方法
  //可以看到帮我们封装了一层,调用了putVal
  public V put(K key, V value) {
      //这里的hash也是对Object.hashCode()的封装,返回的是hashCode的低16位 ^ 高16位
      //因为当table数组的长度很小时,高16位是不参与运算的;因为数组索引是通过&运算获取的
      return putVal(hash(key), key, value, false, true);
  }

  final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                     boolean evict) {
      Node<K,V>[] tab; Node<K,V> p; int n, i;
      if ((tab = table) == null || (n = tab.length) == 0)
          //如果table数组未空,就先去resize()初始化
          n = (tab = resize()).length;
      if ((p = tab[i = (n - 1) & hash]) == null)
          //当前索引位置是空的,newNode就完事了,null对应的是next
          tab[i] = newNode(hash, key, value, null);
      else {
          Node<K,V> e; K k;
          if (p.hash == hash &&
              ((k = p.key) == key || (key != null && key.equals(k))))
              //e最终记下的都是key相同的那个结点
              e = p;
          else if (p instanceof TreeNode)
              //当前是红黑树,向红黑树中插入值
              //算法基础好的话可以考虑看红黑树怎么插入的,我就算了吧
              e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
          else {
              //当前是链表,遍历链表
              for (int binCount = 0; ; ++binCount) {
                  if ((e = p.next) == null) {
                      //尾插法,遍历完了还没有key相同的
                      p.next = newNode(hash, key, value, null);
                      //插入之后检查是否需要变成红黑树
                      if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                          treeifyBin(tab, hash);
                      break;
                  }
                  if (e.hash == hash &&
                      ((k = e.key) == key || (key != null && key.equals(k))))
                      break;
                  p = e;
              }
          }
          if (e != null) { // existing mapping for key
              V oldValue = e.value;
              if (!onlyIfAbsent || oldValue == null)
                  e.value = value;
              afterNodeAccess(e);
              //如果是更新旧值,就没有下面的判断扩容操作了
              return oldValue;
          }
      }
      ++modCount;
      if (++size > threshold)
          resize();
      afterNodeInsertion(evict);
      return null;
  }
  final Node<K,V>[] resize() {
      Node<K,V>[] oldTab = table;
      int oldCap = (oldTab == null) ? 0 : oldTab.length;
      int oldThr = threshold;
      int newCap, newThr = 0;
      if (oldCap > 0) {
          if (oldCap >= MAXIMUM_CAPACITY) {
              threshold = Integer.MAX_VALUE;
              return oldTab;
          }
          else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                   oldCap >= DEFAULT_INITIAL_CAPACITY)
              //对于已初始化的table数组,扩容到2倍
              newThr = oldThr << 1; // double threshold
      }
      else if (oldThr > 0) // initial capacity was placed in threshold
          //只有<int,float>构造方***初始化threshold的值
          newCap = oldThr;
      else {               // zero initial threshold signifies using defaults
          //这是未赋初始容量的情况,使用默认
          newCap = DEFAULT_INITIAL_CAPACITY;
          newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
      }
      if (newThr == 0) {
          float ft = (float)newCap * loadFactor;
          newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                    (int)ft : Integer.MAX_VALUE);
      }
      threshold = newThr;
      @SuppressWarnings({"rawtypes","unchecked"})
      Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
      table = newTab;
      if (oldTab != null) {
          for (int j = 0; j < oldCap; ++j) {
              //遍历赋值
              Node<K,V> e;
              if ((e = oldTab[j]) != null) {
                  oldTab[j] = null;
                  if (e.next == null)
                      newTab[e.hash & (newCap - 1)] = e;
                  else if (e instanceof TreeNode)
                      ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                  else { // preserve order
                      Node<K,V> loHead = null, loTail = null;
                      Node<K,V> hiHead = null, hiTail = null;
                      Node<K,V> next;
                      do {
                          next = e.next;
                          if ((e.hash & oldCap) == 0) {
                              if (loTail == null)
                                  loHead = e;
                              else
                                  loTail.next = e;
                              loTail = e;
                          }
                          else {
                              if (hiTail == null)
                                  hiHead = e;
                              else
                                  hiTail.next = e;
                              hiTail = e;
                          }
                      } while ((e = next) != null);
                      if (loTail != null) {
                          loTail.next = null;
                          newTab[j] = loHead;
                      }
                      if (hiTail != null) {
                          hiTail.next = null;
                          newTab[j + oldCap] = hiHead;
                      }
                  }
              }
          }
      }
      return newTab;
  }

表述:常用的put(k,v)API中封装了一层putVal(hash,k,v,onlyIfAbsent,evict),在插入之前检查table数组是否已经初始化(resize),已经初始化再通过hash值&数组长度计算出索引,如果当前结点为null,直接插入,否则判断结构是红黑树还是链表,遍历决定是更新值还是尾插法插入(未找到key相同的结点)


6.get

  //get方法也封装了一层getNode
  public V get(Object key) {
      Node<K,V> e;
      return (e = getNode(hash(key), key)) == null ? null : e.value;
  }

  final Node<K,V> getNode(int hash, Object key) {
      Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
      if ((tab = table) != null && (n = tab.length) > 0 &&
          (first = tab[(n - 1) & hash]) != null) {
          if (first.hash == hash && // always check first node
              ((k = first.key) == key || (key != null && key.equals(k))))
              return first;
          if ((e = first.next) != null) {
              if (first instanceof TreeNode)
                  return ((TreeNode<K,V>)first).getTreeNode(hash, key);
              do {
                  if (e.hash == hash &&
                      ((k = e.key) == key || (key != null && key.equals(k))))
                      return e;
              } while ((e = e.next) != null);
          }
      }
      //table未初始化直接返回null
      return null;
  }

表述:常用的get(k,v)也封装了一层get(hash,k),首先判断数组是否初始化,如果数组为空直接返回null,不为空则分情况遍历红黑树或者链表(头结点就是当然皆大欢喜),写到这里我想起来,昨天表述的时候至少讲一下hashCode和equals的关系吧,答得还是太烂了


全部评论

相关推荐

1 收藏 评论
分享
牛客网
牛客企业服务