前言:本文介绍Set系列集合两种常用结构,HashSet以及TreeSet集合
提前说明:Set系列集合具有的共性方法与Collection完全一致,本文不再额外列举
可以参考:菜鸟猿大战Java之集合框架系列(一)
HashSet
的底层数据结构是哈希表,哈希表按照元素的哈希值大小顺序进行存储,而非元素的实际存入顺序。HashSet
是非线程同步的
哈希表特点: 存储时先比较两元素哈希值是否相同,若相同,再比较两元素是否为同一对象[利用元素自身的equals
方法]
构造方法:
HashSet()
构造一个新的空 set,其底层 HashMap 实例的默认初始容量是 16,加载因子是 0.75。HashSet(Collection
extends E>
c)
构造一个包含指定 collection 中的元素的新 set。HashSet(int initialCapacity)
构造一个新的空 set,其底层 HashMap 实例具有指定的初始容量和默认的加载因子(0.75 )。HashSet(int initialCapacity, float loadFactor)
构造一个新的空 set,其底层 HashMap 实例具有指定的初始容量和指定的加载因子。
特性方法:
Object clone()
返回此 HashSet 实例的浅表副本:并没有复制这些元素本身。
说明:浅表复制就是仅复制类中的值类型成员,深表复制就是复制类中的值类型成员和引用类型成员。
场景描述:自定义Teacher类,将其多个实例化对象加入HashSet集合对象中,且要求不能存入多个相同元素。之后演示元素的取出操作。
步骤:
1.复写自定义对象的
hashCode)()
方法(本例中可以返回姓名字符串的hashCode
加上年龄组成的int数据),保证每个对象哈希值的唯一性 (也可以在相加之前进行age*39
之类的操作,尽量保证最终返回哈希值的唯一性,提高效率)2.复写自定义对象的
equals()
方法(本例可认为年龄和姓名一致的两个对象为一个对象),以保证HashSet
中的元素唯一性
示例代码:
import java.util.HashSet;
import java.util.Iterator;
//自定义作为存储对象的Teacher类
class Teacher{
private String name;
private int age;
//构造方法
public Teacher(String name,int age){
this.name=name;
this.age=age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
//重写equals方法
public boolean equals(Object o){
if(!(o instanceof Teacher))
return false;
//将传入对象强转为Teacher对象
Teacher t=(Teacher)o;
if(t.getAge()==this.age&&t.getName().equals(this.name))
return true;
else
return false;
}
//重写hashCode方法
public int hashCode(){
return name.hashCode()+age*39;
}
}
//测试用类
public class HashSetDemo {
public static void main(String[] args) {
//新建HashSet集合装载Teacher对象
HashSet hs=new HashSet();
hs.add(new Teacher("bill",20));
hs.add(new Teacher("bill",20));
hs.add(new Teacher("tom",30));
hs.add(new Teacher("gary",25));
hs.add(new Teacher("gary",25));
hs.add(new Teacher("sythen",20));
//利用迭代器对HashSet中的元素进行取出
Iterator it=hs.iterator();
while(it.hasNext()){
Teacher t=(Teacher)it.next();
System.out.println("姓名:"+t.getName()+"----"+"年龄:"+t.getAge());
}
}
}
程序运行结果:
姓名:gary—-年龄:25
姓名:sythen—-年龄:20
姓名:tom—-年龄:30
姓名:bill—-年龄:20
说明:从元素的取出结果可以看出HashSet集合的元素取出顺序与其存入顺序并不相同。
总结:
1.HashSet保证元素唯一性的方法:通过元素的两个方法,hashCode和equals()来完成。若元素的hashCode值相同,才会调用equals()判断是否为true;若元素的hashCode值不同,则不会调用equals()方法
2.实际开发中要放置于集合中的自定义对象一般都要复写hashCode()和equals()方法!!
3.HashSet判断元素是否存在和删除元素等操作,依赖的方法也是是元素的hashCode()
和equals()
方法
特点:
1.可以对set
集合中的元素进行排序,但并不一定是加入顺序,默认为根据元素的自然顺序排序
2.TreeSet
底层数据结构:二叉树
3.保证元素唯一性的依据:compareTo()
方法返回值为0
构造方法
TreeSet()
构造一个新的空 set,该 set 根据其元素的自然顺序进行排序。TreeSet(Collection
extends E>
c)
构造一个包含指定 collection 元素的新 TreeSet,它按照其元素的自然顺序进行排序。TreeSet(Comparator
super E>
comparator)
构造一个新的空 TreeSet,它根据指定比较器进行排序。TreeSet(SortedSet
s)
构造一个与指定有序 set 具有相同映射关系和相同排序的新 TreeSet。
特性方法:
E ceiling(E e)
返回此 set 中大于等于给定元素的最小元素;如果不存在这样的元素,则返回 null。
E floor(E e)
返回此 set 中小于等于给定元素的最大元素;如果不存在这样的元素,则返回 null。E higher(E e)
返回此 set 中严格大于给定元素的最小元素;如果不存在这样的元素,则返回 null。
E lower(E e)
返回此 set 中严格小于给定元素的最大元素;如果不存在这样的元素,则返回 null。E first()
返回此 set 中当前第一个(最低)元素。
E last()
返回此 set 中当前最后一个(最高)元素。E pollFirst()
获取并移除第一个(最低)元素;如果此 set 为空,则返回 null。
E pollLast()
获取并移除最后一个(最高)元素;如果此 set 为空,则返回 null。Object clone()
返回 TreeSet 实例的浅表副本。Comparator
super E>
comparator()
返回对此 set 中的元素进行排序的比较器;如果此 set 使用其元素的自然顺序,则返回 null。Iterator
descendingIterator()
返回在此 set 元素上按降序进行迭代的迭代器。NavigableSet
descendingSet()
返回此 set 中所包含元素的逆序视图。SortedSet
headSet(E toElement)
返回此 set 的部分视图,其元素严格小于 toElement。
NavigableSetheadSet(E toElement, boolean inclusive)
返回此 set 的部分视图,其元素小于(或等于,如果 inclusive 为 true)toElement。SortedSet
tailSet(E fromElement)
返回此 set 的部分视图,其元素大于等于 fromElement。
NavigableSettailSet(E fromElement, boolean inclusive)
返回此 set 的部分视图,其元素大于(或等于,如果 inclusive 为 true)fromElement。NavigableSet
subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive)
返回此 set 的部分视图,其元素范围从 fromElement 到 toElement。
SortedSetsubSet(E fromElement, E toElement)
返回此 set 的部分视图,其元素从 fromElement(包括)到 toElement(不包括)。
场景描述:
自定义Teacher类,将其多个实例化对象加入TreeSet集合对象中,且要求不能存入多个相同元素。存入过程需要根据Teacher对象的年龄进行排序,之后演示元素的取出操作。
Comparable
:此接口强行对实现它的每个类的对象进行整体排序。这种排序被称为自然排序,类的 compareTo
方法被称为它的自然比较方法。
自然比较方法:
int compareTo(T o)
比较此对象与指定对象的顺序。
返回:负整数、零或正整数,根据此对象是小于、等于还是大于指定对象。
说明:
1.要存入
TreeSet
中的自定义对象必须实现Comparable
接口,以便具有可排序性;并且为了获取有意义的排序方式应该在类中实现compareTo()
方法2.当
compareTo
方法返回0时,在该集合中认为进行比较的两个对象为同一对象,两者仅有第一个会存入TreeSet
中3.针对条目二中的情况,应该在主要条件之外添加次要判断条件(如本例中的姓名排序[使用字符串的compareTo(String s)方法])
4.以上是
TreeSet
实现可排序的第一种方式,即让元素本身直接具备可比较性
示例代码:
import java.util.Iterator;
import java.util.TreeSet;
class Teacher implements Comparable{
private String name;
private int age;
//构造方法
public Teacher(String name,int age){
this.name=name;
this.age=age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
//重写compareTo方法
public int compareTo(Teacher t) {
if(this.age>t.getAge())
return 1;
else if(this.agereturn -1;
else{
//当两个对象年龄相同时将姓名作为次级排序依据
return this.name.compareTo(t.getName());
}
}
}
//测试用类
public class TreeSetDemo {
public static void main(String[] args) {
//新建TreeSet对象装载Teacher对象
TreeSet ts=new TreeSet();
ts.add(new Teacher("bill",20));
ts.add(new Teacher("bill",20));
ts.add(new Teacher("tom",30));
ts.add(new Teacher("gary",25));
ts.add(new Teacher("gary",25));
ts.add(new Teacher("sythen",20));
//利用迭代器进行取出
Iterator it=ts.iterator();
while(it.hasNext()){
Teacher t=(Teacher)it.next();
System.out.println("姓名:"+t.getName()+"----"+"年龄:"+t.getAge());
}
}
}
程序运行结果:
姓名:bill—-年龄:20
姓名:sythen—-年龄:20
姓名:gary—-年龄:25
姓名:tom—-年龄:30
目的:减少比较次数,提高执行效率
说明:
1.利用二叉树的特点,只要将
compareTo()
方法实现为每次返回1,则取出时的顺序为就为元素存入的顺序2.同理,只要将compareTo方法实现为每次返回-1,则取出时的顺序就为为元素存入顺序的反序
使用场景:当元素本身不具备比较性时,或者其具备的比较性不是业务所需要的,这时就需要让集合本身具备比较性。定义一个比较器类实现Comparator
接口,并复写compare()
方法。将比较器对象作为参数传递给TreeSet
的构造函数。
在集合初始化时,就让其拥有比较方式:
使用到的TreeSet构造方法:
TreeSet(Comparator
super E>
comparator)
构造一个新的空 TreeSet,它根据指定比较器进行排序
说明:
1.在自定义比较器类中实现
Comparator
接口,在该类中重写compare(Object o1,Object o2)
方法
2.实例化该自定义对象,作为参数新建TreeSet集合对象
3.也可以使用匿名内部类的方式对TreeSet的构造函数传递参数
4.这也是实现TreeSet
排序的第二种方式,即利用比较器实现集合内元素的排序
总结:
1.其实这就是把原来在自定义类中对compareTo()
方法的重写转移到集合的实例化过程中。即将相关的判断过程写在实现了Comparator
接口的自定义类的compare
方法中
2.若两种排序都存在,则程序会以比较器的排序方式为主
3.基本类型(如int)的包装类(Interger)也实现了comparable
方法
需求描述:通过比较器类实现Teacher对象按照姓名排序,次级排序依据为Teacher对象的年龄大小。
示例代码:
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;
//...省略Teacher类
//自定义比较器类
class OwnComparator implements Comparator{
//重写compare方法
public int compare(Teacher o1, Teacher o2) {
if(o1.getName().equals(o2.getName()))
return new Integer(o1.getAge()).compareTo(new Integer(o2.getAge()));
return o1.getName().compareTo(o2.getName());
}
}
//自定义测试类
public class OwnComparatorDemo {
public static void main(String[] args) {
//实例化自定义选择器对象
OwnComparator oc=new OwnComparator();
//实例化TreeSet承载Teacher对象
TreeSet ts=new TreeSet(oc);
ts.add(new Teacher("bill",20));
ts.add(new Teacher("bill",20));
ts.add(new Teacher("tom",30));
ts.add(new Teacher("gary",25));
ts.add(new Teacher("gary",25));
ts.add(new Teacher("sythen",20));
//利用Iterator对元素进行取出
Iterator it=ts.iterator();
while(it.hasNext()){
Teacher t=it.next();
System.out.println("姓名"+t.getName()+"----"+"年龄"+t.getAge());
}
}
}
程序运行结果:
姓名:bill—-年龄:20
姓名:gary—-年龄:25
姓名:sythen—-年龄:20
姓名:tom—-年龄:30
场景描述:将字符串存入TreeSet中,并按照字符串长度排序
说明:字符串本身具备排序性(即已经实现了comparable
接口),但其排序方式并不是本例所需要的,因此可以使用比较器进行自定义排序。但是可以将字符串本身具有的compareTo()
方法作为次要排序条件。
示例程序:
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;
//自定义String比较器类
class StringComparator implements Comparator{
//重写compare方法
public int compare(String s1, String s2) {
if(s1.length()>s2.length())
return 1;
else if(s1.length()return -1;
else{
//当用于比较的两个字符串长度相等时返回字符串的默认排序返回值
return s1.compareTo(s2);
}
}
}
//自定义测试类
public class StringComparatorDemo {
public static void main(String[] args) {
//新建自定义字符串比较器对象
StringComparator sc=new StringComparator();
//新建TreeSet对象承载字符串
TreeSet ts=new TreeSet(sc);
ts.add("java");
ts.add("python");
ts.add("php");
ts.add("c#");
ts.add("c++");
ts.add("jsp");
//利用迭代器对元素进行输出
Iterator it=ts.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
}
程序运行结果:
c#
c++
jsp
php
java
python
相关阅读:
菜鸟猿大战Java之集合框架系列(一)
菜鸟猿大战Java之集合框架系列(二)
菜鸟猿大战Java之集合框架系列(四)
菜鸟猿大战Java之集合框架系列(五)