Hashtable和HashMap在Java面试中相当容易被问到,甚至成为了集合框架面试题中最常被考的问题,所以在参加任何Java面试之前,都不要忘了准备这一题。
我们先看2个类的定义
public class Hashtable extends Dictionary implements Map, Cloneable, <a href="http://lib.csdn.net/base/javase" class='replace_word' title="Java SE知识库" target='_blank' style='color:#df3434; font-weight:bold;'>Java</a>.io.Serializable
public class HashMap extends AbstractMap implements Map, Cloneable, Serializable
可见Hashtable 继承自 Dictiionary 而 HashMap继承自AbstractMap
Hashtable的put方法如下
public synchronized V put(K key, V value) { //###### 注意这里1 // Make sure the value is not null if (value == null) { //###### 注意这里 2 throw new NullPointerException(); } // Makes sure the key is not already in the hashtable. Entry tab[] = table; int hash = key.hashCode(); //###### 注意这里 3 int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry e = tab[index]; e != null; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { V old = e.value; e.value = value; return old; } } modCount++; if (count >= threshold) { // Rehash the table if the threshold is exceeded rehash(); tab = table; index = (hash & 0x7FFFFFFF) % tab.length; } // Creates the new entry. Entry e = tab[index]; tab[index] = new Entry(hash, key, value, e); count++; return null; }
注意1 方法是同步的
注意2 方法不允许value==null
注意3 方法调用了key的hashCode方法,如果key==null,会抛出空指针异常
HashMap的put方法如下
public V put(K key, V value) { //###### 注意这里 1 if (key == null) //###### 注意这里 2 return putForNullKey(value); int hash = hash(key.hashCode()); int i = indexFor(hash, table.length); for (Entry e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); //###### 注意这里 return null; }
注意1 方法是非同步的
注意2 方法允许key==null
注意3 方法并没有对value进行任何调用,所以允许为null
是否提供contains方法
HashMap把Hashtable的contains方法去掉了,改成containsValue和containsKey,因为contains方法容易让人引起误解。
Hashtable则保留了contains,containsValue和containsKey三个方法,其中contains和containsValue功能相同。
我们看一下Hashtable的ContainsKey方法和ContainsValue的源码:
public boolean containsValue(Object value) { return contains(value); }
// 判断Hashtable是否包含“值(value)” public synchronized boolean contains(Object value) { //注意,Hashtable中的value不能是null, // 若是null的话,抛出异常! if (value == null) { throw new NullPointerException(); } // 从后向前遍历table数组中的元素(Entry) // 对于每个Entry(单向链表),逐个遍历,判断节点的值是否等于value Entry tab[] = table; for (int i = tab.length ; i-- > 0 ;) { for (Entry<K,V> e = tab[i] ; e != null ; e = e.next) { if (e.value.equals(value)) { return true; } } } return false; }
// 判断Hashtable是否包含key public synchronized boolean containsKey(Object key) { Entry tab[] = table; /计算hash值,直接用key的hashCode代替 int hash = key.hashCode(); // 计算在数组中的索引值 int index = (hash & 0x7FFFFFFF) % tab.length; // 找到“key对应的Entry(链表)”,然后在链表中找出“哈希值”和“键值”与key都相等的元素 for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { return true; } } return false; }
下面我们看一下HashMap的ContainsKey方法和ContainsValue的源码:
// HashMap是否包含key public boolean containsKey(Object key) { return getEntry(key) != null; }
// 返回“键为key”的键值对 final Entry<K,V> getEntry(Object key) { // 获取哈希值 // HashMap将“key为null”的元素存储在table[0]位置,“key不为null”的则调用hash()计算哈希值 int hash = (key == null) ? 0 : hash(key.hashCode()); // 在“该hash值对应的链表”上查找“键值等于key”的元素 for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } return null; }
// 是否包含“值为value”的元素 public boolean containsValue(Object value) { // 若“value为null”,则调用containsNullValue()查找 if (value == null) return containsNullValue(); // 若“value不为null”,则查找HashMap中是否有值为value的节点。 Entry[] tab = table; for (int i = 0; i < tab.length ; i++) for (Entry e = tab[i] ; e != null ; e = e.next) if (value.equals(e.value)) return true; return false; }
通过上面源码的比较,我们可以得到如下不同地方:key和value是否允许null值。
其中key和value都是对象,并且不能包含重复key,但可以包含重复的value。通过上面的ContainsKey方法和ContainsValue的源码我们可以很明显的看出:
Hashtable中,key和value都不允许出现null值。但是如果在Hashtable中有类似put(null,null)的操作,编译同样可以通过,因为key和value都是Object类型,但运行时会抛出NullPointerException异常,这是JDK的规范规定的。
HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。当get()方法返回null值时,可能是 HashMap中没有该键,也可能使该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键, 而应该用containsKey()方法来判断。
HashMap和Hashtable的区别
HashMap是Hashtable的轻量级实现(非线程安全的实现),他们都完成了Map接口。主要的区别有:线程安全性,同步(synchronization),以及速度。
1.Hashtable继承自Dictionary类,而HashMap是Java1.2引进的Map interface的一个实现。
2.HashMap允许将null作为一个entry的key或者value,而Hashtable不允许。
3.HashMap是非synchronized,而Hashtable是synchronized,这意味着Hashtable是线程安全的,多个线程可以共享一个Hashtable;而如果没有正确的同步的话,多个线程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。(在多个线程访问Hashtable时,不需要自己为它的方法实现同步,而HashMap 就必须为之提供外同步(Collections.synchronizedMap))
4.另一个区别是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别。fail-fast机制如果不理解原理,可以查看这篇文章:http://www.cnblogs.com/alexlo/archive/2013/03/14/2959233.html
5.由于HashMap非线程安全,在只有一个线程访问的情况下,效率要高于HashTable。
6.HashMap把Hashtable的contains方法去掉了,改成containsvalue和containsKey。因为contains方法容易让人引起误解。
7.Hashtable中hash数组默认大小是11,增加的方式是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。
8..两者通过hash值散列到hash表的算法不一样:
,HashTbale是古老的除留余数法,直接使用hashcode
int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length;
而后者是强制容量为2的幂,重新根据hashcode计算hash值,在使用hash 位与 (hash表长度 – 1),也等价取膜,但更加高效,取得的位置更加分散,偶数,奇数保证了都会分散到。前者就不能保证
int hash = hash(k); int i = indexFor(hash, table.length); static int hash(Object x) { int h = x.hashCode(); h += ~(h << 9); h ^= (h >>> 14); h += (h << 4); h ^= (h >>> 10); return h; } static int indexFor(int h, int length) { return h & (length-1);
要注意的一些术语:
1.sychronized意味着在一次仅有一个线程能够更改Hashtable。就是说任何线程要更新Hashtable时要首先获得同步锁,其它线程要等到同步锁被释放之后才能再次获得同步锁更新Hashtable。
2.Fail-safe和iterator迭代器相关。如果某个集合对象创建了Iterator或者ListIterator,然后其它的线程试图“结构上”更改集合对象,将会抛出ConcurrentModificationException异常。但其它线程可以通过set()方法更改集合对象是允许的,因为这并没有从“结构上”更改集合。但是假如已经从结构上进行了更改,再调用set()方法,将会抛出IllegalArgumentException异常。
3.结构上的更改指的是删除或者插入一个元素,这样会影响到map的结构。
代码演示部分如下:
先看个Hashtable正常输出的示例:
Hashtable table = new Hashtable(); table.put("a-key", "a-value"); table.put("b-key", "b-value"); table.put("c-key", "c-value");
输出如下:
a-key - a-value c-key - c-value b-key - b-value
再看个Hashtable拒绝null的示例:
table.put(null, "a-value");
运行之后异常如下:
Exception in thread "main" java.lang.NullPointerException at java.util.Hashtable.put(Hashtable.java:399) at com.darkmi.sandbox.HashtableTest.main(HashtableTest.java:20)
HashMap示例:
HashMap map = new HashMap(); map.put(null, "a-value"); map.put("b-key", null); map.put("c-key", null);
b-key - null null - a-value c-key - null
PS:从上面的示例我们倒是可以发现Hashtable与HashMap相同的一点:无序存放。
3.两者的遍历方式大同小异,Hashtable仅仅比HashMap多一个elements方法。
Enumeration em = table.elements(); while (em.hasMoreElements()) { String obj = (String) em.nextElement(); System.out.println(obj); }
HashMap和HashTable都能通过values()方法返回一个 Collection ,然后进行遍历处理:
Collection coll = map.values(); Iterator it = coll.iterator(); while (it.hasNext()) { String obj = (String) it.next(); System.out.println(obj); }
两者也都可以通过 entrySet() 方法返回一个 Set , 然后进行遍历处理:
Set set = table.entrySet(); Iterator it = set.iterator(); while (it.hasNext()) { Entry entry = (Entry) it.next(); System.out.println(entry.getKey() + " - " + entry.getValue()); }
相关推荐
在Java编程中,`HashMap`与`HashTable`作为两种常用的数据结构,经常被用来存储键值对数据。尽管它们在功能上相似,但在实现细节、性能表现以及使用场景方面存在显著差异。本文将深入探讨两者之间的区别,帮助读者更...
`HashMap` 和 `HashTable` 都是 Java 集合框架中非常重要的数据结构,它们都实现了 `Map` 接口,用于存储键值对。尽管它们在功能上有很多相似之处,但在实现细节和性能特性上存在显著差异。 #### 二、主要区别 1. ...
本文总结了Java集合专题中的HashMap和HashTable,涵盖了它们的源码学习和面试总结。HashMap是一种基于哈希表的集合类,它的存储结构是一个数组,每个元素是一个单链表的头节点,链表用来解决hash地址冲突问题。...
在Java编程语言中,`HashMap`和`HashTable`是两种非常重要的数据结构,它们都实现了`Map`接口,并提供了键值对的存储方式。这两种数据结构虽然相似,但在实现细节和使用场景上存在显著差异。下面将详细介绍`HashMap`...
在Java集合框架中,`HashMap`, `HashTable` 和 `HashSet` 是三个重要的数据结构,它们分别实现了`Map`接口和`Set`接口,提供了不同的功能来满足不同的编程需求。本文将重点分析这三种数据结构之间的区别,特别是针对...
HashMap和HashTable是Java中两个常用的数据结构,都是基于哈希表实现的,但它们之间存在着一些关键的区别。本文将深入探讨HashMap和HashTable的底层原理,并总结常见的面试题。 HashMap的底层原理 HashMap是Java中...
在Java编程语言中,`HashMap`和`Hashtable`是两种非常重要的数据结构,它们都用于存储键值对。然而,在实际应用过程中,这两种数据结构有着本质的不同,下面将详细介绍这些差异。 #### 1. 历史背景及实现原理 - **...
HashMap和Hashtable的区别Java开发Java经验技巧共2页.pdf.zip
在Java编程语言中,`HashMap`和`HashTable`都是实现键值对存储的数据结构,但它们之间存在一些显著的区别,这些区别主要体现在线程安全性、性能、null值处理以及一些方法特性上。以下是对这两个类的详细分析: 1. ...
Java面试题11.HashMap和HashTable的区别.mp4
HashMap 和 Hashtable 是 Java 集合框架中两个重要的映射数据结构,它们都实现了 Map 接口,但具有显著的差异。以下将详细介绍这两个类的主要区别: 1. 线程安全性: - HashMap 不是线程安全的,这意味着在多线程...
### Java中HashMap, LinkedHashMap, TreeMap,HashTable的区别 在Java编程语言中,`Map`接口是集合框架中的一个重要组成部分,用于存储键值对。本文将详细分析四种常用的`Map`实现类:`HashMap`, `LinkedHashMap`, ...
hashMap和Hashtable的区别1
java面试题精讲视频MP4Java面试题11.HashMap和HashTable的区别提取方式是百度网盘分享地址
HashMap是Java中常用的一种数据结构,它基于哈希表实现,提供快速的键值对存储和检索。HashMap的核心原理是通过散列函数将键对象转换为哈希码,然后使用这个哈希码来确定键值对在内部数组中的位置。哈希函数的设计...
HashMap和HashTable的区别?但是如果想线程安全有想效率高?
在Java编程语言中,集合框架是处理对象数组的重要工具,其中`List`、`ArrayList`、`Vector`、`HashTable`和`HashMap`是五个关键的接口和类,它们各有不同的特性和用途。以下是这些概念的详细解释: 1. **List接口**...
在Java编程语言中,HashMap和Hashtable都是用于存储键值对的数据结构,它们都实现了Map接口。然而,两者之间存在一些显著的区别,这些差异主要体现在线程安全性、空值支持、继承结构以及方法实现等方面。以下是关于...
在Java编程语言中,`HashMap`和`HashTable`是两种常用的集合类,它们都是用于存储键值对的数据结构。这两个类都实现了`Map`接口,但它们之间存在一些显著的区别,这些差异主要体现在线程安全性、null值处理、迭代...
在Java编程语言中,`Hashtable` 和 `HashMap` 都是用来存储键值对的数据结构。这两种数据结构虽然相似,但是在实现细节上存在显著差异。 1. **Hashtable**:作为 `Dictionary` 类的子类,`Hashtable` 是 Java 最早...