夜深了,快睡吧-HashMap和HashTable

2018-08-07  本文已影响35人  一个喜欢烧砖的人

首先,今天主要说说hashMap和hashTable的使用和不同,至于还有个ConcurrentHashMap,明晚再说吧...|

大家总是对map来说比较熟一点,那就先说map再说table把

HashMap

基本使用

public class MyTest {

    public static void main(String[] args){

        HashMap<String, String> hashMap = new HashMap<String, String>();
        hashMap.put("cn", "中国");
        hashMap.put("jp", "日本");
        hashMap.put("fr", "法国");
        hashMap.put("cn", "中国2号");
        System.out.println(hashMap);
        System.out.println("****");
        System.out.println("cn:" + hashMap.get("cn"));
        System.out.println(hashMap.containsKey("cn"));

        System.out.println(hashMap.keySet());
        System.out.println(hashMap.isEmpty());

        hashMap.remove("cn");
        System.out.println(hashMap);
        //采用Iterator遍历HashMa
        Iterator it = hashMap.keySet().iterator();
        while(it.hasNext()) {
            String key = (String)it.next();
            System.out.println("key:" + key);
            System.out.println("value:" + hashMap.get(key));
        }

        //遍历HashMap的另一个方法
        Set<Map.Entry<String, String>> sets = hashMap.entrySet();
        for(Map.Entry<String, String> entry : sets) {
            System.out.print(entry.getKey() + ", ");
            System.out.println(entry.getValue());
        }
    }
}

运行结果

{jp=日本, cn=中国2号, fr=法国}
****
cn:中国2号
true
[jp, cn, fr]
false
{jp=日本, fr=法国}
key:jp
value:日本
key:fr
value:法国
jp, 日本
fr, 法国
hashMap 总结

HashTable

基本使用

private static void initHashTable() {
        Hashtable<String, String> table = new Hashtable<String, String>();

        //[1]添加元素
        table.put("cn", "中国");
        table.put("jp", "日本");
        table.put("fr", "法国");
        table.put("cn", "中国2号");
        //[2]toString()方式打印
        System.out.println(table.toString());
        //都可以 
        System.out.println(table);
        //[3]Iterator遍历方式1--键值对遍历entrySet()
        Iterator<Map.Entry<String, String>> iter = table.entrySet().iterator();
        while(iter.hasNext()){
            Map.Entry<String, String> entry = (Map.Entry<String, String>)iter.next();
            String key = entry.getKey();
            String value = entry.getValue();
            System.out.println("entrySet:"+key+" "+value);
        }

        System.out.println("====================================");

        //[4]Iterator遍历方式2--key键的遍历
        Iterator<String> iterator = table.keySet().iterator();
        while(iterator.hasNext()){
            String key = (String)iterator.next();
            String value = table.get(key);
            System.out.println("keySet:"+key+" "+value);
        }

        System.out.println("====================================");

        //[5]通过Enumeration来遍历Hashtable
        Enumeration<String> enu = table.keys();
        while(enu.hasMoreElements()) {
            System.out.println("Enumeration:"+table.keys()+" "+enu.nextElement());
        }
    }

运行结构

{jp=日本, fr=法国, cn=中国2号}
{jp=日本, fr=法国, cn=中国2号}
entrySet:jp 日本
entrySet:fr 法国
entrySet:cn 中国2号
====================================
keySet:jp 日本
keySet:fr 法国
keySet:cn 中国2号
====================================
Enumeration:java.util.Hashtable$Enumerator@4554617c jp
Enumeration:java.util.Hashtable$Enumerator@74a14482 fr
Enumeration:java.util.Hashtable$Enumerator@1540e19d cn

两者的不同

1、作者不同
* @author  Doug Lea
 * @author  Josh Bloch
 * @author  Arthur van Hoff
 * @author  Neal Gafter
 * @author  Arthur van Hoff
 * @author  Josh Bloch
 * @author  Neal Gafter

总结:map比table多了一个人,而dl也是util.concurrent包的作者

2、产生时间

hashmap产生于jdk1.2
hashTable产生于jdk1.0

3、继承的父类不同
public class Hashtable<K,V>
         extends Dictionary<K,V>
         implements Map<K,V>, Cloneable, java.io.Serializable {

备注:Dictionary类是一个已经被废弃的类(见其源码中的注释)。父类都被废弃,自然而然也没人用它的子类Hashtable了。

public class HashMap<K,V> 
          extends AbstractMap<K,V>
          implements Map<K,V>, Cloneable, Serializable {
4、对外接口不同

Hashtable比HashMap多提供了elments() 和contains() 两个方法。
elments() 方法继承自Hashtable的父类Dictionnary。elements() 方法用于返回此Hashtable中的value的枚举。

   * @see     #keys()
     * @see     #values()
     * @see     Map
     */
    public synchronized Enumeration<V> elements() {
        return this.<V>getEnumeration(VALUES);
    }
    /**
     * Tests if some key maps into the specified value in this hashtable.
     * This operation is more expensive than the {@link #containsKey
     * containsKey} method.

contains()方法判断该Hashtable是否包含传入的value。它的作用与containsValue()一致。事实上,contansValue() 就只是调用了一下contains() 方法。

   * @param value value whose presence in this hashtable is to be tested
     * @return <tt>true</tt> if this map maps one or more keys to the
     *         specified value
     * @throws NullPointerException  if the value is <code>null</code>
     * @since 1.2
     */
    public boolean containsValue(Object value) {
        return contains(value);
    }
    /**
     * Tests if the specified object is a key in this hashtable.
     *
     * @param   key   possible key
     * @return  <code>true</code> if and only if the specified object
5、对null key 和 null value 的支持不同

hashTable:

//        table.put(null,"fsdfd");//会报空指针
//        table.put("cdcd",null);//也会报空指针

hashMap:

rivate static void initHashMap() {
        HashMap<String, String> hashMap = new HashMap<String, String>();
        hashMap.put("cn", "中国");
        hashMap.put("jp", "日本");
        hashMap.put("fr", "法国");
        hashMap.put("cn", "中国2号");
        hashMap.put(null,"空");
        hashMap.put("nu",null);
        System.out.println(hashMap);

运行结构

{null=空, jp=日本, nu=null, cn=中国2号, fr=法国}

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

6、线程安全不同
7、遍历实现的内部方式不同

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

HashMap的Iterator是fail-fast迭代器。当有其它线程改变了HashMap的结构(增加,删除,修改元素),将会抛出ConcurrentModificationException。不过,通过Iterator的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。

JDK8之前的版本中,Hashtable是没有fast-fail机制的。在JDK8及以后的版本中 ,HashTable也是使用fast-fail的, 源码如下:
modCount的使用类似于并发编程中的CAS(Compare and Swap)技术。我们可以看到这个方法中,每次在发生增删改的时候都会出现modCount++的动作。而modcount可以理解为是当前hashtable的状态。每发生一次操作,状态就向前走一步。设置这个状态,主要是由于hashtable等容器类在迭代时,判断数据是否过时时使用的。尽管hashtable采用了原生的同步锁来保护数据安全。但是在出现迭代数据的时候,则无法保证边迭代,边正确操作。于是使用这个值来标记状态。一旦在迭代的过程中状态发生了改变,则会快速抛出一个异常,终止迭代行为。

8、初始容量大小和每次扩充容量大小的不同

Hashtable默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。

创建时,如果给定了容量初始值,那么Hashtable会直接使用你给定的大小,而HashMap会将其扩充为2的幂次方大小。也就是说Hashtable会尽量使用素数、奇数。而HashMap则总是使用2的幂作为哈希表的大小。

之所以会有这样的不同,是因为Hashtable和HashMap设计时的侧重点不同。Hashtable的侧重点是哈希的结果更加均匀,使得哈希冲突减少。当哈希表的大小为素数时,简单的取模哈希的结果会更加均匀。而HashMap则更加关注hash的计算效率问题。在取模计算时,如果模数是2的幂,那么我们可以直接使用位运算来得到结果,效率要大大高于做除法。HashMap为了加快hash的速度,将哈希表的大小固定为了2的幂。当然这引入了哈希分布不均匀的问题,所以HashMap为解决这问题,又对hash算法做了一些改动。这从而导致了Hashtable和HashMap的计算hash值的方法不同

9、计算hash值的方法不同

为了得到元素的位置,首先需要根据元素的 KEY计算出一个hash值,然后再用这个hash值来计算得到最终的位置。

Hashtable直接使用对象的hashCode。hashCode是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值。然后再使用除留余数发来获得最终的位置。

Hashtable在计算元素的位置时需要进行一次除法运算,而除法运算是比较耗时的。
HashMap为了提高计算效率,将哈希表的大小固定为了2的幂,这样在取模预算时,不需要做除法,只需要做位运算。位运算比除法的效率要高很多。

HashMap的效率虽然提高了,但是hash冲突却也增加了。因为它得出的hash值的低位相同的概率比较高,而计算位运算

为了解决这个问题,HashMap重新根据hashcode计算hash值后,又对hash值做了一些运算来打散数据。使得取得的位置更加分散,从而减少了hash冲突。当然了,为了高效,HashMap只做了一些简单的位处理。从而不至于把使用2 的幂次方带来的效率提升给抵消掉。

上一篇下一篇

猜你喜欢

热点阅读