博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[置顶] HashMap HashTable HashSet区别剖析
阅读量:6553 次
发布时间:2019-06-24

本文共 6069 字,大约阅读时间需要 20 分钟。

HashMap、HashSet、HashTable之间的区别是Java程序员的一个常见面试题目,在此仅以此博客记录,并深入源代码进行分析:

在分析之前,先将其区别列于下面

1:HashSet底层采用的是HashMap进行实现的,但是没有key-value,只有HashMap的key set的视图,HashSet不容许重复的对象

2:Hashtable是基于Dictionary类的,而HashMap是基于Map接口的一个实现

3:Hashtable里默认的方法是同步的,而HashMap则是非同步的,因此Hashtable是多线程安全的

4:HashMap可以将空值作为一个表的条目的key或者value,HashMap中由于键不能重复,因此只有一条记录的Key可以是空值,而value可以有多个为空,但HashTable不允许null值(键与值均不行)

5:内存初始大小不同,HashTable初始大小是11,而HashMap初始大小是16

6:内存扩容时采取的方式也不同,Hashtable采用的是2*old+1,而HashMap是2*old。

7:哈希值的计算方法不同,Hashtable直接使用的是对象的hashCode,而HashMap则是在对象的hashCode的基础上还进行了一些变化

源代码分析:

对于区别1,看下面的源码

 

//HashSet类的部份源代码public class HashSet
extends AbstractSet
implements Set
, Cloneable, java.io.Serializable{ //用于类的序列化,可以不用管它 static final long serialVersionUID = -5024744406713321676L; //从这里可以看出HashSet类里面真的是采用HashMap来实现的 private transient HashMap
map; // Dummy value to associate with an Object in the backing Map //这里是生成一个对象,生成这个对象的作用是将每一个键的值均关联于此对象,以满足HashMap的键值对 private static final Object PRESENT = new Object(); /** * Constructs a new, empty set; the backing
HashMap instance has * default initial capacity (16) and load factor (0.75). */ //这里是一个构造函数,开构生成一个HashMap对象,用来存放数据 public HashSet() { map = new HashMap
(); }

从上面的代码中得出的结论是HashSet的确是采用HashMap来实现的,而且每一个键都关键同一个Object类的对象,因此键所关联的值没有意义,真正有意义的是键。而HashMap里的键是不允许重复的,因此1也就很容易明白了。

 

对于区别2,继续看源代码如下

 

//从这里可以看得出Hashtable是继承于Dictionary,实现了Map接口public class Hashtable
extends Dictionary
implements Map
, Cloneable, java.io.Serializable {
//这里可以看出的是HashMap是继承于AbstractMap类,实现了Map接口//因此与Hashtable继承的父类不同public class HashMap
extends AbstractMap
implements Map
, Cloneable, Serializable

区别3,找一个具有针对性的方法看看,这个方法就是put

 

 

//Hashtable里的向集体增加键值对的方法,从这里可以明显看到的是//采用了synchronized关键字,这个关键字的作用就是用于线程的同步操作//因此下面这个方法对于多线程来说是安全的,但这会影响效率   public synchronized V put(K key, V value) {	// Make sure the value is not null	//如果值为空的,则会抛出异常	if (value == null) {	    throw new NullPointerException();	}	// Makes sure the key is not already in the hashtable.	Entry tab[] = table;	//获得键值的hashCode,从这里也可以看得出key!=null,否则的话会抛出异常的呦	int hash = key.hashCode();	//获取键据所在的哈希表的位置	int index = (hash & 0x7FFFFFFF) % tab.length;	//从下面这个循环中可以看出的是,内部实现采用了链表,即桶状结构	for (Entry
e = tab[index] ; e != null ; e = e.next) { //如果向Hashtable中增加同一个元素时,则会重新更新元素的值 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; }
//HashMap中的实现则相对来说要简单的很多了,如下代码//这里的代码中没有synchronize关键字,即可以看出,这个关键函数不是线程安全的    public V put(K key, V value) {    //对于键是空时,将向Map中放值一个null-value构成的键值对    //对值却没有进行判空处理,意味着可以有多个具有键,键所对应的值却为空的元素。        if (key == null)            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; }

区别4在上面的代码中,已经分析了,可以再细看一下

 

区别5内存初化大小不同,看看两者的源代码:

 

public Hashtable() {      //从这里可以看出,默认的初始化大小11,这里的11并不是11个字节,而是11个Entry,这个Entry是      //实现链表的关键结构      //这里的0.75代表的是装载因子			this(11, 0.75f);    }
//这里均是一些定义    public HashMap() {    //这个默认的装载因子也是0.75        this.loadFactor = DEFAULT_LOAD_FACTOR;    //默认的痤为0.75*16        threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);    //这里开始是默认的初始化大小,这里大小是16        table = new Entry[DEFAULT_INITIAL_CAPACITY];        init();    }

从上面的代码中,可以看出的是两者的默认大小是不同的,一个是11,一个是16

 

区别6内存的扩容方式,看一看源代码也是很清楚的,其实区别是不大的,看到网上一哥们写的,说两者有区别,其实真正深入源码,区别真不大,一个是2*oldCapacity+1, 一个是2*oldCapacity,你说大吗:)

 

//Hashtable中调整内存的函数,这个函数没有synchronize关键字,但是protected呦protected void rehash() {	//获取原来的表大小	int oldCapacity = table.length;	Entry[] oldMap = table;  //设置新的大小为2*oldCapacity+1	int newCapacity = oldCapacity * 2 + 1;	//开设空间	Entry[] newMap = new Entry[newCapacity];  //以下就不用管了。。。	modCount++;	threshold = (int)(newCapacity * loadFactor);	table = newMap;	for (int i = oldCapacity ; i-- > 0 ;) {	    for (Entry
old = oldMap[i] ; old != null ; ) { Entry
e = old; old = old.next; int index = (e.hash & 0x7FFFFFFF) % newCapacity; e.next = newMap[index]; newMap[index] = e; } } }
//HashMap中要简单的多了,看看就知道了 void addEntry(int hash, K key, V value, int bucketIndex) {	Entry
e = table[bucketIndex]; table[bucketIndex] = new Entry
(hash, key, value, e); //如果超过了阀值 if (size++ >= threshold) //就将大小设置为原来的2倍 resize(2 * table.length); }

是吧,没什么区别吧

 

对于区别7的哈希值计算方法的不同,源码面前,同样是了无秘密

 

//Hashtable中可以看出的是直接采用关键字的hashcode作为哈希值	int hash = key.hashCode();	//然后进行模运算,求出所在哗然表的位置 	int index = (hash & 0x7FFFFFFF) % tab.length;
//HashMap中的实现//这两行代码的意思是先计算hashcode,然后再求其在哈希表的相应位置      int hash = hash(key.hashCode());int i = indexFor(hash, table.length);

上面的HashMap中可以看出关键在两个函数hash与indexFor

 

源码如下:

 

static int hash(int h) {        // This function ensures that hashCodes that differ only by        // constant multiples at each bit position have a bounded        // number of collisions (approximately 8 at default load factor).        //这个我就不多说了,>>>这个是无符号右移运算符,可以理解为无符号整型        h ^= (h >>> 20) ^ (h >>> 12);        return h ^ (h >>> 7) ^ (h >>> 4);    }
//求位于哈希表中的位置    static int indexFor(int h, int length) {        return h & (length-1);    }

 

转载地址:http://cjjco.baihongyu.com/

你可能感兴趣的文章
canal源码分析——DirectLogFetcher源码分析
查看>>
Thrift0.9.2 安装
查看>>
基于 Tile 连接 Row-Store 和 Column-Store
查看>>
新办公环境之浏览器上不了网
查看>>
只要有基础的access_token和用户openid就可以判断用户是否关注该公众号
查看>>
oracle备份
查看>>
linux 下tr命令
查看>>
Remove Element
查看>>
LNMP架构之访问日志、日志切割、静态文件不记录及过期时间设置
查看>>
Hadoop之HDFS基本概念
查看>>
为WordPress主题加入一个可选的定制样式表
查看>>
jquery旋转木马插件SLICK
查看>>
java web 生成验证码
查看>>
在js中实现对数字每个3个增加一个逗号
查看>>
EL表达式的基本语法
查看>>
Linux solr安装
查看>>
python爬取淘宝商品信息并加入购物车
查看>>
BeanPropertyRowMapper
查看>>
javascript-引用类型的使用
查看>>
新手学习在Ubuntu 14.04搭建Javaweb网站(4)--安装JDK
查看>>