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()方法的详细,我将在学习后在后面的文章中写出。