月吖吖

Java、Python、大数据、前端

HashMap和HashTable简介和区别

一、HashMap简介

      HashMap是基于哈希表实现的,每一个元素是一个key-value对,其内部通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长。

      HashMap是非线程安全的,只是用于单线程环境下,多线程环境下可以采用concurrent并发包下的concurrentHashMap。

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

      HashMap存数据的过程是:

      HashMap内部维护了一个存储数据的Entry数组,HashMap采用链表解决冲突,每一个Entry本质上是一个单向链表。当准备添加一个key-value对时,首先通过hash(key)方法计算hash值,然后通过indexFor(hash,length)求该key-value对的存储位置,计算方法是先用hash&0x7FFFFFFF后,再对length取模,这就保证每一个key-value对都能存入HashMap中,当计算出的位置相同时,由于存入位置是一个链表,则把这个key-value对插入链表头。

      HashMap中key和value都允许为null。key为null的键值对永远都放在以table[0]为头结点的链表中。

      了解了数据的存储,那么数据的读取也就很容易就明白了。

      HashMap的存储结构,如下图所示:

 

      图中,紫色部分即代表哈希表,也称为哈希数组,数组的每个元素都是一个单链表的头节点,链表是用来解决冲突的,如果不同的key映射到了数组的同一位置处,就将其放入单链表中。

      HashMap内存储数据的Entry数组默认是16,如果没有对Entry扩容机制的话,当存储的数据一多,Entry内部的链表会很长,这就失去了HashMap的存储意义了。所以HasnMap内部有自己的扩容机制。HashMap内部有:

      变量size,它记录HashMap的底层数组中已用槽的数量;

      变量threshold,它是HashMap的阈值,用于判断是否需要调整HashMap的容量(threshold = 容量*加载因子)    

      变量DEFAULT_LOAD_FACTOR = 0.75f,默认加载因子为0.75

      HashMap扩容的条件是:当size大于threshold时,对HashMap进行扩容  

      扩容是是新建了一个HashMap的底层数组,而后调用transfer方法,将就HashMap的全部元素添加到新的HashMap中(要重新计算元素在新的数组中的索引位置)。 很明显,扩容是一个相当耗时的操作,因为它需要重新计算这些元素在新的数组中的位置并进行复制处理。因此,我们在用HashMap的时,最好能提前预估下HashMap中元素的个数,这样有助于提高HashMap的性能。

      HashMap共有四个构造方法。构造方法中提到了两个很重要的参数:初始容量和加载因子。这两个参数是影响HashMap性能的重要参数,其中容量表示哈希表中槽的数量(即哈希数组的长度),初始容量是创建哈希表时的容量(从构造函数中可以看出,如果不指明,则默认为16),加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度,当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 resize 操作(即扩容)。

      下面说下加载因子,如果加载因子越大,对空间的利用更充分,但是查找效率会降低(链表长度会越来越长);如果加载因子太小,那么表中的数据将过于稀疏(很多空间还没用,就开始扩容了),对空间造成严重浪费。如果我们在构造方法中不指定,则系统默认加载因子为0.75,这是一个比较理想的值,一般情况下我们是无需修改的。

       另外,无论我们指定的容量为多少,构造方法都会将实际容量设为不小于指定容量的2的次方的一个数,且最大值不能超过2的30次方。
 

二、Hashtable简介

      Hashtable同样是基于哈希表实现的,同样每个元素是一个key-value对,其内部也是通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长。

      Hashtable也是JDK1.0引入的类,是线程安全的,能用于多线程环境中。

  Hashtable同样实现了Serializable接口,它支持序列化,实现了Cloneable接口,能被克隆。


 

三、HashMap和Hashtable的区别

1、两者最主要的区别在于Hashtable是线程安全,而HashMap则非线程安全。Hashtable的实现方法里面都添加了synchronized关键字来确保线程同步,因此相对而言HashMap性能会高一些,我们平时使用时若无特殊需求建议使用HashMap,在多线程环境下若使用HashMap需要使用Collections.synchronizedMap()方法来获取一个线程安全的集合(Collections.synchronizedMap()实现原理是Collections定义了一个SynchronizedMap的内部类,这个类实现了Map接口,在调用方法时使用synchronized来保证线程同步,当然了实际上操作的还是我们传入的HashMap实例,简单的说就是Collections.synchronizedMap()方法帮我们在操作HashMap时自动添加了synchronized来实现线程同步,类似的其它Collections.synchronizedXX方法也是类似原理。

2、HashMap可以使用null作为key,不过建议还是尽量避免这样使用。HashMap以null作为key时,总是存储在table数组的第一个节点上。而Hashtable则不允许null作为key。

3、HashMap继承了AbstractMap,HashTable继承Dictionary抽象类,两者均实现Map接口。

4、HashMap的初始容量为16,Hashtable初始容量为11,两者的填充因子默认都是0.75。

5、HashMap扩容时是当前容量翻倍即:capacity*2,Hashtable扩容时是容量翻倍+1即:capacity*2+1。

6、HashMap和Hashtable的底层实现都是数组+链表结构实现。

7、两者计算hash的方法不同: Hashtable计算hash是直接使用key的hashcode对table数组的长度直接进行取模:

 

int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;

 

HashMap计算hash对key的hashcode进行了二次hash,以获得更好的散列值,然后对table数组长度取摸:

 

static int hash(int h) {
     h ^= (h >>> 20) ^ (h >>> 12);
     return h ^ (h >>> 7) ^ (h >>> 4);
 }

static int indexFor(int h, int length) {
     return h & (length-1);
 }

参考上海尚学堂java文章,,获取更多内容或支持,请点击  上海Java培训

阅读更多
个人分类: Java hashmap hashtable
上一篇Angularjs中的缓存以及缓存清理
下一篇Python函数声明以及与其他编程语言数据类型的比较
想对作者说点什么? 我来说一句

j2ee面试题

2012年12月10日 134KB 下载

没有更多推荐了,返回首页

关闭
关闭