• 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;
    }
  • 相关阅读:
    FZU 2098 刻苦的小芳(卡特兰数,动态规划)
    卡特兰数总结
    FZU 1064 教授的测试(卡特兰数,递归)
    HDU 4745 Two Rabbits(区间DP,最长非连续回文子串)
    Java 第十一届 蓝桥杯 省模拟赛 正整数的摆动序列
    Java 第十一届 蓝桥杯 省模拟赛 反倍数
    Java 第十一届 蓝桥杯 省模拟赛 反倍数
    Java 第十一届 蓝桥杯 省模拟赛 反倍数
    Java 第十一届 蓝桥杯 省模拟赛 凯撒密码加密
    Java 第十一届 蓝桥杯 省模拟赛 凯撒密码加密
  • 原文地址:https://www.cnblogs.com/forerver-elf/p/8310065.html
Copyright © 2020-2023  润新知