Java集合框架——HashMap与Hashtable

来源:互联网 发布:文本相似性算法 编辑:程序博客网 时间:2024/05/08 23:20

Java集合框架——HashMap与Hashtable 



先来看看两个类的重点方法,再来比较。(这是我看源代码的思路,你也先去看看吧) 

集合最主要的操作:写入,读取,移除。 

Java代码  收藏代码
  1. /** 
  2.  * Associates the specified value with the specified key in this map. 
  3.  * If the map previously contained a mapping for the key, the old 
  4.  * value is replaced. 
  5.  *  接合指定的value与指定的key在这个map中,假如以前在这个map中包含这个key,以前   
  6.  *  对应的value将会被替换 
  7.  * @param key key with which the specified value is to be associated 
  8.  * @param value value to be associated with the specified key 
  9.  * @return the previous value associated with <tt>key</tt>, or 
  10.  *         <tt>null</tt> if there was no mapping for <tt>key</tt>. 
  11.  *         (A <tt>null</tt> return can also indicate that the map 
  12.  *         previously associated <tt>null</tt> with <tt>key</tt>.) 
  13.  */  
  14. public V put(K key, V value) {  
  15.     if (key == null)  
  16.         return putForNullKey(value);  
  17.     int hash = hash(key.hashCode());  
  18.     int i = indexFor(hash, table.length);  
  19.     for (Entry<K,V> e = table[i]; e != null; e = e.next) {  
  20.         Object k;  
  21.         if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  
  22.             V oldValue = e.value;  
  23.             e.value = value;  
  24.             e.recordAccess(this);  
  25.             return oldValue;  
  26.         }  
  27.     }  
  28.   
  29.     modCount++;  
  30.     addEntry(hash, key, value, i);  
  31.     return null;  
  32. }  

读源代码时,记得把上面的注释读一下,这是最快的理解方法的方式,不要一看源代码,就想着那几个for。

 

里面有几个关键点:

1:int hash = hash(key.hashCode());
2:int i = indexFor(hash, table.length);
3:addEntry(hash, key, value, i);

先说第1点:

Java代码  收藏代码
  1. /** 
  2.      * Applies a supplemental hash function to a given hashCode, which 
  3.      * defends against poor quality hash functions.  This is critical 
  4.      * because HashMap uses power-of-two length hash tables, that 
  5.      * otherwise encounter collisions for hashCodes that do not differ 
  6.      * in lower bits. Note: Null keys always map to hash 0, thus index 0. 
  7.      */  
  8.     static int hash(int h) {  
  9.         // This function ensures that hashCodes that differ only by  
  10.         // constant multiples at each bit position have a bounded  
  11.         // number of collisions (approximately 8 at default load factor).  
  12.         h ^= (h >>> 20) ^ (h >>> 12);  
  13.         return h ^ (h >>> 7) ^ (h >>> 4);  
  14.     }  

 

这个是HashMap的哈唏值的算法:(正好之前看过一篇博文,给出分析图,论坛地址是:http://www.iteye.com/topic/709945


画的很好,所以就引用一下啊,至于分析的话,还是看原地址中的吧。(我说过,让你与我一起来这个过程,所以自己去看)。

 

第2点:

    /**
     * Returns index for hash code h.
     */
    static int indexFor(int h, int length) {
        return h & (length-1);
    }

 

这么少?那分析什么啊。这里真看不出来有什么好分析的,可是我运气比较好,看过一篇博文。这个地址忘记了,我就把内容写一下吧。

 

这里的关键点在于length的长度。

它是Entry[] table的长度,这个与HashMap的初始化时指定的。

先看一下HashMap的三个构造函数:

HashMap(int initialCapacity, float loadFactor)

HashMap(int initialCapacity)

HashMap()

 

从最后一个说起:

public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
        table = new Entry[DEFAULT_INITIAL_CAPACITY];
        init();
    }

 

其中:DEFAULT_LOAD_FACTOR=0.75;DEFAULT_INITIAL_CAPACITY=16;

这是默认值,平时用的是不是这个呢?(今天看了之后,以后可以不用这个了,因为你要知道它的用意。)

第二个就不说了,直接第一个构造函数吧。(记得看一下注释)

Java代码  收藏代码
  1. /** 
  2.      * Constructs an empty <tt>HashMap</tt> with the specified initial 
  3.      * capacity and load factor. 
  4.      * 
  5.      * @param  initialCapacity the initial capacity 
  6.      * @param  loadFactor      the load factor 
  7.      * @throws IllegalArgumentException if the initial capacity is negative 
  8.      *         or the load factor is nonpositive 
  9.      */  
  10.     public HashMap(int initialCapacity, float loadFactor) {  
  11.         if (initialCapacity < 0)  
  12.             throw new IllegalArgumentException("Illegal initial capacity: " +  
  13.                                                initialCapacity);  
  14.   
  15.         //如果你给出的容量大小大于HashMap支持的最大值时,取最大值  
  16.         if (initialCapacity > MAXIMUM_CAPACITY)  
  17.             initialCapacity = MAXIMUM_CAPACITY;  
  18.         if (loadFactor <= 0 || Float.isNaN(loadFactor))  
  19.             throw new IllegalArgumentException("Illegal load factor: " +  
  20.                                                loadFactor);  
  21.   
  22.         //下面的是关键:获取大于或等于的initialCapacity值的最小的2的n次方。  
  23.         // Find a power of 2 >= initialCapacity  
  24.         int capacity = 1;  
  25.         while (capacity < initialCapacity)  
  26.             capacity <<= 1;  
  27.   
  28.         this.loadFactor = loadFactor;  
  29.         threshold = (int)(capacity * loadFactor);  
  30.         table = new Entry[capacity];  
  31.         init();  
  32.     }  

 

看到了吧,这里有个2的n次方。回到先前说的indexFor方法

h&(length-1),这下知道道理了吧,保证在数组之内循环。(只能设计者太邪恶了,因为你只有把代码全部看懂才知道这个用处,好在分享者众多。)

 

再回到上面说的第3重点:addEntry(hash, key, value, i);

Java代码  收藏代码
  1.    /** 
  2.     * Adds a new entry with the specified key, value and hash code to 
  3.     * the specified bucket.  It is the responsibility of this 
  4.     * method to resize the table if appropriate. 
  5.     * 
  6.     * Subclass overrides this to alter the behavior of put method. 
  7.     */  
  8.    void addEntry(int hash, K key, V value, int bucketIndex) {  
  9. Entry<K,V> e = table[bucketIndex];  
  10.        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);  
  11.        if (size++ >= threshold)  
  12.            resize(2 * table.length);  
  13.    }  

 这里真没什么好说的,为了分析与Hashtable的不同,看看这个resize(2 * table.length);再到这个方法中的transfer(newTable);(其实这个在Hashtable与这没什么不同,引大家来是想学一下人家的设计思想,都是两个循环,因为HashMap可以有key为null的值,所以设计的时候,就得考虑进去。至于效率方面,两个没什么差别,但是可以看出在扩大容量时,是一个很耗时的工作。认清这点,虽然比较Hashtable没什么用,但是你在理解HashMap有用,与别的集合比较也有用,难道不是吗?)

Java代码  收藏代码
  1. /** 
  2.      * Transfers all entries from current table to newTable. 
  3.      */  
  4.     void transfer(Entry[] newTable) {  
  5.         Entry[] src = table;  
  6.         int newCapacity = newTable.length;  
  7.         for (int j = 0; j < src.length; j++) {  
  8.             Entry<K,V> e = src[j];  
  9.             if (e != null) {  
  10.                 src[j] = null;  
  11.                 do {  
  12.                     Entry<K,V> next = e.next;  
  13.                     int i = indexFor(e.hash, newCapacity);  
  14.                     e.next = newTable[i];  
  15.                     newTable[i] = e;  
  16.                     e = next;  
  17.                 } while (e != null);  
  18.             }  
  19.         }  
  20.     }  

 
到此put方法就结束了。(看客还有什么好的要说的,就回复吧,因为我分享的同时,也希望能再深入点。)

 

 

 

第二大重点,读取

Java代码  收藏代码
  1. /** 
  2.  * Returns the value to which the specified key is mapped, 
  3.  * or {@code null} if this map contains no mapping for the key. 
  4.  * 
  5.  * <p>More formally, if this map contains a mapping from a key 
  6.  * {@code k} to a value {@code v} such that {@code (key==null ? k==null : 
  7.  * key.equals(k))}, then this method returns {@code v}; otherwise 
  8.  * it returns {@code null}.  (There can be at most one such mapping.) 
  9.  * 
  10.  * <p>A return value of {@code null} does not <i>necessarily</i> 
  11.  * indicate that the map contains no mapping for the key; it's also 
  12.  * possible that the map explicitly maps the key to {@code null}. 
  13.  * The {@link #containsKey containsKey} operation may be used to 
  14.  * distinguish these two cases. 
  15.  * 
  16.  * @see #put(Object, Object) 
  17.  */  
  18. public V get(Object key) {  
  19.     if (key == null)  
  20.         return getForNullKey();  
  21.     int hash = hash(key.hashCode());  
  22.     for (Entry<K,V> e = table[indexFor(hash, table.length)];  
  23.          e != null;  
  24.          e = e.next) {  
  25.         Object k;  
  26.         if (e.hash == hash && ((k = e.key) == key || key.equals(k)))  
  27.             return e.value;  
  28.     }  
  29.     return null;  
  30. }  

 

代码本身没什么好说的,这里来看看构造函数对它的影响。

如果每个key的hash值都是不一样的,那就很快;反之,如果多个key的hash值一样,而table的深度就更深,获取的速度就有可能遍历到最后。

      如果开始就知道 HashMap 会保存多个 key-value 对,可以在创建时就使用较大的初始化容量,如果 HashMap 中 Entry 的数量一直不会超过极限容量(capacity * load factor),HashMap 就无需调用 resize() 方法重新分配 table 数组,从而保证较好的性能。当然,开始就将初始容量设置太高可能会浪费空间(系统需要创建一个长度为 capacity 的 Entry 数组),因此创建 HashMap 时初始化容量设置也需要小心对待。 

 

再看移除:remove(Object key),与Hashtable没有什么区别的。

都是要遍历一次。并找到对应的key-value,将其移除。所以要它的时间复杂度o(n)。(等之后看了别的集合再说说。)

 

说完了。到Hashtable了。

构造函数差不多,不说。

不同点:

1,默认值不一样。0.75是一样的,initialCapacity为11。而且没有最大值,最小值是1。与2的30次方=1073741824

为什么没有最大值呢???(等写完这博文再去看看,这是突然想到的。)

 

2,hash的算法:直接用的int hash = key.hashCode();

    而int index = (hash & 0x7FFFFFFF) % tab.length;与HashMap区别不大,因为HashMap支持key为null,而且固定的把第0位给了它,所以通过h&(length-1)把0给去掉。而这里的这个就是直接循环,对于null的支持,总是要点代价的。

 

3,就是刚说的对null的支持不同,Hashtable是不支持的。如果从源代码中,可以看到

if (value == null) {
     throw new NullPointerException();
 }

不支持value为null,而通过

int hash = key.hashCode();可以知道不支持key不能为null。

而HashMap支持有一个key为null,而value为null的不限制。

 

4,就是看看Hashtable在操作方法前都是有个关键字synchronized,不懂的去看我的博客,地址:http://ciding.iteye.com/blog/1300110 (Java多线程及线程池专题

 

5,这个不是重点,也顺带说一下,Hashtable有一个contains(Object value)方法与containsValue(Object value)方法一样。而HashMap只有containsValue(Object value)方法。说这一点的原因是,在有的博文中乱写

写道
HashMap把Hashtable的contains方法去掉了,改成containsvalue和containsKey。因为contains方法容易让人引起误解。 

 只是将两个方法合并而已,引起误解也是因为没有看源代码。

 

先这到了。。。

原创粉丝点击