HashSet底层实现原理
- 当然Hashset是基于HashMap实现 的,下面来看一下它的构造方法
//Constructs a new, empty set;
//the backing HashMap instance has default
// initial capacity (16) and load factor (0.75).
public HashSet() {
map = new HashMap<>();
}
上面注释的意思就是:默认构造函数是构建一个初始容量为16,负载因子为0.75 的HashMap。
- 往HashSet集合中放入元素 ,会调用add()方法,实际上是放到了HashMap(存储的是key-vakue)中的key中
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
可以看到add()方式实际上也是调用了构造方法中的构建的map对象中的 put()方法 。
大家都知道HashMap中存储的是键值对的形式,那为什么HashSet能使用HashMap 的put()方法呢?
其实是 所有放入 HashSet 中的集合元素实际上由 HashMap 的 key 来保存,而 HashMap 的 value 则存储了一个 PRESENT,它是一个静态的 Object 对象。
点击PRESENT进去后会发现
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
-
当我们想使用HashMap和HashSet存储一个类的对象时,必须要重写该类的==equals()方法和 hashCode() 方法,但是存储一个int 类型的元素时,我们不需要重写Integer类的equals()==方法和 hashCode() 方法。是因为Integer中已经重写的这两个方法,
-
如果要放入一个你自己写的类,那一定要重写这个类的==equals()==方法和 hashCode() 方法。
-
那为什要重写这两个方法呢?
-
首先我们要知道HashSet不能存入重复的元素。如果不重写会不会违背这个条件呢?
public static void main(String[] args) { HashSet<Student> set2 = new HashSet<Student>(); set2.add(new Student("ccc",15)); set2.add(new Student("aaa",14)); set2.add(new Student("ccc",15)); set2.add(new Student("bbb",21)); for(Student i:set2) { System.out.println(i.getName()+"---"+i.getAge()); } } //结果 ccc---15 ccc---15 bbb---21 aaa---14
接下来就验证了面的猜想。因为自己定义的Student没有重写这两个方法,所以存入了重复的值。
- 先说说为什么重写hashcode?
1.1 首先进入add
1.2 接着进入put方法
1.3 接着进入putVak方法
此处可以看到HashSet在进行添加底层使用到了hash方法,而hash值的获取实际上是在hashCode基础上的一系列操作,如果不重写hashcode,用的则是从Object继承下来的hashCode,而这个hashCode每次new一个对象就会有不同的值;所以就造成了上面的student中的即使 内容一样 但还是被认为是不同的元素,因为每次存入时 都是new了一个新对象,就造成了hash值一直不同。
- 接下来说为什么重写equals?
对于自定义类需要手动实现hashCode,当判断hash值一致后还会调用equals再次进行检测,判断内容是否一致;
为什么还要用equals比较呢?
因为hash值毕竟只是一个逻辑值,不同的对象有几率出现相同的hash值(称为哈希碰撞)。如果不同的对象出现了相同的hash值就需要使用equals进行再次判断