• HashMap


      HashMap本质是一个链表散列的数据结构,即数组和链表的结合体。HashMap底层是一个数组结构,数组中的每一项又是一个链表。当新建一个HashMap的时候,就会初始化一个数组。

    transient Entry[] table;
    static class Entry<K, V> implements Map.Entry<K, V>{
        final K key;
        V value;
        Entry<K, V>next;
        final int hash;
        .....
    }

      put方法

        先根据key算出hash值,然后根据得到的hash值来定位这个元素在数组中的位置,若数组该位置已经存放了其他元素,那么这个位置上的元素将以链表的形式存放,新加入的放在链表头,最先加入的放在链尾,如果数组在该位置没有元素,则将该元素放到数组中。

    public V put(K key, V value){
        if(key == null)
            return putForNullKey(value);
        int hash = hash(key.hashCode());
        int i = indexFor(hash, table.length);
        for(Entry<K, V> e = table[i]; e != null; ){
            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;
    }    

    void addEntry(int hash, K key, V vaule, int bucketIndex){
      Entry<K, V> e = table[bucketIndex];
      table[bucketIndex] = new Entry<K, V>(hash, key, value, e);
      if(size ++ >= threshold)
        resize(2 * table.length);
    }

    static int hash(int h){
      h ^= (h >>> 20) ^ (h >>> 12);
      return h ^ (h >>> 7) ^ (h >>> 4);
    }

    static int indexFor(int h, int length){
      return h & (length - 1);
    }

      get()方法:先根据key算出hash值,然后根据hash值找到对应位置的某一元素,然后通过key的equlas方法在对应位置的链表中找到需要的元素

    public V get(Object key){
        if(key == null)
            return getForNullKey();
        int hash = hash(key.hashCode());
        for(Entry<K, V> e = table[indexFor(hash, table.length)]; e != null; e = e.next){
          Object k;
         if(e.hash == hash && ((k = e.key) == key || key.equals(k)))
           return e.value; }
       return null; }

      当HashMap中的元素个数超过数组大小*loadFactor时,就会对数组进行扩容,扩大一倍后重新计算每个元素在数组中的位置。loadFactor的默认值是0.75。

      loadFactor = 散列表的实际元素数目/散列表的容量。负载因子衡量的是一个散列表的空间的使用程度,负载因子越大表示散列表的装填程度越高。对于使用链表法的散列表来说,查找一个元素的平均时间是O(1+a),若负载因子太大,对空间利用充分,但查找效率较低;若负载因子太小,那么散列表的数据将过于稀疏,对空间进行严重浪费。

      threshold = (int) (capacity * loadFactor);超过threshold会重新resize,以降低实际的负载因子

      java.util.HashMap是不安全的,若在使用迭代器的过程中有其他线程修改了map,则抛出ConcurrentModificationException,这是所谓的fail-fast策略。每次对HashMap内容的修改都将增加modCount域,在迭代器初始化过程中会将这个值赋给迭代器expectedModCount。

    HashIterator(){
        expectedModCount = modCount;
        if(size  > 0) {
            Entry[] t = table;
            while(index <  t.length && (next = t[index++]) == null)
                ;
        }

       final Entry<K, V> nextEntry(){
         if(modCount != expectedModCount)
           throw new ConcurrentModificationException();
       } }

      在所有HashMap类的collection视图方法所返回的迭代器都是fail-fase的:在迭代器创建之后,若从结构上对映射进行修改,除非通过迭代器本身的remove方法,其他任何时间任何方式的修改,迭代器都会抛出ConcurrentModificationException。

      JDK1.8改版HashMap  

        从结构实现上由数组+链表改为数组+链表+红黑树(当链表长度大于8时转换为红黑树)

    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){...}
     
      public final K getKey(){
        return key;
      }
      public final V getValue(){
        return value;
      }
      public final String toString(){
        return key + "=" + value;
      }
      public final int hashCode(){
        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<?, ?> e = (Map.Entry<?, ?>)o;
          if(Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue()))
            return true;
        }
        return false;
      }
    }

      put()方法

    public V put(K key, V value){
       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)
        n = (tab = resize()).length;
      if((p = tab[i = (n -1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
      else{
        Node<K, V> e;
        K k;
        if(p.has == hash && ((k = p.key) == key || (key != null && key.equals(k)))
          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){
              p.next = newNode(hash, key, value, null);
              if(binCount >= TREEIFY_THRESHOLD - 1)
                treeifyBin(tab, hash);
              break;
            }
            if(e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
              break;
            p = e;
          }
        }

        if(e != null){
          V oldValue = e.value;
          if(!onlyIfAbsent || oldValue == null)
            e.value = value;
          afterNodeAccess(e);
          return oldValue;
        }
      }
      
      ++modCount;
      if(++size > threshold)
        resize();
      afterNodeInsertion(evict);
      return null;
    }

       resize():重新计算容量,向HashMap对象里不停的添加元素,而HashMap对象内部的数组无法装载更多的元素时,对象就需要扩容。Java的数组是无法自动扩容的,只好使用一个新的大的数组代替已有的容量小的数组。扩容时使用的是2次幂的扩展,因此元素的位置要么是在原位置,要么是在原位置再移动2次幂的位置。在JDK1.8中就无需重新计算hash,只需要看原来的hash值新增的那个bit是1还是0,是0的话索引没变,是1的话索引编程“原索引 + oldCap”。

    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){
          newThr = oldThr << 1;
        }
      }else if(oldThr > 0){
        newCap = oldThr;
      }else{
        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", "uncheck"})
      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.next == null)
            newTab[e.hash & (newCap -1)] = e;
          else if(e instanceof TreeNode)
            ((TreeNode<K, V>)e).split(this, newTab, j, oldCap);
          else{
            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; }

      get()方法

    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;
      if((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n -1) & hash]) != null){
        if(first.hash == hast && ((k = first.ley) == 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);
        }
      }
      return null;
    }
  • 相关阅读:
    写代码注意了,打死都不要用 User 这个单词
    图解 Java 垃圾回收机制,写得非常好!
    Spring的核心模块解析
    单点登录终极方案之 CAS 应用及原理
    重磅!!Redis 6.0.0 已发布,有史以来改变最大的版本
    linux中$与()的一点使用疑惑解释
    mysql 行锁一则
    mysql: you can't specify target table 问题解决
    mysql update中需要根据条件列更新写法update case
    mysql depended_query 优化案例一则
  • 原文地址:https://www.cnblogs.com/forerver-elf/p/8310065.html
Copyright © 2020-2023  润新知