HashSet与TreeSet详解

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

1.集合是JavaAPI所提供的一系列类,用来动态存放多个对象----集合只能存放对象
2.集合与数组的区别是,集合长度可变,且存放元素不受限定,主要是引用类型。
3.集合全部支持泛型,是一种数据安全的用法

集合框架图
虚线框代表接口,实现框为实现类
集合分两大家族:
Collection(接口)家族:存放单一对象
Map(接口)家族:存放键值对(key,value)

先说Collection(接口)家族
Collection接口定义了存取对象的方法,它有两个很常用的子接口
List接口:存放的元素有序且重复
Set接口:存放的元素无序且唯一

List接口的两个重要实现类:ArrayList和LinkedList
这个不是我们要说的重点,就不多BB了,反正想用集合但不知道用什么集合,那就用ArrayList。

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

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

HashSet

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

对!你也许已经发现了:

不同数字的hashcode可能是相同的,在图中也可以看到hashcode相同时,元素是以链表的形式存放在该位置的。
相同数字的hashcode必然相同,比较相同元素的过程被大大简化。

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

   public class Test {
	public static void main(String[] args) {
		Set<Integer> 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<Integer> it = set.iterator();
		while (it.hasNext()) {
			System.out.print(it.next()+"-");
		}
	}
}

在这里插入图片描述
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<Teacher> 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的去重功能。

再来测试下:
在这里插入图片描述
可以看到,我们终于去掉了重复的苍老师。

下面重点来了:

TreeSet

先了解下TreeSet的存储方式
在这里插入图片描述
由于TreeSet的存储特性,可知TreeSet存储特点是唯一且有序。

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

public class Test {
	public static void main(String[] args) {
		Set<Integer> 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<Integer> it = set.iterator();
		while (it.hasNext()) {
			System.out.println(it.next());
		}
	}
}

输出:
在这里插入图片描述
可以看到TreeSet不仅实现了去重,还进行了升序排序。
那么将Integer类对象换为我们定义的Teacher对象呢

public class Test2 {
	public static void main(String[] args) {
		
		Set<Student> 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);
	}
}

在这里插入图片描述
这次不仅仅是无法去重排序了,直接报了个Bug。
那该如何解决呢?
和之前一样,看下Integer类源码

public final class Integer extends Number implements Comparable<Integer> {
	 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<? super K>)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<Student, Integer> map3 = new TreeMap<>(new Comparator<Student>() {
			@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<String> set1 = map.keySet();
		for(String a:set1){
			System.out.println(a+" "+map.get(a));
		}
		System.out.println("============");
		
		//键值对遍历
		Set<Entry<String, Integer>> set = map.entrySet();
		Iterator<Entry<String, Integer>> it = set.iterator();
		while (it.hasNext()) {
			Entry<String, Integer> entry = (Entry<String, Integer>) it.next();
			System.out.println(entry.getKey()+"-->"+entry.getValue());
		}	
  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值