Java集合框架中treeSet和hashSet的坑!

我以两道题目的形式来引入问题,分析问题,解决问题。

题目一:分析下列程序片段是否会报错,如果报错,报什么类型的错?

public class test {
    public static void main(String[] args) {
        TreeSet treeSet = new TreeSet();
        treeSet.add(new huangs());
    }
}
class huangs{

}

分析&解答:TreeSet实现了Set接口,是集合框架中为数不多的能对集合中元素进行排序的集合类。那么为什么它可以这种排序功能了,分析其源码才知道,它将元素转化为Comparable类型,调用的其compare方法实现的这种排序。

所以显然题目一的答案已经水落石出———会报错!那么报什么类型的错了,显然经过前面的分析,他会报类的转换错误,即ClassCastException。

原因是huangs类没有实现Comparable接口,也没有在treeSet的构造器中传入指定的比较器。

报错信息如下图所示

TreeSet源码如下图所示

 

 

 

 

 

 题目二:下面这段代码的输出是什么?

public class HomeWork4 {
    public static void main(String[] args) {
        HashSet hashSet = new HashSet();
        Person p1 = new Person(1001,"AA");
        Person p2 = new Person(1002,"BB");
        hashSet.add(p1);
        hashSet.add(p2);
        p1.name="CC";
        hashSet.remove(p1);
        System.out.println(hashSet);
        hashSet.add(new Person(1001,"CC"));
        System.out.println(hashSet);
        hashSet.add(new Person(1001,"AA"));
        System.out.println(hashSet);
    }
}
//Person类重写了equals和hashcode方法
class Person{
    int id;
    String name;

    public Person(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return id == person.id && Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name);
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

这题难度很大,没有仔细看过源码的,或者对底层结构不清楚的一般都会弄错!!!

分析&解答:hashSet底层是hashMap,存储的是key-value的双列键值对,只不过hashSet的value存储的是一个固定值。底层的结构是table数组+结点类型为node的链表+红黑树。

添加元素是先通过key值计算其hash值,决定其在table数组上索引,若该索引位置上无元素,则直接存放;若有元素,即产生了hash碰撞,依次比较链表中元素(equals方法),相同则加入失败(要区别于hashmap,它是相同就进行value值的替换),不相同就插入到链表表尾,然后判断是否需要树化(树化条件:链表长度>=8,table数组长度>=64)。

此题中先加入p1,p2,因为id和name都不同所以不会散列到同一位置,下一步将p1的name改为“CC”,此时p1本应该不属于现在的位置,因为name的变化会导致hash值变化,从而导致索引的变化。因此删除p1会根据id和新的name重新计算索引,而新的索引并没有元素,所有删除失败。之后又加入了一个新的person,其值和p1一样,所有其会散列到别的不同于p1的位置,所以新对象加入成功。当加入最后一个对象时,因为其属性和老的p1一样,所以hashcode一样,会被散列到和p1相同的位置,但是由于name不一样,equals方法返回为false,即不是同一个对象,会被添加进链表,并且是链接在p1后面。

运行结果如下,你做对了嘛!

 

 底层结构如下所示:

 

posted @ 2021-07-29 17:36  huangs154  阅读(187)  评论(0)    收藏  举报