Moosphan / Android-Daily-Interview

:pushpin:每工作日更新一道 Android 面试题,小聚成河,大聚成江,共勉之~

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

2019-04-26:请说一下HashMap与HashTable的区别?

Moosphan opened this issue · comments

2019-04-26:请说一下HashMap与HashTable的区别?
commented

1.hashMap是线程不安全的,hashTable是线程安全的。
2.hashMap允许key为null,且只允许一个存在,hashtable不允许。

1、HashMap可以接受null(HashMap可以接受为null的键值(key)和值(value),而HashTable则不行。
2、HashMap的迭代器(Iterator)是fail-fast迭代器,而HashTable的enumerator迭代器。
3、单一线程,HashMap的性能要比HashTable好。

HashMap和Hashtable的比较是Java面试中的常见问题,用来考验程序员是否能够正确使用集合类以及是否可以随机应变使用多种思路解决问题。HashMap的工作原理、ArrayList与Vector的比较以及这个问题是有关Java 集合框架的最经典的问题。Hashtable是个过时的集合类,存在于Java API中很久了。在Java 4中被重写了,实现了Map接口,所以自此以后也成了Java集合框架中的一部分。Hashtable和HashMap在Java面试中相当容易被问到,甚至成为了集合框架面试题中最常被考的问题,所以在参加任何Java面试之前,都不要忘了准备这一题。

1父类不同

第一个不同主要是历史原因。Hashtable是基于陈旧的Dictionary类的,HashMap是Java 1.2引进的Map接口的一个实现。

public class HashMap<K, V> extends AbstractMap<K, V> implements Cloneable, Serializable {...}
public class Hashtable<K, V> extends Dictionary<K, V> implements Map<K, V>, Cloneable, Serializable {...}
而HashMap继承的抽象类AbstractMap实现了Map接口:

public abstract class AbstractMap<K, V> implements Map<K, V> {...}

2 线程安全不一样
Hashtable 中的方法是同步的,而HashMap中的方法在默认情况下是非同步的。在多线程并发的环境下,可以直接使用Hashtable,但是要使用HashMap的话就要自己增加同步处理了。
3允不允许null值
Hashtable中,key和value都不允许出现null值,否则会抛出NullPointerException异常。
而在HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。当get()方法返回null值时,即可以表示 HashMap中没有该键,也可以表示该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键, 而应该用containsKey()方法来判断。
4遍历方式的内部实现上不同
Hashtable、HashMap都使用了 Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式 。
5哈希值的使用不同
HashTable直接使用对象的hashCode。而HashMap重新计算hash值。
6 内部实现方式的数组的初始大小和扩容的方式不一样
HashTable中的hash数组初始大小是11,增加的方式是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。

回答的问题有些晚,但依然希望能抛砖引玉

HashMap和Hashtable都实现了Map接口,但决定用哪一个之前先要弄清楚它们之间的分别。主要的区别有:线程安全性,同步(synchronization),以及速度。

继承的父类不同

HashMap和Hashtable不仅作者不同,而且连父类也是不一样的。HashMap是继承自AbstractMap类,而HashTable是继承自Dictionary类。不过它们都实现了同时实现了map、Cloneable(可复制)、Serializable(可序列化)这三个接口

特点及优缺点比较:

  1. HashMap几乎可以等价于Hashtable,除了HashMap是非synchronized的,并可以接受null(HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行)。

  2. HashMap是非synchronized,而Hashtable是synchronized,这意味着Hashtable是线程安全的,多个线程可以共享一个Hashtable;而如果没有正确的同步的话,多个线程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。

  3. 另一个区别是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别。

  4. 由于Hashtable是线程安全的也是synchronized,所以在单线程环境下它比HashMap要慢。如果你不需要同步,只需要单一线程,那么使用HashMap性能要好过Hashtable。

  5. HashMap不能保证随着时间的推移Map中的元素次序是不变的。

内部实现与操作问题

HashTable

  • 底层数组+链表实现,无论key还是value都不能为null,线程安全,实现线程安全的方式是在修改数据时锁住整个HashTable,效率低,ConcurrentHashMap做了相关优化
  • 初始size为11,扩容:newsize = olesize*2+1
  • 计算index的方法:index = (hash & 0x7FFFFFFF) % tab.length

HashMap

  • 底层数组+链表实现,可以存储null键和null值,线程不安全
  • 初始size为16,扩容:newsize = oldsize*2,size一定为2的n次幂
  • 扩容针对整个Map,每次扩容时,原来数组中的元素依次重新计算存放位置,并重新插入
  • 插入元素后才判断该不该扩容,有可能无效扩容(插入后如果扩容,如果没有再次插入,就会产生无效扩容)
  • 当Map中元素总数超过Entry数组的75%,触发扩容操作,为了减少链表长度,元素分配更均匀
  • 计算index方法:index = hash & (tab.length – 1)

HashMap的初始值还要考虑加载因子:

  • 哈希冲突:若干Key的哈希值按数组大小取模后,如果落在同一个数组下标上,将组成一条Entry链,对Key的查找需要遍历Entry链上的每个元素执行equals()比较。
  • 加载因子:为了降低哈希冲突的概率,默认当HashMap中的键值对达到数组大小的75%时,即会触发扩容。因此,如果预估容量是100,即需要设定100/0.75=134的数组大小。
  • 空间换时间:如果希望加快Key查找的时间,还可以进一步降低加载因子,加大初始大小,以降低哈希冲突的概率。

HashMap和HashTable都是用hash算法来决定其元素的存储,因此HashMap和Hashtable的hash表包含如下属性:

  • 容量(capacity):hash表中桶的数量
  • 初始化容量(initial capacity):创建hash表时桶的数量,HashMap允许在构造器中指定初始化容量
  • 尺寸(size):当前hash表中记录的数量
  • 负载因子(load factor):负载因子等于“size/capacity”。负载因子为0,表示空的hash表,0.5表示半满的散列表,依此类推。轻负载的散列表具有冲突少、适宜插入与查询的特点(但是使用Iterator迭代元素时比较慢)

除此之外,hash表里还有一个“负载极限”,“负载极限”是一个0~1的数值,“负载极限”决定了hash表的最大填满程度。当hash表中的负载因子达到指定的“负载极限”时,hash表会自动成倍地增加容量(桶的数量),并将原有的对象重新分配,放入新的桶内,这称为rehashing。

HashMap和Hashtable的构造器允许指定一个负载极限,HashMap和Hashtable默认的“负载极限”为0.75,这表明当该hash表的3/4已经被填满时,hash表会发生rehashing。

“负载极限”的默认值(0.75)是时间和空间成本上的一种折中:

  • 较高的“负载极限”可以降低hash表所占用的内存空间,但会增加查询数据的时间开销,而查询是最频繁的操作(HashMap的get()与put()方法都要用到查询)
  • 较低的“负载极限”会提高查询数据的性能,但会增加hash表所占用的内存开销程序猿可以根据实际情况来调整“负载极限”值。

结论

Hashtable和HashMap有几个主要的不同:线程安全以及速度。仅在你需要完全的线程安全的时候使用Hashtable,而如果你使用Java 5或以上的话,请使用ConcurrentHashMap。

1.HashMap和Hashtable都实现了map接口。
2.HashMap和Hashtable继承类不一致。
3.Hashtable中实现了同步方法,所以在多线程并发环境下是安全的,
但是Hashmap里面没有实现同步方法,在单线程操作中,HashMap比Hashtable要更加快速
4.Hashmap中的key是可以为null的,但是Hashtable的不能为null
5.HashMap的初始长度为16,Hashtable的初始长度为11

1,HashMap 支持 null 键 和null值,而HashMap 在遇到null 是,会抛出 空指针异常。

2,我们说HashTable是同步的,HashMap不是,也就是说HashTable在多线程使用的情况下,不需要做额外的同步 

3, HashMap 实现了Serializable接口,因此它支持序列化,实现了Cloneable接口,能被克隆。 

4,HashTable 继承子Dictionary 类,二HashMap 继承AbstractMap 类,但是二者都实现了Map 接口

5, Hashtable扩容时,将容量变为原来的2倍加1,而HashMap扩容时,将容量变为原来的2倍。 

6,HashTable直接使用对象的hashCode。而HashMap重新计算hash值。 

7,HashTable已经被淘汰了,不要在代码中再使用它。

HashTable 同步 ,不支持null 键 和 值,由于同步导致性能开销,所以不推荐使用
HashMap 线程不安全,基于hash表实现.使用HashMap要添加键值明确定义了 hashCode() 和 equal()可以重写 hashCode() 和 equals() ,为了优化HashMap 空间的使用,您可以调优初始容量和负载因子.其中散列表的冲突处理主要有两种,一是开放定址法,另一种是链表法,HashMap的实现采用的是链表法

commented

知其然知其所以然

补充一点:

大家都提到了 HashTable 不支持 null 的键和值,为什么 HashTable 不支持呢?是出于什么原因和考虑呢?

你有没有发现,只要是用于多线程场景的 MapHashTableConcurrentHashMap都不支持 null 键和 null,这是为什么呢?

回答这个问题前,我们先来思考下:

假设你在使用 HashMap 时,调用 map.get(key) 方法得到的值是 null,是因为这个 key 在 map 里面value = null?还是这个 keymap 里面根本就不存在?

你肯定说,这好办,我调用一下map.contains(key) 不就知道了?

那如果是ConcurrentHashMap呢?在多线程的场景下。假设ConcurrentHashMap允许存放值为nullvalue,此时线程 1 get(key)方法(这个key本来在Map里不存在),返回为 null,线程 2 执行了put(key,null),线程 1 调用contains(key)就会返回 true,这就会出现误导了。

所以总结一下,用于多线程场景的 Map 不允许 null,是为了避免二义性

HashMap:This implementation provides all of the optional map operations, and permits null values and the null key. (The HashMap class is roughly equivalent to Hashtable, except that it is unsynchronized and permits nulls.)