多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
>[success] # Set 1. `java.util.Set`集合是Collection集合的子集合,与List集合平级,该集合中元素**没有先后放入次序,且不允许重复** 2. 该集合的主要实现类是:`HashSet`类 和 `TreeSet`类以及`LinkedHashSet`类 3. `HashSet`类的底层是采用**哈希表**进行数据管理的 4. `TreeSet`类的底层是采用**红黑树**进行数据管理的 5. `LinkedHashSet`了一个**双向链表**,链表中记录了元素的迭代顺序,也就是元素插入集合中的**先后顺序,因此便于迭代** >[info] ## 常用方法 * 和 `Collection` 集合中的方法一样 |方法声明 |功能介绍| |--|--| |boolean add(E e); |向集合中添加对象| |boolean addAll(Collection<? extends E> c)|用于将参数指定集合c中的所有元素添加到当前集合中| |boolean contains(Object o); |判断是否包含指定对象| |boolean containsAll(Collection<?> c) |判断是否包含参数指定的所有对象| |boolean retainAll(Collection<?> c) |保留当前集合中存在且参数集合中存在的所有对象| |boolean remove(Object o); |从集合中删除对象| |boolean removeAll(Collection<?> c) |从集合中删除参数指定的所有对象| |void clear(); |清空集合| |int size();| 返回包含对象的个数| |boolean isEmpty(); |判断是否为空| |boolean equals(Object o)| 判断是否相等| |int hashCode() |获取当前集合的哈希码值| |Object[] toArray()| 将集合转换为数组| |Iterator iterator()| 获取当前集合的迭代器| >[success] # HashSet 使用 ~~~ import java.util.Set; import java.util.HashSet; import java.util.LinkedHashSet; public class HashSetTest { public static void main(String[] args) { // Set s1 = new HashSet(); // 默认 Object 泛型 Set<String> s1 = new HashSet<>(); // String 泛型 // add 添加元素 boolean b1 = s1.add("two"); System.out.println("b1 = " + b1); // true 添加成功返回true System.out.println("s1 = " + s1); // [two] // 从打印结果上可以看到元素没有先后放入次序(表面) b1 = s1.add("one"); System.out.println("b1 = " + b1); // true System.out.println("s1 = " + s1); // [one, two] [two, one] // 验证元素不能重复 b1 = s1.add("one"); System.out.println("b1 = " + b1); // false 元素已经存在返回false System.out.println("s1 = " + s1); // [one, two] // 打印有顺序且不重复 LinkedHashSet Set<String> s2 = new LinkedHashSet<>(); // 将放入的元素使用双链表连接起来 b1 = s2.add("two"); System.out.println("b1 = " + b1); // true 添加成功返回true System.out.println("s2 = " + s2); // [two] b1 = s2.add("one"); System.out.println("b1 = " + b1); // true 添加成功返回true System.out.println("s2 = " + s2); // [two, one] b1 = s2.add("one"); System.out.println("b1 = " + b1); // false 元素已经存在返回false } } ~~~ >[info] ## HashSet集合的原理 1. 使用元素调用hashCode方法获取对应的**哈希码值**,再由某种哈希算法计算出该元素在数组中的**索引位置**。 2. 若该位置没有元素,则将该元素直接放入即可 3. 若该位置有元素,则使用新元素与已有元素依次**比较哈希值**,**若哈希值不相同**,则将该元素直接放入。 4. 若新元素与已有元素的哈希值相同,则使用新元素调用**equals方法与已有元素依次比较** 5. 若相等则添加元素失败,否则将元素直接放入即可 * 哈希表结构 ![](https://img.kancloud.cn/c0/8c/c08c65c5777831a43ac1ae53273d5eb7_315x391.png) **总结**:当两个元素调用**equals方法相等时证明这两个元素相**同,重写hashCode方法后保证这两个元素得到的哈**希码值相同**,由同一个哈希算法生成的索引位置相同,此时只需要与该索引位置已有元素比较即可,从而**提高效率并避免重复元素的出现** >[success] # TreeSet 1. **TreeSet集合**的底层采用**红黑树**进行数据的管理,当有新元素插入到TreeSet集合时,需要使用新元素与集合中已有的元素**依次比较来确定新元素的合理位置** * 红黑树是一种**平衡二叉树**,二叉树主要概念,每个节点最多只有两个子节点的树形结构,**左子树中的任意节点元素都小于根节点元素值,右子树中的任意节点元素都大于根节点元素值** 2. **TreeSet** 遵循红黑树的规则,那么就要去实现其比较的规则书写,这里分两种 * 使用元素的自然排序规则进行比较并排序,让元素类型实现**java.lang.Comparable**接口(String 内部就继承了该接口并实现了 compareTo 方法因此可以从小到大排序) * 使用比较器规则进行比较并排序,构造TreeSet集合时传入**java.util.Comparator**接口 >[danger] ##### 案例 1. String 内部就继承了**java.lang.Comparable**接口并实现了 `compareTo` 方法因此可以从小到大排序 ~~~ import java.util.TreeSet; public class TreeSetTest { public static void main(String[] args) { TreeSet<String> s1 = new TreeSet<>(); // 添加元素 boolean a1 = s1.add("aa"); System.out.println(a1); // true 添加成功 a1 = s1.add("aa"); System.out.println(a1); // false 已存在添加失败 s1.add("cc"); s1.add("bb"); // TreeSet集合的底层是采用红黑树实现的,因此元素有大小次序,默认从小到大打印 System.out.println(s1); // [aa, bb, cc] } } ~~~ >[danger] ##### 自定义类实现**java.lang.Comparable**接口 1. 如果没有去实现自定类中**java.lang.Comparable**接口 直接使用**TreeSet** 打印会提示报错`Exception in thread "main" java.lang.ClassCastException: class cannot be cast to class java.lang.Comparable` 2. Set 是**不允许重复**,因此比较时符合 `compareTo` 规则重复会被忽略 ~~~ import java.util.TreeSet; public class Person implements Comparable<Person> { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } @Override public int compareTo(Person o) { // return 0; // 调用对象和参数对象相等,调用对象就是新增加的对象 // return -1; // 调用对象小于参数对象 // return 1; // 调用对象大于参数对象 // return this.getName().compareTo(o.getName()); // 比较姓名 // return this.getAge() - o.getAge(); // 比较年龄 // 姓名相同则按照年龄比价 int ia = this.getName().compareTo(o.getName()); return ia == 0 ? this.getAge() - o.getAge() : ia; } public static void main(String[] args) { TreeSet<Person> tp = new TreeSet<>(); Person p = new Person("w", 15); Person p1 = new Person("e", 10); Person p2 = new Person("r", 16); Person p3 = new Person("q", 10); Person p4 = new Person("q", 10); tp.add(p); tp.add(p1); tp.add(p2); tp.add(p3); Boolean b = tp.add(p4); System.out.print(b); // false // 自定义类如果没用实现接口 java.lang.Comparable 会报错 // Exception in thread "main" java.lang.ClassCastException: class Person cannot // be cast to class java.lang.Comparable System.out.println(tp); // [Person{name='e', age=10}, Person{name='q', age=10}, Person{name='r', age=16}, Person{name='w', age=15}] 重复的比较规则P4 被忽略 } } ~~~ >[danger] ##### java.util.Comparator 接口形式比较 1. 自定义实现类没有实现接口**java.lang.Comparable**,但通过 `Lambda` 表达式 和`匿名内部类` 作为比较条件参数传入 ~~~ import java.util.TreeSet; import java.util.Comparator; public class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } public static void main(String[] args) { // // 准备一个比较器对象作为参数传递给构造方法 // // 匿名内部类: 接口/父类类型 引用变量名 = new 接口/父类类型() { 方法的重写 }; // Comparator<Person> c = new Comparator<Person>() { // @Override // public int compare(Person o1, Person o2) { // o1表示新增加的对象 o2表示集合中已有的对象 // return o1.getAge() - o2.getAge(); // 表示按照年龄比较 // } // }; // 使用从Java8开始支持Lambda表达式: (参数列表) -> { 方法体 } Comparator<Person> c = (Person o1, Person o2) -> { return o1.getAge() - o2.getAge(); }; TreeSet<Person> tp = new TreeSet<>(c); Person p = new Person("w", 15); Person p1 = new Person("e", 10); Person p2 = new Person("r", 16); Person p3 = new Person("q", 10); Person p4 = new Person("q", 10); tp.add(p); tp.add(p1); tp.add(p2); tp.add(p3); tp.add(p4); /* * [Person{name='e', age=10}, Person{name='w', age=15}, Person{name='r', * age=16}] */ System.out.println(tp); } } ~~~ >[danger] ##### 总结 自然排序的规则比较单一,而比较器的规则比较多元化,而且比较器优先于自然排序