zoukankan      html  css  js  c++  java
  • HashMap和Hashtable区别

    1. 类定义

    这个从源码中可以直接看出来,HashMap 继承自 AbstractMap,而 Hashtabl 继承自 Dictionary。

    public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
    public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable
    

    2. 线程安全性

    Hashtable 在很多方法定义时都会加上 synchronized 关键字,说明 Hashtabl 是线程安全的,而 HashMap 并不能保证线程安全。

    public synchronized int size();
    public synchronized boolean isEmpty();
    public synchronized boolean contains(Object o);
    public synchronized V get(Object key);
    public synchronized V put(K key, V value);
    ...
    

    3. key 和 value 是否允许 null

    在 Hashtable 添加元素源码中,我们可以发现,如果添加元素的 value 为 null 时,会抛出 NullPointerException。在程序内部,有这样一行代码 int hash = key.hashCode ,如果添加的 key 为 null 时,此时也会抛出空指针异常,因此,在 Hashtable 中,是不允许 key 和 value 为 null 的。

    public synchronized V put(K key, V value) {
            // Make sure the value is not null
            if (value == null) {
                throw new NullPointerException();
            }
    
            // Makes sure the key is not already in the hashtable.
            Entry<?,?> tab[] = table;
            int hash = key.hashCode();
            int index = (hash & 0x7FFFFFFF) % tab.length;
            @SuppressWarnings("unchecked")
            Entry<K,V> entry = (Entry<K,V>)tab[index];
            for(; entry != null ; entry = entry.next) {
                if ((entry.hash == hash) && entry.key.equals(key)) {
                    V old = entry.value;
                    entry.value = value;
                    return old;
                }
            }
    
            addEntry(hash, key, value, index);
            return null;
        }
    

    而在 HashMap 的 put 方法中,调用了 putVal 方法(1.8 版本中),该方法需要有一个 int 类型的 hash 值,这个值是利用内部的 hash 方法产生的。从下面的源代码可以看出,当 key 为 null 时,返回的 hash 值为 0,说明在 HashMap 中是允许 key=null 的情况存在的。

    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){
    }
        
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    

    4. 是否提供 contains 方法

    从 HashMap 的 API 可以看出,它只包含 containsKey 和 containsValue 方法。而 Hashtable 还包含了 contains 方法。

    HashMap 中把 contains 方法去掉的原因主要它容易引起混淆,不如 containsKey 和 containsValue 表达的准确。

    而 Hashtable 中 contains 方法也是调用 containsKey 方法来实现的。

    public boolean contains(Object o) {
    	 return containsKey(o);
    }
    

    5. 初始容量

    Hashtable 初始容量为 11,默认的负载因子为 0,.75。HashMap 定义了两个常量在对容器进行初始化会用到,可以看到其初始容量为 16,默认的负载因子也是为 0.75.

    //---------------------Hashtable-----------------------------
    public Hashtable() {
         this(11, 0.75f);
    }
    public Hashtable(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal Load: "+loadFactor);
    
        if (initialCapacity==0)
            initialCapacity = 1;
        this.loadFactor = loadFactor;
        // 这里对桶进行初始化
        table = new Entry<?,?>[initialCapacity];
        threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
    }
    //---------------------HashMap-----------------------------
    /**
     * The default initial capacity - MUST be a power of two.
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
    /**
     * The load factor used when none specified in constructor.
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    

    6. 扩容程度

    Hashtable 扩容时调用 rehash 方法,增加容器容量的代码在下面。从中可以看出,最终的容量是 newCapacity ,如果该变量在没有大于 MAX_ARRAY_SIZE(静态变量,内部定义为 Integer.MAX_VALUE - 8) 之前,都是按照 oldCapacity*2 + 1 的速度增加的。

    int newCapacity = (oldCapacity << 1) + 1;
    if (newCapacity - MAX_ARRAY_SIZE > 0) {
        if (oldCapacity == MAX_ARRAY_SIZE)
            // Keep running with MAX_ARRAY_SIZE buckets
            return;
        newCapacity = MAX_ARRAY_SIZE;
    }
    Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];
    

    在 HashMap 中,扩容主要是通过 resize 方法实现的,其扩容的代码是这样的 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];,可见是跟 newCap 变量有关,在正常情况下,newCapa 是按照 oldCap<<1 的速度,即每次长度变为原来的两倍增长的。

     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; // double threshold
    }
    else if (oldThr > 0) // initial capacity was placed in threshold
        newCap = oldThr;
    else {               // zero initial threshold signifies using defaults
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    

    7. 迭代器

    本段话摘自 http://www.cnblogs.com/alexlo/archive/2013/03/14/2959233.html

    HashMap 的迭代器(Iterator) 是 fail-fast 迭代器,而 Hashtable 的 enumerator 迭代器不是 fail-fast 的。所以当有其它线程改变了 HashMap 的结构(增加或者移除元素),将会抛出 ConcurrentModificationException,但迭代器本身的 remove() 方法移除元素则不会抛出 ConcurrentModificationException 异常。但这并不是一个一定发生的行为,要看 JVM 。这条同样也是 Enumeration 和 Iterator 的区别。关于 fail-fast 机制可以查看这篇文章

    8. hash 算法

    一下下这段分析摘自:https://www.cnblogs.com/xiaoxi/p/7233201.html
    hash 算法是将元素定位到相对应桶的位置上,在 Hashtable 中,是这样实现 hash 算法的。因为 Hashtable 中,其桶扩容之后长度为奇数,这种方式的哈希取模会更加均匀(这点还是不清楚为什么)。

    int hash = key.hashCode();
    // hash 不能超过 Integer.MAX_VALUE,所以要取其最小的 32 个 bit
    int index = (hash & 0x7FFFFFFF) % tab.length;
    

    在 JDK 1.8 版本中,HashMap 的 hash 方法如下。

    static final int hash(Object key){
    	int h;
    	return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    
    final V putValu(int hash,K key,V value,boolean onlyIfAbsent,boolean evict){
    	...
    	if ((p = tab[i = (n - 1) & hash]) == null)
    	    tab[i] = newNode(hash, key, value, null);
    	...
    } 
    
    • 其首先是获取该对象的 hashCode 值,然后将 hashCode 值右移 16 位,然后将右移后的值与原来的 hashCode 做 异或 运算,返回结果。
    • 在 putVal 中,通过 (n-1)&hash 获取该对象的键在 hashmap 中的位置。(其中 hash 就是通过 hash 方法获得的值),n 表示 hash 桶数组的长度,并且该长度为 2 的 n 次方。

    通常声明 map 集合时不会指定大小,或者初始化的时候就创建一个容量很大的map 对象,所以这个通过容量大小与 key 值进行 hash 的算法在开始的时候只会对低位进行计算,虽然容量的 2 进制高位一开始都是 0,但是 key 的 2 进制高位通常是有值的,因此先在 hash 方法中将 key 的 hashCode 右移 16 位在与自身异或,使得高位也可以参与hash,更大程度上减少了碰撞率。

  • 相关阅读:
    在Linux下卸载Oracle 11g
    Android UI学习 Menu
    Android中设置EditText获得焦点时不弹出软键盘
    Android ADB 命令
    Oracle下载地址
    Android开发快捷键
    Android中将字符串文字内容复制到系统剪贴板
    MySQL数据库常用备份方法和注意事项
    详解Android首选项框架ListPreference
    sql连接查询方法
  • 原文地址:https://www.cnblogs.com/firepation/p/9448027.html
Copyright © 2011-2022 走看看