Java之HashMap和Hashtable

1,介绍

Hashtable是java一开始发布时就提供的键值映射的数据结构,而HashMap产生于JDK1.2。虽然Hashtable比HashMap出现的早一些,但是现在Hashtable基本上已经被弃用了。而HashMap已经成为应用最为广泛的一种数据类型了。造成这样的原因一方面是因为Hashtable是线程安全的,效率比较低。

2,HashMap和Hashtable的区别

1,父类不同:

HashTable是继承自Dictionary(已被废弃,详情看源代码)

public class Hashtable<K, V> extends Dictionary<K, V> implements Map<K, V>, Cloneable, Serializable 

HashMap是继承自AbstractMap类

public class HashMap extends AbstractMap implements Map, Cloneable, Serializable

不过hashmap和Hashtable都实现了map、Cloneable(可克隆)、Serializable(可序列化)这三个接口

2,底层数据结构不同

jdk1.7前两者解决哈希冲突的底层都是数组+链表,但JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有变化。

3、null值问题

Hashtable既不支持Null key也不支持Null value。

HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。当get()方法返回null值时,可能是 HashMap中没有该键,也可能使该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键, 而应该用containsKey()方法来判断。

4,线程安全性

Hashtable是线程安全的,它的每个方法中都加入了Synchronize方法。在多线程并发的环境下,可以直接使用Hashtable,不需要自己为它的方法实现同步。因此多个线程可以共享一个Hashtable。具体可以看以下示例:

public synchronized boolean containsKey(Object key) 
public synchronized V get(Object key)
public synchronized int size()
public synchronized boolean isEmpty()
public synchronized boolean contains

以上都是Hashtable源码中的方法。

hashMap不是线程安全的,在多线程并发的环境下,可能会产生死锁等问题。如果没有正确的同步的话,多个线程是不能共享HashMap的。

当需要多线程操作的时候可以使用线程安全的ConcurrentHashMap。ConcurrentHashMap虽然也是线程安全的,但是它的效率比Hashtable要高好多倍。因为ConcurrentHashMap使用了分段锁,并不对整个数据进行锁定。ConcurrentHashMap内容很多,具体见下篇文件详细分析hashMap和ConcurrentHashMap

5、遍历方式不同

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

6、初始容量不同

Hashtable的初始长度是11,之后每次扩充容量变为之前的2n+1

而HashMap的初始长度为16,之后每次扩充变为原来的两倍:

创建时,如果给定了容量初始值,那么Hashtable会直接使用你给定的大小,而HashMap会将其扩充为2的幂次方大小(HashMap 中的tableSizeFor()方法保证,下面给出了源代码)。具体实现可以看下面代码:

static final int tableSizeFor(int cap) {
    int n = -1 >>> Integer.numberOfLeadingZeros(cap - 1);
    return n < 0 ? 1 : (n >= 1073741824 ? 1073741824 : n + 1);
}

public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0) {
        throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
    } else {
        if (initialCapacity > 1073741824) {
            initialCapacity = 1073741824;
        }

        if (loadFactor > 0.0F && !Float.isNaN(loadFactor)) {
            this.loadFactor = loadFactor;
            this.threshold = tableSizeFor(initialCapacity);
        } else {
            throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
        }
    }
}
public HashMap() {
        this.loadFactor = 0.75F;//负载因子
    }
public Hashtable() {
        this(11, 0.75F);
    }
public Hashtable(Map<? extends K, ? extends V> t) {
        this(Math.max(2 * t.size(), 11), 0.75F);
        this.putAll(t);
    }

7,计算哈希值的方法不同

为了得到元素的位置,首先需要根据元素的 KEY计算出一个hash值,然后再用这个hash值来计算得到最终的位置。Hashtable直接使用对象的hashCode。HashCode是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值。然后再使用除留余数发来获得最终的位置。 然而除法运算是非常耗费时间的。效率很低

HashMap为了提高计算效率,将哈希表的大小固定为了2的幂,这样在取模预算时,不需要做除法,只需要做位运算。位运算比除法的效率要高很多。

注:

  1. 负载因子两者都为0.75,其作用是,当哈希表中已使用存储的容量占据总容量的75%时,进行扩容。选择0.75的原因是:从时间和空间上考虑,如果为1,空间上确实利用率高,但是hash冲突的概率会增加,查找成本会增加;如果为0.5,hash冲突会降低,但是有一半的空间会被浪费。
  2. Hashtable现在几乎已经被淘汰了,一般情况下使用HashMap,如果对多线程处理安全性要求高,则可以使用ConcurrentHashMap。
  3. Hashtable 并没有遵守驼峰命名规则(当初找了好久才发现这个小细节)。

作者:王二黑_Leon

欢迎任何形式的转载,但请务必注明出处。

限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。

本文章仅作为自学所用。

你可能感兴趣的:(java,java,极限编程)