Hashtable和HashMap引发的血案

开发 后端
本票文章很生动的给Hashtable和HashMap举例,并给出代码,方便大家理解。

人物:

王小胖:性别:男。程序员,工作经验1 year。爱好:吃肉、电玩、马小花。特技:吃肉不用考虑胃的容量。

马小花:性别:女。学生,工作经验0 year。爱好:蛋糕、臭美、王小胖。特技:能够降服王小胖……

/**2011年2月,电影《将爱情进行到底》火得不得了。周末,小胖也陪着小花去看这部电影。放映中,小花被影片中的靖哥哥和杜拉拉感动的一沓糊涂,而小胖则心里暗自后悔没有买一袋大爆米花来打发这无聊的时间。影片结束,小花已经是鼻涕一把泪一把,小胖也只有装模作样地抽动了几下鼻子,一心只想着一会儿是吃麦当劳还是必胜客。*/

回到家中,小胖和小花各自玩着电脑。

小花:胖子,你知道Hashtable和HashMap的区别吗?

小胖:略知。

小花:……装什么!!给我讲讲!!!

小胖:好的……

第一个区别就先来说说继承关系吧。

如果你在baidu里google一下(技术类文章的搜索还是推荐google),会发现网上的大致说法与“由于Java发展的历史原因。Hashtable是基于陈旧的Dictionary类的,HashMap是Java 1.2引进的Map接口的一个实现。”相同。这种说法没有错,但是胖子觉得不够准确,特别是对于我们这种大众菜鸟来说,如果不去深究的话,可能就会造成一些理解上的差异。简单的认为Hashtable没有继承Map接口。胖子之前就犯过这样的错误(胖子承认自己笨,是真笨……)。

小花:那你怎么知道它们两个各自的继承关系呢?胖子。

我们可以参考一下最新的JDK1.6的源码,看看这两个类的定义:

Java代码

 

  1. public class Hashtable     
  2.     extends Dictionary     
  3. implements Map, Cloneable, java.io.Serializable {…}     
  4.       
  5. public class HashMap     
  6.     extends AbstractMap     
  7. implements Map, Cloneable, Serializable {…}   

可以看到hashtable也是继承了Map接口。它们的不同是Hashtable(since JDK1.0)就继承了Dictionary这个抽象类,而HashMap(since JDK1.2)继承的则是AbstractMap这个抽象类。因为在Hashtable中看到继承Map后所实现的方法是JDK1.2版本时加上去的,所以胖子猜想可能是在JDK 1.2开发时Sun工程师出于统一的考虑使得Hashtable也继承了Map接口。

小花:哦,原来JDK源码还能看出来这个。

小胖:……后面还能看出更多东西的。

小花:好期待啊。

第二个区别我们从同步和并发性上来说说它们两个的不同。

可以通过这两个类得源码来分析,Hashtable中的主要方法都做了同步处理,而HashMap则没有。可以说Hashtable在默认情况支持同步,而HashMap在默认情况下是不支持的。我们在多线程并发的环境下,可以直接使用Hashtable,但是要使用HashMap的话就要自己增加同步处理了。对HashMap的同步处理可以使用Collections类提供的synchronizedMap静态方法;或者直接使用JDK5.0之后提供的java.util.concurrent包里的ConcurrentHashMap类。

小胖:synchronizedMap静态方法和ConcurrentHashMap类我会以后再给你详细讲一下的。肥婆。

小花:你保证啊。钥匙忘了你知道后果的。

小胖:好的……

第三个区别就是它们对于null值的处理方式了。

我们依然能够从源代码中得知,Hashtable中,key和value都不允许出现null值。

Java代码

 

  1. public synchronized V put(K key, V value) {     
  2.    // Make sure the value is not null     
  3.    if (value == null) {     
  4.        throw new NullPointerException();     
  5.    }     
  6.       
  7.    // Makes sure the key is not already in the hashtable.     
  8.    Entry tab[] = table;     
  9.    int hash = key.hashCode();     
  10.    int index = (hash & 0x7FFFFFFF) % tab.length;     
  11.    //…     
  12. }   

在我们使用上面的方法时,如参数value为null,可以从代码中直接看出程序会抛出NullPointerException;而在key为null时,则会在“int hash = key.hashCode();“这段计算Hash值的过程中抛出NullPointerException。而在在HashMap中,允许null作为key存在,并且和其他key的特性一样,这样的null值key只能有一个;另外HashMap允许多个value为null。这样大家就要注意了, HashMap中就不能用get(key)方法来判断HashMap中是否存在某个key,因为value为null和不存在该key的Entry都会返回null值,而应该用containsKey()方法来判断了。

Java代码

 

  1. import java.util.HashMap;     
  2. import java.util.Map;     
  3. import java.util.Map.Entry;     
  4.       
  5. public class TestCase {     
  6.       
  7.     public static void main(String[] args) {     
  8.        Map hashMap = new HashMap();     
  9.        hashMap.put(0null);     
  10.        hashMap.put(1"one");     
  11.        hashMap.put(2"two");     
  12.        hashMap.put(null"null");     
  13.        for(Entry e : hashMap.entrySet()) {     
  14.            System.out.println("Key: " + e.getKey() + " -- Value: " + e.getValue());     
  15.        }     
  16.        System.out.println(hashMap.get(0));     
  17.        System.out.println(hashMap.get(4));     
  18.        System.out.println("Contains key 0 ? :" + hashMap.containsKey(0));     
  19.        System.out.println("Contains key 4 ? :" + hashMap.containsKey(4));     
  20.        System.out.println("Contains value null ? :" + hashMap.containsValue(null));     
  21.     }     
  22.       
  23. }  

结果:

Java代码

 

  1. Key: null -- Value: null    
  2. Key: 0 -- Value: null    
  3. Key: 1 -- Value: one     
  4. Key: 2 -- Value: two     
  5. null    
  6. null    
  7. Contains key 0 ? :true    
  8. Contains key 4 ? :false    
  9. Contains value null ? :true   

HashMap对于null值key的处理网上有说“null 用new Object()来代替,其Entry.hashCode=0,而且在取出的时候还会还回null的。”胖子我在读取源码的过程中看到了null值的hash值确实是0 (内部实现的数组中的index也是),但是能力有限没有看到转为new Object()的过程。

小花: 原来hashMap的containsKey还有这么个陷阱,以后肥婆要小心了。

第四个不同就是它们两个Hash值的获取方式了。

还是通过源代码源代码,Hashtable是直接使用key对象的hash值。

Java代码

 

  1. public synchronized V put(K key, V value) {     
  2.        // Make sure the value is not null     
  3.        if (value == null) {     
  4.            throw new NullPointerException();     
  5.        }     
  6.       
  7.        // Makes sure the key is not already in the hashtable.     
  8.        Entry tab[] = table;     
  9.        int hash = key.hashCode();//hashcode     
  10.        int index = (hash & 0x7FFFFFFF) % tab.length;     
  11.        //…     
  12. }   

而HashMap则是利用key对象的hash值重新计算一个新的hash值。

Java代码

 

  1. public V put(K key, V value) {     
  2.         if (key == null)     
  3.             return putForNullKey(value);     
  4.         int hash = hash(key.hashCode());//hashcode     
  5.         int i = indexFor(hash, table.length);     
  6.         //…     
  7. }     
  8.       
  9. static int hash(int h) {     
  10.         h ^= (h >>> 20) ^ (h >>> 12);     
  11.         return h ^ (h >>> 7) ^ (h >>> 4);     
  12.     }  

小花:胖子,都用了hash算法,你给我讲讲Hash算法吧。

小胖:嗯……以后的,今天我比较忙(其实是不会)。

小花:你是不是不会啊?嘿嘿(坏笑)。

小胖:什么不会……谈下一话题……

第五个不同就是Hashtable和HashMap它们两个内部实现方式的数组的初始大小和扩容的方式。

HashMap中内部数组的初始容量是16, 加载因子为0.75,而且数组容量增容后也要是2指数次幂:

Java代码

 

  1. /**    
  2.      * The default initial capacity - MUST be a power of two.    
  3.      */    
  4. static final int DEFAULT_INITIAL_CAPACITY = 16;     
  5. /**    
  6.      * The load factor used when none specified in constructor.    
  7.      */    
  8.     static final float DEFAULT_LOAD_FACTOR = 0.75f; 

HashTable中的内部数组的初始容量是11,加载因子也是0.75数组的增容方式为(oldCapacity * 2 + 1):

Java代码

 

  1. public Hashtable() {     
  2.        this(110.75f);     
  3.  }     
  4.       
  5. protected void rehash() {     
  6.        int oldCapacity = table.length;     
  7.        Entry[] oldMap = table;     
  8.       
  9.        int newCapacity = oldCapacity * 2 + 1;     
  10.        //…     
  11. }   

第六个不同我们从它们两个遍历方式的内部实现上来说。

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

小花:Iterator和Enumeration的区别是什么啊?给我讲讲。

小胖:我不是说我没有时间嘛,下回的。

小花:我都记下来,省得你给我混过去。(拿起笔开始记账中)

小胖:……(紧张)

第七个不同时它们的拷贝构造函数的不同。

依然是通过查看源码,可以发现它们两个对于拷贝函数初始容量的不同值。

HashMap的实现是:

Java代码

 

  1. public HashMap(Mapextends K, ? extends V> m) {     
  2.         this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,     
  3.                       DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);     
  4.         putAllForCreate(m);     
  5.     }   

而Hashtable的实现是:

Java代码

 

  1. public Hashtable(Mapextends K, ? extends V> t) {     
  2.        this(Math.max(2*t.size(), 11), 0.75f);     
  3.        putAll(t);     
  4.     }   

小胖:今天讲的已经很多了。我有点饿了,肥婆。

小花:看你今天的表现这么好。走,带你去吃烤肉去。

小胖:哈哈,肥婆万岁。

PS:下面打算写的一些东西

TreeMap的排序及其他相关集合类

synchronizedMap的使用方式

concurrentMap实现细节和使用

Properties使用说明和 扩展

Iterator和Enumeration的区别

Hash算法 的实现

【编辑推荐】

  1. 透过Java中的HashMap了解Map接口
  2. Java中对HashMap的深度分析
  3. Java中对HashMap的深度分析与比较
  4. 深入探究J2ME Hashtable实现原理
  5. java的hashtable的用法
责任编辑:金贺 来源: JavaEye博客
相关推荐

2021-01-11 05:30:04

Boot 单机片

2015-02-04 14:36:07

格式串漏洞Ghost漏洞安全漏洞

2017-03-20 19:40:29

AndroidSwipeRefres下拉刷新

2021-07-27 07:12:11

Getter接口Setter

2023-01-11 08:41:47

微服务循环依赖

2021-12-01 06:59:27

架构

2022-04-12 08:43:04

生产故障Dubbo调用

2019-09-09 08:30:57

MYSQL代码数据库

2018-11-22 15:50:27

MySQL数据库双引号

2021-01-25 08:08:22

APP机器人KOB

2020-01-06 09:43:14

赔偿TSB迁移

2021-02-01 10:42:47

MySQL双引号数据库

2017-05-22 08:35:07

MySQL双引号错位

2013-08-26 10:19:24

纳斯达克数据专线交易暂停

2017-08-25 16:38:05

表达式正则血案

2016-12-01 09:30:03

运维网络网线

2013-03-18 09:56:18

2010-08-09 09:46:40

2011-05-20 12:34:05

大话IT云服务中断亚马逊

2022-06-14 08:00:28

切换包管理器版本
点赞
收藏

51CTO技术栈公众号