从Java源码的角度来分析HashMap与HashTable的区别

由于HashMap与HashTable都是用来存储Key-Value的键值对,所以经常拿来对比二者的区别,下面就从源码的角度来分析一下HashMap与HashTable的区别,

首先介绍一下两者的区别,然后再从源码分析。

HahMap与HahTable两者主要区别:

1、继承的父类不同

<span style="font-size:18px;">public class HashMap<K, V> extends AbstractMap<K, V> implements Cloneable, Serializable</span>
<span style="font-size:18px;"><pre name="code" class="java">public class Hashtable<K, V> extends Dictionary<K, V> implements Map<K, V>, Cloneable, Serializable</span>

HashTable是一个古老的Map实现类,从JDK1.0就开始出现,继承自Dictionary,当时JDK还有没有出现Map这个接口;HashMap继承自AbstractMap这个抽象类,而AbstractMap这个抽象类实现了Map接口。


2、HashTable中的方法是同步的,在多线程程序中无需程序员自己去实现同步处理;而HashMap中的方法是非同步的,需要程序员自己去实现同步处理,常用的方法是使用Collections工具类中的synchronizedXxx()方法吧集合类包装成线程同步的集合。

        3、HashTable中Key与Value都不允许出现NULL值,而HashMap中Key可以为NULL,由于HashMap是Map中实现类,Map集合中要求每个Key唯一,所以HashMap中之多只能出现一个Key为NULL的情况,但HashMap中可以出现多个Value为NULL的情况。

        4、遍历方式不同

        Hashtable、HashMap都使用了 Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式

然后接下来看看HashMap的源码。

HashMap中一些属性Field如下:

<span style="font-size:18px;">    /**
     * Min capacity (other than zero) for a HashMap. Must be a power of two
     * greater than 1 (and less than 1 << 30).
     * HashMap的最小容量
     */
    private static final int MINIMUM_CAPACITY = 4;

    /**
     * Max capacity for a HashMap. Must be a power of two >= MINIMUM_CAPACITY.
     * HashMap的最大容量
     */
    private static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * An empty table shared by all zero-capacity maps (typically from default
     * constructor). It is never written to, and replaced on first put. Its size
     * is set to half the minimum, so that the first resize will create a
     * minimum-sized table.
     *
     * HashMapEntry数组,HashMapEntry条目数组,HashMapEntry数组大小为MINIMUM_CAPACITY/2
     */
    private static final Entry[] EMPTY_TABLE
            = new HashMapEntry[MINIMUM_CAPACITY >>> 1];

    /**
     * The default load factor. Note that this implementation ignores the
     * load factor, but cannot do away with it entirely because it's
     * mentioned in the API.
     *
     * <p>Note that this constant has no impact on the behavior of the program,
     * but it is emitted as part of the serialized form. The load factor of
     * .75 is hardwired into the program, which uses cheap shifts in place of
     * expensive division.
     * 默认的负载因子
     */
    static final float DEFAULT_LOAD_FACTOR = .75F;

    /**
     * The hash table. If this hash map contains a mapping for null, it is
     * not represented this hash table.
     * HashMap容器数组,用于存放HashMapEntry对象的数组
     */
    transient HashMapEntry<K, V>[] table;

    /**
     * The entry representing the null key, or null if there's no such mapping.
     * 空键值对应的HashMapEntry条目对象
     */
    transient HashMapEntry<K, V> entryForNullKey;

    /**
     * The number of mappings in this hash map.
     * HashMap中元素的数量(大小)
     */
    transient int size;

    /**
     * Incremented by "structural modifications" to allow (best effort)
     * detection of concurrent modification.
     * 用于确保使用迭代器的时候,HashMap并未进行更改
     */
    transient int modCount;

    /**
     * The table is rehashed when its size exceeds this threshold.
     * The value of this field is generally .75 * capacity, except when
     * the capacity is zero, as described in the EMPTY_TABLE declaration
     * above.
     * 负载因子(阈值)
     */
    private transient int threshold;

    // Views - lazily initialized
    /**由K键组成的Set集合*/
    private transient Set<K> keySet;
    /**由K-Value组成的Set集合,Set集合中存放了Entry<K, V>元素*/
    private transient Set<Entry<K, V>> entrySet;

    private transient Collection<V> values;</span>

HashMap中是用HashMapEntry数组来存储Key-Value键值对的,HashMapEntry是HashMap中一个静态的内部类,实现了java.util.Map.Entry接口。在HashMap的属性Field中定义用于存放Key-Value键值对的HashMapEntry数组长度的默认的最小值和最大值。一个空的HashMapEntry数组EMPTY_TABLE以及HashMap容器数组tab,用于存放HashMapEntry对象的数组。默认的负载因子DEFAULT_LOAD_FACTOR,将其定义为0.75(3/4)。

HashMap的构造函数主要有以下几个:

HashMap()

Constructs a new empty HashMap instance.

HashMap(int capacity)

Constructs a new HashMap instance with the specified capacity.

HashMap(int capacity, float loadFactor)

Constructs a new HashMap instance with the specified capacity and load factor.

HashMap(Map<? extendsK,?
extends V> map)

Constructs a new HashMap instance containing the mappings from the specified map.

我们来看下HashMap(int capacity)这个构造函数的源码

<span style="font-size:18px;">    /**
     * Constructs a new {@code HashMap} instance with the specified capacity.
     *
     * @param capacity
     *            the initial capacity of this hash map.
     * @throws IllegalArgumentException
     *                when the capacity is less than zero.
     *通过一个指定的容量来构造一个HashMap实例对象
     *@param    capacity:HashMap初始化容量
     *
     */
    public HashMap(int capacity) {
        if (capacity < 0) {
            throw new IllegalArgumentException("Capacity: " + capacity);
        }

        if (capacity == 0) {
            @SuppressWarnings("unchecked")
            HashMapEntry<K, V>[] tab = (HashMapEntry<K, V>[]) EMPTY_TABLE;
            table = tab;
            threshold = -1; // Forces first put() to replace EMPTY_TABLE
            return;
        }

        if (capacity < MINIMUM_CAPACITY) {
            capacity = MINIMUM_CAPACITY;
        } else if (capacity > MAXIMUM_CAPACITY) {
            capacity = MAXIMUM_CAPACITY;
        } else {
            capacity = Collections.roundUpToPowerOfTwo(capacity);
        }
        makeTable(capacity);
    }</span>

该函数通过一个指定的容量值capacity来创建一个HashMapEntry数组对象,当capacity等于0时将空的HashMapEntry数组对象EMPTY_TABLE赋值给tab对象;如果capacity不为0时,当capacity小于容量最小值时将capacity调整为容量最小值,同理当capacity大于容量最大值时将capacity调整为容量最大值。最后调用makeTable来初始化创建tab数组。下面是makeTable的具体实现

<span style="font-size:18px;">    /**
     * Allocate a table of the given capacity and set the threshold accordingly.
     * 根据指定容量来分配table数组的大小,并设置负载因子(阈值)
     * @param newCapacity must be a power of two
     *
     */
    private HashMapEntry<K, V>[] makeTable(int newCapacity) {
        @SuppressWarnings("unchecked") HashMapEntry<K, V>[] newTable
                = (HashMapEntry<K, V>[]) new HashMapEntry[newCapacity];
        table = newTable;
        threshold = (newCapacity >> 1) + (newCapacity >> 2); // 3/4 capacity
        return newTable;
    }</span>

可以发现在makeTable中是通过创建一个按指定容量大小新的HashMapEntry数组newTable,并将其newTable数组赋值给HashMap的属性File变量table,因此在HashMap中table数组才是真正的存放HashMapEntry的容器,即存放Key-Value条目的容器。在构造函数中将负载因子threshold设置为容量的3/4,当table数组中存放的HashMapEntry元素个数大于负载因子threshold时table数组将扩容。通过查看java.util.HashMap.put(K
key, V value)源码即可发现这点:

<span style="font-size:18px;">    /**
     * Maps the specified key to the specified value.
     *
     * @param key
     *            the key.
     * @param value
     *            the value.
     * @return the value of any previous mapping with the specified key or
     *         {@code null} if there was no such mapping.
     */
    @Override public V put(K key, V value) {
        if (key == null) {
            return putValueForNullKey(value);
        }

        int hash = secondaryHash(key);
        HashMapEntry<K, V>[] tab = table;
        int index = hash & (tab.length - 1);
        for (HashMapEntry<K, V> e = tab[index]; e != null; e = e.next) {
            if (e.hash == hash && key.equals(e.key)) {
                preModify(e);
                V oldValue = e.value;
                e.value = value;
                return oldValue;
            }
        }

        // No entry for (non-null) key is present; create one
        modCount++;
        if (size++ > threshold) {
            tab = doubleCapacity();
            index = hash & (tab.length - 1);
        }
        addNewEntry(key, value, hash, index);
        return null;
    }</span>

HashMap.put方法用于向HashMap中插入HashMapEntry元素(即插入Key-Value键值对),当key为空的时候调用putValueForNullKey方法将value加入到key为NULL的HashMapEntry对象中。然后再检测当前传入的key是否在table数组中已经存在,如果存在就将新的value值替换掉key对应旧的value值oldValue并将oldValue返回;如果key在table数组中不存在则调用addNewEntry(K
key, V value, int hash, int index) 方法将Value对象添加到table数组中。不过在加入到table数组之前要先进行判断,如果table数组元素数量size大于负载因子threshold则调用doubleCapacity来给table数组扩容。doubleCapacity方法源码如下:

<span style="font-size:18px;">    /**
     * Doubles the capacity of the hash table. Existing entries are placed in
     * the correct bucket on the enlarged table. If the current capacity is,
     * MAXIMUM_CAPACITY, this method is a no-op. Returns the table, which
     * will be new unless we were already at MAXIMUM_CAPACITY.
     * 将数组容量扩大
     */
    private HashMapEntry<K, V>[] doubleCapacity() {
    	/**旧的table数组*/
        HashMapEntry<K, V>[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            return oldTable;
        }
        int newCapacity = oldCapacity * 2;
        HashMapEntry<K, V>[] newTable = makeTable(newCapacity);
        if (size == 0) {
            return newTable;
        }

        for (int j = 0; j < oldCapacity; j++) {
            /*
             * Rehash the bucket using the minimum number of field writes.
             * This is the most subtle and delicate code in the class.
             */
            HashMapEntry<K, V> e = oldTable[j];
            if (e == null) {
                continue;
            }
            int highBit = e.hash & oldCapacity;
            HashMapEntry<K, V> broken = null;
            newTable[j | highBit] = e;

            for (HashMapEntry<K, V> n = e.next; n != null; e = n, n = n.next) {
                int nextHighBit = n.hash & oldCapacity;
                if (nextHighBit != highBit) {
                    if (broken == null)
                        newTable[j | nextHighBit] = n;
                    else
                        broken.next = n;
                    broken = e;
                    highBit = nextHighBit;
                }
            }
            if (broken != null)
                broken.next = null;
        }
        return newTable;
    }</span>

下面再看看java.util.HashMap.get(Object key)方法源码:

<span style="font-size:18px;">    /**
     * Returns the value of the mapping with the specified key.
     *
     * @param key
     *            the key.
     * @return the value of the mapping with the specified key, or {@code null}
     *         if no mapping for the specified key is found.
     */
    public V get(Object key) {
        if (key == null) {
            HashMapEntry<K, V> e = entryForNullKey;
            return e == null ? null : e.value;
        }

        // Doug Lea's supplemental secondaryHash function (inlined).
        // Replace with Collections.secondaryHash when the VM is fast enough (http://b/8290590).
        int hash = key.hashCode();
        hash ^= (hash >>> 20) ^ (hash >>> 12);
        hash ^= (hash >>> 7) ^ (hash >>> 4);

        HashMapEntry<K, V>[] tab = table;
        for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)];
                e != null; e = e.next) {
            K eKey = e.key;
            if (eKey == key || (e.hash == hash && key.equals(eKey))) {
                return e.value;
            }
        }
        return null;
    }</span>

在HashMap.get方法可以发现HashMap在查找Key的过程中,如果是自定义类作为Key对象的话,通常要求重写equals方法和hashCode方法,并且equals方法和hashCode方法判断标准必须保持一致,否则将出现冲突。即在使用自定义类作为HashMap中Key对象,HashMap判断两个Key相等的标准是:两个Key通过equals()方法比较返回true,两个Key的hashCode值也必须相等。

与此同时,我们再看看HashTable的源码,可以发现HashTable与HashMap有一部分方式的底层实现差不多,在此就不详述,主要看下两者在底层实现中的一些区别。

java.util.Hashtable.put(K key, V value)源码:

<span style="font-size:18px;">    /**
     * Associate the specified value with the specified key in this
     * {@code Hashtable}. If the key already exists, the old value is replaced.
     * The key and value cannot be null.
     *
     * @param key
     *            the key to add.
     * @param value
     *            the value to add.
     * @return the old value associated with the specified key, or {@code null}
     *         if the key did not exist.
     * @see #elements
     * @see #get
     * @see #keys
     * @see java.lang.Object#equals
     */
    public synchronized V put(K key, V value) {
        if (key == null) {
            throw new NullPointerException("key == null");
        } else if (value == null) {
            throw new NullPointerException("value == null");
        }
        int hash = Collections.secondaryHash(key);
        HashtableEntry<K, V>[] tab = table;
        int index = hash & (tab.length - 1);
        HashtableEntry<K, V> first = tab[index];
        for (HashtableEntry<K, V> e = first; e != null; e = e.next) {
            if (e.hash == hash && key.equals(e.key)) {
                V oldValue = e.value;
                e.value = value;
                return oldValue;
            }
        }

        // No entry for key is present; create one
        modCount++;
        if (size++ > threshold) {
            rehash();  // Does nothing!!
            tab = doubleCapacity();
            index = hash & (tab.length - 1);
            first = tab[index];
        }
        tab[index] = new HashtableEntry<K, V>(key, value, hash, first);
        return null;
    }</span>

HashTable.put方法与HashMap.put方法基本实现逻辑相同,主要区别是HashTable.put方法被synchronized关键字修饰,因此HashTable是线程安全的。同时如果Key为NULL的话,在HashTable.put方法中将会报异常,所以在HashTable中Key不能为NULL,而我们在前面的分析中可以发现HashMap.put方法中Key是可以为NULL的。

下面给出java.util.Hashtable.get(Object key)源码,亦可以发现相似结果,故就不具体分析。

<span style="font-size:18px;">    /**
     * Returns the value associated with the specified key in this
     * {@code Hashtable}.
     *
     * @param key
     *            the key of the value returned.
     * @return the value associated with the specified key, or {@code null} if
     *         the specified key does not exist.
     * @see #put
     */
    public synchronized V get(Object key) {
        int hash = Collections.secondaryHash(key);
        HashtableEntry<K, V>[] tab = table;
        for (HashtableEntry<K, V> e = tab[hash & (tab.length - 1)];
                e != null; e = e.next) {
            K eKey = e.key;
            if (eKey == key || (e.hash == hash && key.equals(eKey))) {
                return e.value;
            }
        }
        return null;
    }</span>

HashMap源码(JDK1.7,含注释)      HashTable源码(JDK1.7,含注释)

时间: 2024-10-01 03:00:12

从Java源码的角度来分析HashMap与HashTable的区别的相关文章

JAVA源码走读(一) HashMap与ArrayList

HashMap 一.HashMap基本概念: HashMap是基于哈希表的Map接口的实现.此实现提供所有可选的映射操作,并允许使用null值和null键.此类不保证映射的顺序,特别是它不保证该顺序恒久不变. HashMap不是线程安全的,如果想要线程安全的HashMap,可以通过Collections类的静态方法synchronizedMap获得线程安全的HashMap. Map map = Connections.synchronized(new HashMap()); 二.HashMap的

(转) Android事件分发机制完全解析,带你从源码的角度彻底理解(上)

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9097463 其实我一直准备写一篇关于Android事件分发机制的文章,从我的第一篇博客开始,就零零散散在好多地方使用到了Android事件分发的知识.也有好多朋友问过我各种问题,比如:onTouch和onTouchEvent有什么区别,又该如何使用?为什么给ListView引入了一个滑动菜单的功能,ListView就不能滚动了?为什么图片轮播器里的图片使用Button而不用Ima

Android事件分发机制完全解析,带你从源码的角度彻底理解(上)

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9097463 其实我一直准备写一篇关于Android事件分发机制的文章,从我的第一篇博客开始,就零零散散在好多地方使用到了Android事件分发的知识.也有好多朋友问过我各种问题,比如:onTouch和onTouchEvent有什么区别,又该如何使用?为什么给ListView引入了一个滑动菜单的功能,ListView就不能滚动了?为什么图片轮播器里的图片使用Button而不用Ima

[转]Android事件分发机制完全解析,带你从源码的角度彻底理解(上)

Android事件分发机制 该篇文章出处:http://blog.csdn.net/guolin_blog/article/details/9097463 其实我一直准备写一篇关于Android事件分发机制的文章,从我的第一篇博客开始,就零零散散在好多地方使用到了Android事件分发的知识. 也有好多朋友问过我各种问题,比如:onTouch和onTouchEvent有什么区别,又该如何使用?为什么给ListView引入了一个滑动菜单的 功能,ListView就不能滚动了?为什么图片轮播器里的图

从源码的角度解析View的事件分发

有好多朋友问过我各种问题,比如:onTouch和onTouchEvent有什么区别,又该如何使用?为什么给ListView引入了一个滑动菜单的功能,ListView就不能滚动了?为什么图片轮播器里的图片使用Button而不用ImageView?等等……对于这些问题,我并没有给出非常详细的回答,因为我知道如果想要彻底搞明白这些问题,掌握Android事件分发机制是必不可少的,而Android事件分发机制绝对不是三言两语就能说得清的. 在我经过较长时间的筹备之后,终于决定开始写这样一篇文章了.目前虽

《Java源码分析》:线程池 ThreadPoolExecutor

<Java源码分析>:线程池 ThreadPoolExecutor ThreadPoolExecutor是ExecutorService的一张实现,但是是间接实现. ThreadPoolExecutor是继承AbstractExecutorService.而AbstractExecutorService实现了ExecutorService接口. 在介绍细节的之前,先介绍下ThreadPoolExecutor的结构 1.线程池需要支持多个线程并发执行,因此有一个线程集合Collection来执行

《Java源码分析》:Java NIO 之 Buffer

<Java源码分析>:Java NIO 之 Buffer 在上篇博文中,我们介绍了Java NIO 中Channel 和Buffer的基本使用方法,这篇博文将从源码的角度来看下Buffer的内部实现. 在Java API文档中,对Buffer的说明摘入如下: Buffer:是一个用于特定基本数据类型的容器.这里的特定基本数据类型指的是:除boolean类型的其他基本上数据类型. 缓冲区是特定基本数据类型元素的线性有限序列.除内容外,缓冲区饿基本属性还包括三个重要的属性,如下: 1.capaci

从源码的角度分析ViewGruop的事件分发

从源码的角度分析ViewGruop的事件分发. 首先我们来探讨一下,什么是ViewGroup?它和普通的View有什么区别? 顾名思义,ViewGroup就是一组View的集合,它包含很多的子View和子VewGroup,是Android中所有布局的父类或间接父类,像LinearLayout.RelativeLayout等都是继承自ViewGroup的.但ViewGroup实际上也是一个View,只不过比起View,它多了可以包含子View和定义布局参数的功能.ViewGroup继承结构示意图如

《Java源码分析》:HashMap

<Java源码分析>:HashMap 看过很多次HashMap的源码了,但是,每次都没有做记录,因此,每次记忆都不太深,今天在看别人博客时提到Hashtable是线程安全的,Hashtable中的方法都用了synchronized进行了同步,于是就看了下Hashtable的源码,在看的过程中,写了篇博客,现在2016年7月20日22:03:53,还在教研室,感觉回寝室还早,因此,决定再看下HashMap的源码,也随便以写博客的形式做点笔记. 还是很看其他类的源码一样,先看构造函数,然后看一些比