Java中的Set接口
Java中Set接口继承自Collection接口,Set接口的实现类无序并且不可重复,这里的无序指的是添加的顺序和内部真实排列顺序无关。Set接口的实现类常用的有HashSet和TreeSet,它们都可以通过增强for循环和迭代器的方式进行循环遍历。
HashSet
HashSet底层是由HashMap的key维护的,
//创建HashSet对象,底层创建一个HashMap对象 public HashSet() { map = new HashMap<>(); }
实现结构: 哈希表(数组+链表),jdk1.8后还通过红黑树的结构,维护哈希表,避免单条链表过长。它的特点:查询效率和增删效率都比较高,拥有数组和链表的优点。但是这种结构无序,自定义类想要实现按照一定顺序排列,并实现去重,需要重写hashCode()和equals()方法。
哈希表结构
Java中提供的数据类型,例如String类、包装类等等都重写了Object的hashCode()方法。hashCode()方法在Object类中定义为native本地的,直接返回对象的内存地址表示,而Integer类重写后返回的就是值本身。 HashSet在添加元素时,首先调用hashCode()方法,再根据哈希算法获得一个值,这个值就是"位桶"的索引,位桶就是哈希表中数组中的一个位置。然后看当前位置有没有储存内容,没有储存就放入这个位置,作为链表的头结点。如果有内容,在调用equals方法比较内容,相同结束,不同则顺着该结点向下查找直到找到相同结束,或者找到为null的结点插入即可。HashSet扩容是在数组储存元素大于总长度的0.75倍,则将长度扩大为2倍。这一部分配合代码或者图解比较好理解,因为HashSet底层是由HashMap的key维护的,可以看这篇自定义类实现HashMap的部分功能
自定义类重写hashCode()和equals()方法
//自定义类 public class Person implements Comparable<Person>{ private int age; private int height; private String name; public Person() { } public Person(int age, String name, Integer height) { this.age = age; this.name = name; this.height = height; } 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; } public Integer getHeight() { return height; } public void setHeight(Integer height) { this.height = height; } //重写hashCode()方法 @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + age; result = prime * result + height; result = prime * result + ((name == null) ? 0 : name.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Person other = (Person) obj; if (age != other.age) return false; if (height != other.height) return false; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; return true; } @Override public String toString() { return "Person [age=" + age + ", name=" + name + ", height=" + height + "]"; } //实现compareTo方法,根据年龄排序 @Override public int compareTo(Person o) { return this.age-o.getAge(); } }
TreeSet
TreeSet底层是由TreeMap的key维护的,同样是无序和不可重复。TreeSet储存的自定义类根据实现compareTo()方法或自定义比较器类,可以实现比较排序,默认升序。类似上面的Person类,实现Comparable接口,实现compareTo()方法,这是硬编码习惯,不够灵活,每次要根据需求修改源代码。也可以自定义比较器类,实现Comparator接口,在创建TreeSet时传入自定义比较器类的对象,这种问题我们也可以用Lambda表达式,可以看看我之前写的入门–Java中匿名内部类及lambda表达式–,下面根据Person类给出一个实例。
public class Test02 { public static void main(String[] args) { //lambda表达式在构造时传入参数 TreeSet<Person> set = new TreeSet<>((o1,o2)->{ //根据年龄、身高依次排序 if(o1.getAge() != o2.getAge()) { return o1.getAge()- o2.getAge(); }else if(o1.getHeight() != o2.getHeight()) { return o1.getHeight() - o2.getHeight(); }else { return 0; } }) ; } }
遍历方式
public class Test01 { public static void main(String[] args) { HashSet<Person> set = new HashSet<>(); set.add(new Person(12,"pu",155)); set.add(new Person(15,"liang",162)); set.add(new Person(10,"li",150)); //1、增强for循环 for(Person p : set) { System.out.println(p); } //2、迭代器 Iterator<Person> it = set.iterator(); while(it.hasNext()) { System.out.println(it.next()); } } }