HashSet内部是通过HashMap实现。只有使用排序的时候才使用TreeMap。否知使用HashMap。
使用HashSet 举例子: 情景描述 往HashSet集合中put Student对象实例,要求: code name相同的不重复 放入。
HashSet set = new HashSet
set.add(new Student("1", "aa"));
set.add(new Student("2", "aa"));
set.add(new Student("1", "aa"));
System.out.println("set:===" + set);
结果set集合内的元素为3个,没有去处重复的new Student("1","aa") ?为什么呢?
这里由于两个new Student("1","aa")是不一样的Student对象实例(都是new 出来的,对象引用不一样)。而默认的Student类的hashcode是根据对象的引用算的。所以直接认为是两个不一样的对象,直接add进去了。所以需要重写hashcode方法,如果hashcode不一样则直接认为是不同对象,如下:
@Data
class Student {
String code;
String name;
@Override
public int hashCode() {
return code.hashCode() + name.hashCode();
}
public Student(String code, String name) {
this.code = code;
this.name = name;
}
}
结果发现还是不对,还是add进去了3个Student 呢?
这里重写的hashcode是一样的,所以还是add进去了。所以还需要重新equals方法。
其实是有这样一个规定,如果hashcode一样时,则还需要继续调用equals方式看看对象是否相等。所以再需要重写 equals方法如下即可实现:
@Override
public boolean equals(Object o) {
Student s = (Student) o;
if (name.equals(s.getName()) && code.equals(s.getCode())) {
return true;
}
return false;
}
完整test demo:
public class MyClass {
public static void main(String[] args) {
HashSet set = new HashSet<>();
set.add(new Student("1", "aa"));
set.add(new Student("2", "aa"));
set.add(new Student("1", "aa"));
System.out.println("set:===" + set);
}
}
@Data
class Student {
String code;
String name;
@Override
public int hashCode() {
return code.hashCode() + name.hashCode();
}
public Student(String code, String name) {
this.code = code;
this.name = name;
}
}
可以看到如果hashcode不一样就直接认为是不一样的对象,不需要再去equal比较,更加节省时间。
如果new Student("1","aa")、new Student("1","bb")。通过code和name算出的hashcode就可以算是不一样的对象,就不需要再去equals比较。
往往HashSet中存放的对象是否相等的逻辑都需要自己定义,而并不会直接用默认的引用来算,即一般都会重新hashcode和equals方法,而且同时需要重写。以后要注意哦。
HashMap的put和get也类似。
HashMap是底层实现时数组加链表。
A.当put元素时:
1.首先根据put元素的key获取hashcode,然后根据hashcode算出数组的下标位置,如果下标位置没有元素,直接放入元素即可。
2.如果该下标位置有元素(即根据put元素的key算出的hashcode一样即重复了),则需要已有元素和put元素的key对象比较equals方法,如果equals不一样,则说明可以放入进map中。这里由于hashcode一样,所以得出的数组下标位置相同。所以会在该数组位置创建一个链表,后put进入的元素到放链表头,原来的元素向后移动。
B.当get元素时:
根据元素的key获取hashcode,然后根据hashcode获取数组下标位置,如果只有一个元素则直接取出。如果该位置一个链表,则需要调用equals方法遍历链表中的所有元素与当前的元素比较,得到真正想要的对象。
可以看出如果根据hashcdoe算出的数组位置尽量的均匀分布,则可以避免遍历链表的情况,以提高性能。
所以要求重写hashmap时,也要重写equals方法。以保证他们是相同的比较逻辑