Rumah > Java > javaTutorial > java中HashMap和HashTable的区别是什么?HashMap和HashTable的简单比较

java中HashMap和HashTable的区别是什么?HashMap和HashTable的简单比较

青灯夜游
Lepaskan: 2018-10-22 17:55:04
ke hadapan
2741 orang telah melayarinya

本篇文章给大家带来的内容是java中HashMap和HashTable的区别是什么?HashMap和HashTable的简单比较。有一定的参考价值,有需要的朋友可以参考一下,希望对你们有所助。

1.首先来看下继承结构:

HashMap

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable
Salin selepas log masuk

Hashtable

public class Hashtable<K,V>
    extends Dictionary<K,V>
    implements Map<K,V>, Cloneable, java.io.Serializable
Salin selepas log masuk

1、首先通过名字我们可以看出HashMap符合驼峰命名规则,Hashtable不符合驼峰命名规则,通过继承结构我们发现HashMap继承自AbstractMap,而Hashtable继承自Dictionnary,Dictionary是一个已经被废弃的类,所有Hashtable一般不推荐使用,多线程环境下可以使用同步容器ConcurrentHashMap类。

2、通过类的属性可以发现,在jdk1.8,当HashMap中的某条链中存储的结点数大于等于8时并且链表数组长度大于64时转化为红黑树,而Hashtable并不会转化为红黑树。

3、Hashtable的put()与HashMap的put()

Hashtable的put操作:

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;
    }
Salin selepas log masuk

Hashtable中的方法添加了synchronized 关键字,所以它是一个同步方法。

通过 if (value == null)    throw new NullPointerException();} 可以看出他不允许value的值为空,而当key为null时,调用key.hashCode();会抛出null指针异常,所以Hashtable中存储的Entry中的key和值都不能为空。还有Hashtable取链表下标是通过%运算来取的。

下面看HashMap

public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
Salin selepas log masuk

可以看到HashMap中key可以有一个null,当key为null空时它的hash值为0,而且HashMap获取链表数组下标的方法与Hashtable不同,它是使用(n-1)&hash来计算,因为HashMap的数组链表长度为2的n次方。

总结:HashMap中的key可以有一个null,值可以有多个null,Hashtable中的key和value都不能为空。定位链的方式不同,HashMap通过&运算来获取下标,而Hashtable通过%来获取下标,&运算更快。

4、HashMap和Hashtable的扩容方式不同。

Hashtable扩容:

 @SuppressWarnings("unchecked")
    protected void rehash() {
        int oldCapacity = table.length;
        Entry<?,?>[] oldMap = table;

        // overflow-conscious code
        int newCapacity = (oldCapacity << 1) + 1;
        //MAX_ARRAY_SIZE = int的最大值-8
        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];

        modCount++;
        threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
        table = newMap;
		//从链表数组尾到头遍历
        for (int i = oldCapacity ; i-- > 0 ;) {
            for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
                Entry<K,V> e = old;
                old = old.next;
				//从新定位链位置
                int index = (e.hash & 0x7FFFFFFF) % newCapacity;
                e.next = (Entry<K,V>)newMap[index];
                newMap[index] = e;
            }
        }
    }
Salin selepas log masuk

通过源码我们发现Hashtable链表数组的最大长度为int类型的最大值-8,Hashtable的扩容会把长度变为原来的二倍加1,而且扩容后需要从新定位链表。而且扩容后数组链表的顺序变成了原顺序的倒序。

HashMap的扩容:

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) {
            //如果原长度大于或等于MAXIMUM_CAPACITY(2^30),则将threshold(闸值)设为Integer.MAX_VALUE大约为MAXIMUM_CAPACITY(2^30)的二倍
            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
        }
        //老的链表数组长度小于等于0,老的闸值大于零,这种情况是初始化时传入一个map导致的构造器为public HashMap(Map<? extends K, ? extends V> m)
        else if (oldThr > 0) // initial capacity was placed in threshold
        	//让新的容量等于旧的闸值
            newCap = oldThr;
         //下面的else是默认的构造器,即public HashMap()
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        //新的闸值为零时(也就是(newCap = oldCap << 1) >= MAXIMUM_CAPACITY的情况),这时需要赋予newThr正确的值。
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;   //闸值=链表数组长度*加载因子。
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        //扩容,如果原来的链表数组中有数据,就复杂到table中
        @SuppressWarnings({"rawtypes","unchecked"})
            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;
                //当oldTab[j]这条链不为空时
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    //如果这条链只有首节点有数据,把它添加进新链表数组中
                    if (e.next == null)
                        //因为数组的容量时2的n次方,所以使用hash&(newCap-1)来计算出在那条链中。
                        newTab[e.hash & (newCap - 1)] = e;
                     //如果老的链在红黑树中,使用split()方法来复制
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    //当前链中不只只有一个链表头数据时,遍历链表来复制
                    else { // preserve order
                        //数据的复制有两种情况,第一种是原位置不变,第二种是位置改变
         				loHead代表和原链相同位置的链,hiHead代表是原链加上原容量的链,因为扩容后长度为原长度的二倍,一个链中的节点要不在原位置的链中,要么在原位置加原容量的链中
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            //通过e.hash和oldCap进行&运算来得出位置是否需要改变。
                            比如原数组容量为16(10000)和hash值进行&运算,如果高位1未参加运算,则为0即位置不变,如果高位参加了运算值不等于0,需要改变位置。
                           
                        
                            //loHead和hiHead分别代表原位置的链和新位置的链
                            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;
                            //原位置为j
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            //新位置为j+oldCap
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }
Salin selepas log masuk

可以看到HashMap的扩容容量变为原来的二倍,而且它不需要从新定位链表,因为扩容后的位置要么在原位置,要么在原位置+原容量,通过hash和链表数组的长度进行与运算即可判断,如果数组长度的高位参加了运算就在原位置+原容量,高位没参加运算在原位置。而且HashMap扩容后链表数据顺序不变。

5.HashMap和Hashtable的初始容量不同。
Hashtable的初始容量为11,HashMap的初始容量为16.

Atas ialah kandungan terperinci java中HashMap和HashTable的区别是什么?HashMap和HashTable的简单比较. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn
Tutorial Popular
Lagi>
Muat turun terkini
Lagi>
kesan web
Kod sumber laman web
Bahan laman web
Templat hujung hadapan