HashSet和HashMap都是Collection框架的一部分,它们让我们能够使用对象的集合。Collection框架主要有Set接口、List接口和Queue接口。Set集合不允许对象有重复的值;List允许重复,并可以对集合中的对象进行索引;Queue则是FCFS算法(First Come First Serve)。
HashSet
HashSet实现了Set接口,它不允许集合中有重复的值。当我们使用HashSet时,首先就是要确保在将对象储存进HashSet之前,重写对象的equal()和hashCode()方法,只有这样才能比较对象的值是否相等,以确保HashSet中没有储存相等的对象。
如果没有重写,则采用默认实现。那么就会使用从Object继承来的方法,即比较两个对象的引用(地址)是否相同。
HashMap
HashMap实现了Map接口,Map接口对键值对进行映射。Map中不允许出现重复的键,Map接口有两个基本实现,HashMap和TreeMap。TreeMap可以保存对象的排列顺序,而HashMap不行。
HashMap允许键和值均为null,同时它是非synchronized的,是线程不安全的。但是Collection框架提供的方法能够保证HashMap是同步的。保证多个线程访问HashMap时,只有一个线程能够更改Map。
HashSet和HashMap的区别
HashSet | HashMap |
实现Set接口 | 实现Map接口 |
使用add()方法添加元素 | 使用put()方法添加键值对 |
HashSet使用成员对象来计算hashCode值,两个对象的hashCode值可能相同,所以equals()方法要判断对象是否相等 | HashMap使用键对象来计算hashCode值 |
HashSet较HashMap来说慢 | HashMap比较快,因为使用唯一的键来获取对象 |
区别总括
1、HashSet底层实现采用的是HashMap实现的,但是没有key-value,只有HashMap的key。由于HashMap不允许键重复,所以HashSet也不允许出现重复值。
2、Hashtable是基于Dictionary类的,而HashMap是基于Map接口的一个实现。
3、Hashtable默认的方法是同步的,而HashMap是非同步的,所以Hashtable是线程安全的,而HasnMap不是。
4、HashMap可以将空值作为一个表的条目的key或者value,HashMap由于键不能重复,因此只有一条记录的key可以是空值,而value可以多个为空。但是Hashtable无论key还是value都不可以为null。
5、内存初始化不同,Hashtable初始化大小为11,而HashMap初始大小是16
6、内存扩容采取方式不同,Hashtable是2*old+1,HashMap是2*old
7、hashCode计算方式不一样,Hashtable直接使用对象的hashCode,而HashMap在此基础上做了一些变化。
PS:Hashtable的”t”是小写的,在windows下,如果使用HashTable会看到报错,因为windows文件系统对大小写不敏感,而Java编译器却敏感。
源码分析
//HashSet部分源码
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
static final long serialVersionUID = -5024744406713321676L;
//的确是使用HashMap实现
private transient HashMap<E,Object> 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 <tt>HashMap</tt> instance has
* default initial capacity (16) and load factor (0.75).
*/
//构造函数,生成一个HashMap对象,来存放数据
public HashSet() {
map = new HashMap<>();
}
从上面的代码我们可以看出,HashSet的确是通过HashMap来实现的,而且每一个键都关联同一个Object类的对象,也就是说key对应的value在HashSet里面是没有意义的。由于HashMap的键是不允许重复的,所以HashSet的不能重复也就很好解释了。
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable {
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
可以看到Hashtable继承自Dictionary类,实现Map接口;而HashMap继承自AbstractMap类,实现Map接口。
public Hashtable() {
//从这里可以看出,初始化默认大小为11个Entry,而Entry则是实现
//链表的一个结构
//0.75代表装载因子
this(11, 0.75f);
}
/**
* Constructs an empty <tt>HashMap</tt> with the default initial capacity
* (16) and the default load factor (0.75).
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
从注释我们可以看出,默认的大小是16个Entry,装载因子也是0.75。至于扩容方式,在Hashtable的rehash()方法中、HashMap的addEntry()方法中都可以清楚的看到区别,但是实际上也没啥区别吧。
关于hashCode的计算方法,在源码面前也无所遁形:
//Hashtable中的实现
//直接使用key的hashCode作为哈希值
int hash = key.hashCode();
//然后进行求模运算
int index = (hash & 0x7FFFFFFF) % tab.length;
//HashMap中的实现
//先计算hashCode
int hash = hash(key);
//计算在哈希表中的相应位置
bucketIndex = indexFor(hash, table.length);
关于HashMap中hash()方法的详细,我将在学习后在后面的文章中写出。