美文网首页
HashMap和Hashtable

HashMap和Hashtable

作者: only_run | 来源:发表于2018-07-29 12:41 被阅读0次

    HashMap

    介绍

    HashMap由数组+链表组成的;

    HashMap的基础就是一个线性数组,这个数组就是Entry[],Map里面的内容都保存在Entry[]里面。HashMap里面实现一个静态内部类Entry,其重要的属性有key , value, next,从属性key,value我们就能很明显的看出来Entry就是HashMap键值对实现的一个基础实体对象。

    /**

         * The table, resized as necessary. Length MUST Always be a power of two.

     */

       transient Entry[] table;

    HashMap的存取

    HashMap通过 一个算法实现 存取

    // 这个hashCode方法这里不详述,只要理解每个key的hash是一个固定的int值

    int hash = key.hashCode();

    int index = hash % Entry[].length;Entry[index] = value;

    //取值

    int hash = key.hashCode();

    int index = hash % Entry[].length;

    return Entry[index];

    put

    HashMap里面用到链式数据结构的一个概念。上面我们提到过Entry类里面有一个next属性,作用是指向下一个Entry。打个比方, 第一个键值对A进来,通过计算其key的hash得到的index=0,记做:Entry[0] = A。一会后又进来一个键值对B,通过计算其index也等于0,现在怎么办?HashMap会这样做:B.next = A,Entry[0] = B,如果又进来C,index也等于0,那么C.next = B,Entry[0] = C;这样我们发现index=0的地方其实存取了A,B,C三个键值对,他们通过next这个属性链接在一起。所以疑问不用担心。也就是说数组中存储的是最后插入的元素。到这里为止,HashMap的大致实现,我们应该已经清楚了。

    public V put(K key, V value) {       

     if (key == null)    return putForNullKey(value);//null总是放在数组的第一个链表中       

         int hash = hash(key.hashCode());        

        int i = indexFor(hash, table.length);        

    //遍历链表        

    for (Entry e = table[i]; e != null; e = e.next) 

    {         

       Object k;           

     //如果key在链表中已存在,则替换为新value           

             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 value, int bucketIndex) {  

      Entry e = table[bucketIndex];   

         table[bucketIndex] = new Entry(hash, key, value, e); //参数e, 是Entry.next 

           //如果size超过threshold,则扩充table大小。再散列   

         if (size++ >= threshold)            resize(2 * table.length);

    }

    HashMap里面也包含一些优化方面的实现,这里也说一下。比如:Entry[]的长度一定后,随着map里面数据的越来越长,这样同一个index的链就会很长,会不会影响性能?HashMap里面设置一个因子,随着map的size越来越大,Entry[]会以一定的规则加长长度。

    get

    public V get(Object key) {       

         if (key == null)        return getForNullKey();     

           int hash = hash(key.hashCode());      //先定位到数组元素,再遍历该元素处的链表        for (Entry 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;

    }

    null key的存取

    null key总是存放在Entry[]数组的第一个元素。

    private V putForNullKey(V value) {      

      for (Entry 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++;       

     addEntry(0, null, value, 0);       

     return null;   

     }     

    private V getForNullKey() 

    {      

      for (Entry e = table[0]; e != null; e = e.next) {    

            if (e.key == null)          return e.value;   

         }      

      return null;  

      } 

    确定数组index:hashcode % table.length取模

    HashMap存取时,都需要计算当前key应该对应Entry[]数组哪个元素,即计算数组下标;算法如下:

     /**

         * Returns index for hash code h.

         */

      static int indexFor(int h, int length) {

        return h & (length-1);

    }

    按位取并,作用上相当于取模mod或者取余%。这意味着数组下标相同,并不表示hashCode相同。

    table初始大小

    public HashMap(int initialCapacity, float loadFactor) { 

           .....         // Find a power of 2 >= initialCapacity      

      int capacity = 1;       

     while (capacity < initialCapacity)      

          capacity <<= 1;        

     this.loadFactor = loadFactor;     

       threshold = (int)(capacity * loadFactor);       

     table = new Entry[capacity];      

      init(); 

       }

    注意table初始大小并不是构造函数中的initialCapacity!!

    而是 >= initialCapacity的2的n次幂!!!!

    再散列rehash过程

    当哈希表的容量超过默认容量时,必须调整table的大小。当容量已经达到最大可能值时,那么该方法就将容量调整到Integer.MAX_VALUE返回,这时,需要创建一张新表,将原表的映射到新表中。

    /**

         * Rehashes the contents of this map into a new array with a

         * larger capacity.  This method is called automatically when the

         * number of keys in this map reaches its threshold.

         *

         * If current capacity is MAXIMUM_CAPACITY, this method does not

         * resize the map, but sets threshold to Integer.MAX_VALUE.

         * This has the effect of preventing future calls.

         *

         * @param newCapacity the new capacity, MUST be a power of two;

         *        must be greater than current capacity unless current

         *        capacity is MAXIMUM_CAPACITY (in which case value

         *        is irrelevant).

         */

        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];

            transfer(newTable);

            table = newTable;

            threshold = (int)(newCapacity * loadFactor);

        }

        /**

         * Transfers all entries from current table to newTable.

         */

        void transfer(Entry[] newTable) {

            Entry[] src = table;

            int newCapacity = newTable.length;

            for (int j = 0; j < src.length; j++) {

                Entry e = src[j];

                if (e != null) {

                    src[j] = null;

                    do {

                        Entry next = e.next;

                        //重新计算index

                        int i = indexFor(e.hash, newCapacity);

                        e.next = newTable[i];

                        newTable[i] = e;

                        e = next;

                    } while (e != null);

                }

            }

        }

    Hashtable 与 HashMap 的简单比较

    HashTable 基于 Dictionary 类,而 HashMap 是基于 AbstractMap。Dictionary

    是任何可将键映射到相应值的类的抽象父类,而 AbstractMap 是基于 Map 接口的实现,它以最大限度地减少实现此接口所需的工作。

    2

    HashMap 的 key 和 value 都允许为 null,而 Hashtable 的 key 和 value 都不允许为

    null。HashMap 遇到 key 为 null 的时候,调用 putForNullKey 方法进行处理,而对 value

    没有处理;Hashtable遇到 null,直接返回 NullPointerException。

    3

    Hashtable 方法是同步,而HashMap则不是。我们可以看一下源码,Hashtable 中的几乎所有的 public 的方法都是

    synchronized 的,而有些方法也是在内部通过 synchronized 代码块来实现。所以有人一般都建议如果是涉及到多线程同步时采用

    HashTable,没有涉及就采用 HashMap,但是在 Collections

    类中存在一个静态方法:synchronizedMap(),该方法创建了一个线程安全的 Map 对象,并把它作为一个封装的对象来返回。

    参考:参考1     参考2

    相关文章

      网友评论

          本文标题:HashMap和Hashtable

          本文链接:https://www.haomeiwen.com/subject/kbuzmftx.html