HashSet与TreeSet详解

先介绍下Java中的集合的概念

1.集合是JavaAPI所提供的一系列类,用来动态存放多个对象----集合只能存放对象

2.集合与数组的区别是,集合长度可变,且存放元素不受限定,主要是引用类型。

3.集合全部支持泛型,是一种数据安全的用法

集合框架图

HashSet与TreeSet详解_第1张图片

集合分两大家族:

Collection(接口)家族:存放单一对象

Map(接口)家族:存放键值对(key,value)

先说Collection(接口)家族

Collection接口定义了存取对象的方法,它有两个很常用的子接口

List接口:存放的元素有序且重复

Set接口:存放的元素无序且唯一

List接口的两个重要实现类:ArrayList和LinkedList

这个不是我们要说的重点,就不多BB了,反正想用集合但不知道用什么集合,那就用ArrayList。

Set接口的两个重要实现类:HashSet和TreeSet

下面看看他们的具体存储原理:

HashSet

HashSet与TreeSet详解_第2张图片

图中简单模拟了下hash算法,即数字转为二进制后与一串特定的二进制序列相与,得到hashcode。

对!你也许已经发现了:

不同数字的hashcode可能是相同的,在图中也可以看到hashcode相同时,元素是以链表的形式存放在该位置的。

相同数字的hashcode必然相同,比较相同元素的过程被大大简化。

下面我们来检测下HashSet存放元素的唯一性

  public class Test {

public static void main(String[] args) {

Set set = new HashSet<>();

set.add(11);

set.add(22);

set.add(44);

set.add(11);

System.out.println(set);

//=======遍历1:基本for-无下标不能使用

//-----------增强for(实现原理就是迭代器)

System.out.println();

for(Integer a:set)

System.out.print(a+"-");

System.out.println();

//迭代器

Iterator it = set.iterator();

while (it.hasNext()) {

System.out.print(it.next()+"-");

}

}

}

HashSet与TreeSet详解_第3张图片

HashSet确实去掉了重复元素11

这里存放的是Integer类型对象,那么如果存放我们自己定义的对象呢?

定义一个Teacher类:

public class Teacher {

private String name;

private int age;

public int getAge() {

return age;

}

public void setAge(int age) {

this.age = age;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

@Override

public String toString() {

return "Teacher [name=" + name + age+"]";

}

public Teacher() {

super();

// TODO Auto-generated constructor stub

}

public Teacher(String name, int age) {

super();

this.name = name;

this.age = age;

}

}

我们再来测试下:

public class Test2 {

public static void main(String[] args) {

Set set = new HashSet<>();

set.add(new Teacher("苍老师",18));

set.add(new Teacher("陈老师",18));

set.add(new Teacher("苍老师",18));

System.out.println(set);

}

}

输出结果:

这次输出了两个苍老师,HashSet居然没有去重。

这是为什么呢?

我们注释掉Teacher类中的toString方法,再看下输出

我们看下输出,可以发现输出的对象地址都是不同的(尽管存放的都是苍老师)

因为我们存放的元素是对象,而不同的对象其地址肯定是不同的,而hashcode也是用地址去计算的,不同地址计算出来的hashcode当然也不同。

那么问题来了,我们上面存放的Integer对象,为什么就没出现这样的问题呢?

我们看下Integer类的源码

  public Integer(int value) {

            this.value = value;

        }

    @Override

        public int hashCode() {

            return Integer.hashCode(value);

        }

  public boolean equals(Object obj) {

        if (obj instanceof Integer) {

            return value == ((Integer)obj).intValue();

        }

        return false;

    }

原来Integer类内部悄悄重写了hashcode和equals方法,改为用value值去获取hashcode和比较。

那么给我们的Teacher类也实现hashcode和equals方法吧。

但怎么实现呢,大佬可以参照源码实现的方式自己写。

懒人方法:eclipse已经给我们提供自动实现了,就和getter和setter方法一样,按下alt+shift+s,即可看到Generate hashCode() and equals()选项,一键点击即可在我们自己写的类里实现hashSet的去重功能。

再来测试下:

HashSet与TreeSet详解_第4张图片

可以看到,我们终于去掉了重复的苍老师。

下面重点来了:

TreeSet

先了解下TreeSet的存储方式

HashSet与TreeSet详解_第5张图片

由于TreeSet的存储特性,可知TreeSet存储特点是唯一且有序。

和前面一样,我们先来测试Java自定义的Integer类对象及TreeSet的遍历

public class Test {

public static void main(String[] args) {

Set set = new TreeSet<>();

set.add(1);

set.add(3);

set.add(2);

set.add(1);//唯一

System.out.println(set);

//遍历也是不能用基本for

//可以进行增强for和迭代器遍历

System.out.println("===============");

for(Integer i:set){

System.out.println(i);

}

System.out.println("===============");

Iterator it = set.iterator();

while (it.hasNext()) {

System.out.println(it.next());

}

}

}

输出:

HashSet与TreeSet详解_第6张图片

可以看到TreeSet不仅实现了去重,还进行了升序排序。

那么将Integer类对象换为我们定义的Teacher对象呢

public class Test2 {

public static void main(String[] args) {

Set set = new TreeSet<>();

//ClassCastException: Student cannot be cast to Comparable

set.add(new Student("zs",20));

set.add(new Student("ls",30));

set.add(new Student("ww",20));

set.add(new Student("zs",80));

System.out.println(set);

}

}

HashSet与TreeSet详解_第7张图片

这次不仅仅是无法去重排序了,直接报了个Bug。

那该如何解决呢?

和之前一样,看下Integer类源码

public final class Integer extends Number implements Comparable {

public int compareTo(Integer anotherInteger) {

        return compare(this.value, anotherInteger.value);

    }

}

原来Integer类实现了Comparable接口里的compareTo方法,

compareTo是Comparable接口定义的方法,那么里面调用的compare方法又是什么呢?

由于Integer类连compare方法都重写了,那该如何查看compare方法源码呢

这里补充个知识点,HashSet和TreeSet都是调用Map家族下的HashMap和TreeMap中的方法来实现的,所以我们得去TreeMap中找compare源码

final int compare(Object k1, Object k2) {

        return comparator==null ? ((Comparable)k1).compareTo((K)k2)

            : comparator.compare((K)k1, (K)k2);

    }

这是个三目运算,当comparator为空时,执行第一个表达式,否则执行第二个表达式。

即实现了comparator接口的话,就调用其compare方法实现比较大小;而未实现comparator接口就调用comparable接口的compareTo方法。

也就是说源码给我们提供了两种比较方法,一种是实现comparator接口,另一种是实现comparable接口,这也就是我们所说的比较器法和自然排序法。

自然排序法

方式1:自然排序法------》自定义类实现Comparable接口

public class Student implements Comparable{

private String name;

private int age;

public int getAge() {

return age;

}

public void setAge(int age) {

this.age = age;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

@Override

public String toString() {

return "Student [name=" + name + ", age=" + age + "]";

}

public Student() {

super();

// TODO Auto-generated constructor stub

}

public Student(String name, int age) {

super();

this.name = name;

this.age = age;

}

@Override

public int compareTo(Student o) {

if(o.age==this.age)

return o.name.compareTo(this.name);//升序

//return o.name.compareTo(this.name);//降序

return o.age - this.age; //按年龄降序,相同则按名字首字母升序排列

    }

}

方式2:比较器法------》自定义实现比较器comparator类

这里用匿名内部类实现:

//匿名内部类

Map map3 = new TreeMap<>(new Comparator() {

@Override

public int compare(Student o1, Student o2) {

if(o1.getName().equals(o2.getName()))

return o1.getAge()-o2.getAge();

return o1.getName().compareTo(o2.getName());

}

});

虽是TreeMap集合,但TreeSet与其除相差无几

Map集合中的HashMap、TreeMap与上面说的HashSet、TreeSet实现方式大致相同。

值得一提的就是Map集合的遍历了

//遍历

Set set1 = map.keySet();

for(String a:set1){

System.out.println(a+" "+map.get(a));

}

System.out.println("============");

//键值对遍历

Set> set = map.entrySet();

Iterator> it = set.iterator();

while (it.hasNext()) {

Entry entry = (Entry) it.next();

System.out.println(entry.getKey()+"-->"+entry.getValue());

}

你可能感兴趣的:(HashSet与TreeSet详解)