写点什么

你了解集合?那你倒是给我说说啊!【3】

作者:XiaoLin_Java
  • 2022 年 1 月 07 日
  • 本文字数:6775 字

    阅读完需:约 22 分钟

你了解集合?那你倒是给我说说啊!【3】

八、Set 接口

​ Set 是 Collection 的子接口,Set 接口定义了一种规范,也就是说明该容器不记录元素的添加顺序,也不允许元素重复。

8.1、Set 集合的特点

  1. 不允许元素重复

  2. 不会记录元素添加的先后顺序

  3. ​ set 只包含从 Collection 继承的方法,不过 Set 无法记住添加的顺序,也不允许元素重复,当将两个相同的元素添加进 Set 集合的时候,添加操作失败,添加方法(add())返回 false

8.2、Set 接口常用的实现类

  1. HashSet 类:底层采用哈希表实现

  2. TreeSet 类:底层采用红黑书实现,可以对集合中的元素排序

8.3、HashSet

8.3.1、HashSet 原理


​ HashSet 底层采用哈希表实现,元素对象的 HashCode 值决定了在哈希表中存储的位置,当往 HashSet 中添加新元素的时候,先会判断该位置是否有元素:


  1. 如果没有,直接把该新的对象存储到 hashCode 指定的位置

  2. 如果有,再继续判断新对象和集合对象的 equals 作比较:

  3. 若 equals 为 true,则视为同一个对象,不保存,add()方法返回 false

  4. 如果 equals 为 false,则存储在之前的对象的同一个位置上开辟一个链表进行存储


每一个存储到哈希表中的对象,都得重写 hashCode 和 equals 方法来判断是否是同一个对象,在一般情况下,equals 为 true 的时候,hashCode 应该也相等,Idea 可以自动生成这些方法

8.3.2、HashSet 常用方法

package day14_ArrayList2.classing;
import java.util.HashSet; import java.util.Iterator; import java.util.Set;
/** * @author Xiao_Lin * @version 1.0 * @date 2020/12/17 19:38 */public class SetApi {
public static void main(String[] args) { Set<String> set = new HashSet<>();
//添加操作,向列表中添加元素 set.add("will"); set.add("wolf"); set.add("code"); set.add("lucy");
//查询操作 System.out.println("集合中的所有元素为:"+set); System.out.println("元素数量:"+set.size()); System.out.println("是否存在某个元素:"+set.contains("111")); System.out.println("是否存在某个元素:"+set.contains("code"));
//删除元素 set.remove("code"); System.out.println("删除后的集合为:"+set);
//增强for循环遍历 for (String ele : set){ System.out.println(ele); } //迭代器遍历 Iterator<String> it = set.iterator(); while (it.hasNext()){ System.out.println( it.next()); } }}
复制代码


package day14_ArrayList2.classing;
/** 重写equals和hashCode方法 * @author Xiao_Lin * @version 1.0 * @date 2020/12/17 11:34 */public class Student { private String name; private Integer sn; private String classname;
@Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; }
Student student = (Student) o;
if (name != null ? !name.equals(student.name) : student.name != null) { return false; } if (sn != null ? !sn.equals(student.sn) : student.sn != null) { return false; } return classname != null ? classname.equals(student.classname) : student.classname == null; }
@Override public int hashCode() { int result = name != null ? name.hashCode() : 0; result = 31 * result + (sn != null ? sn.hashCode() : 0); result = 31 * result + (classname != null ? classname.hashCode() : 0); return result; }
public Student(String name, Integer sn, String classname) { this.name = name; this.sn = sn; this.classname = classname; }
public Student(){
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Integer getSn() { return sn; }
public void setSn(Integer sn) { this.sn = sn; }
public String getClassname() { return classname; }
public void setClassname(String classname) { this.classname = classname; }
@Override public String toString() { return "Student{" + "name='" + name + '\'' + ", sn=" + sn + ", classname='" + classname + '\'' + '}'; }}
复制代码

8.4、TreeSet

​ TreeSet 底层采用了红黑树算法,会对存储的元素对象默认使用自然排序(升序)



必须保证 TreeSet 集合中的元素对象是相同的数据类型,否则报错


import java.util.Set;import java.util.TreeSet;
public class TreeSetDemo{ public static void main(String[] args) { Set<String> set = new TreeSet<>(); set.add("wolf"); set.add("will"); set.add("sfef"); set.add("allen"); System.out.println(set);//[allen, sfef, will, wolf] }}
复制代码

8.4.1、TreeSet 底层数据结构详解

第一步


​ 插入第一个节点,无需比较,直接作为根节点



第二步


​ 插入第二个节点,和 wolf 根节点比较,如果小于根节点就作为左子树,如果大于根节点就放在右边,作为右子树



第三步


​ 插入第三个节点,先和 wolf 根节点比较,较小,左移动作为左子树,因为左边已经有了一颗左子树,所以再和 will 节点进行比较,较小,继续放在左边作为左子树



第四步


​ 由于 TreeSet 是平衡二叉树,如果树不平衡(左右子树差值大于 1),会对节点进行调整



第五步


​ 插入第四个节点,先和根节点 will 进行比较,较小,左移,再和 sfef 节点作比较,依然较小,左移


8.4.2、Comparable 接口

​ 在缺省的情况下,TreeSet 中的元素会采用自然排序(从小到大),此时要求元素对象必须实现java.util.Comparable接口,大多数的 JDK 自带的类都实现了该接口,比如说最常见的八大包装类和 String​ TreeSet 会调用元素的 comparaTo()方法来比较元素的大小关系,然后将集合元素按照升序排列


public interface Comparable<T> {  public int compareTo(T o);}
复制代码


比较规则


  • 当前元素大于传进来的元素,返回正整数 1,优先级较高

  • 当前元素小于传进来的元素,返回负整数-1,优先级较低

  • 当前元素等于传进来的元素,返回 0,此时认为两个对象是同一个对象


​ 如果我们自定义一个类,且需要存储到 TreeSet 中,此时我们需要让该类实现 Comparable 接口,并且覆盖 compareTo()方法,在该方法中编写比较规则


package day14_ArrayList2.classing;
/** * @author Xiao_Lin * @version 1.0 * @date 2020/12/17 11:34 */public class Student implements Comparable<Student>{ private String name; private Integer sn; private String classname;
//比较规则 @Override public int compareTo(Student student) { //当前对象大于传进来的对象 if (this.sn > student.sn){ return 1; }else if (this.sn < student.sn){ return -1; } return 0; //可以简化为; //return this.sn - student.sn; }
@Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; }
Student student = (Student) o;
if (name != null ? !name.equals(student.name) : student.name != null) { return false; } if (sn != null ? !sn.equals(student.sn) : student.sn != null) { return false; } return classname != null ? classname.equals(student.classname) : student.classname == null; }
@Override public int hashCode() { int result = name != null ? name.hashCode() : 0; result = 31 * result + (sn != null ? sn.hashCode() : 0); result = 31 * result + (classname != null ? classname.hashCode() : 0); return result; }
public Student(String name, Integer sn, String classname) { this.name = name; this.sn = sn; this.classname = classname; } }
复制代码

8.4.3、Comparator 接口

​ TreeSet 除了支持默认的自然排序外,还支持自定义排序,此时需要在构建 TreeSet 对象时传递java.util.Comparator接口的实现类对象,Comparator 表示比较器,里面封装了比较规则。


​ 此时 compare 方法返回 0,则认为两个对象是同一个对象,返回正数排前面,返回负数排后面。


public interface Comparator<T> {  int compare(T o1, T o2);}
复制代码


示范


class User {  private String name;    import java.util.TreeSet;
private int age;public User(String name, int age) { this.name = name; this.age = age; }public int getAge() { return age; }public String getName() { return name; }public String toString() { return "User [name=" + name + ", age=" + age + "]"; } }public class App { public static void main(String[] args) { Set<User> set = new TreeSet<>(new NameLengthComparator()); set.add(new User("James", 30)); set.add(new User("Bryant", 22)); set.add(new User("Allen", 28)); set.add(new User("Will", 17)); System.out.println(set);// }}//封装了比较规则class NameLengthComparator implements java.util.Comparator<User> { public int compare(User o1, User o2) { int ret = o1.getName().length() - o2.getName().length(); if (ret > 0) { return 1; } else if (ret < 0) { return -1; } else { if (o1.getAge() > o2.getAge()) { return 1; } else if (o1.getAge() < o2.getAge()) { return -1; } else { return 0; } } }}
复制代码

九、Map 接口

​ Map 翻译为映射



​ 从结构图上看,Map 并不是集合,而是类似两个集合的映射关系,所以 Map 中没有实现 Collection 接口


​ 在 Map 中,要求 A 集合的每一个元素(key)都可以在 B 集合中找到唯一的值(value)与之对应,意味着 A 集合中的元素是不可以重复的而 B 集合中的元素却可以重复,所以 A 集合应该是一个 Set 集合,而 B 集合是一个 List 集合



​ 其实一个 Map 中就由很多的 key-value(kv 键值对)组成的,每一个键值对我们用 Entry 表示



​ 其实我们也可以把 Map 看成是多个 Entry 的集合



​ 总的来说,我们还是习惯把 Map 称为集合,不过和 List、Set 不同,List、Set 是单元素集合,Map 是双元素集合


  • 单元素集合:每一次只能存储一个元素,比如 List、Set

  • 双元素集合:每次需要存储两个元素(一个 key 一个 value),比如 Map

  • 注意:

  • Map 接口并没有继承 Collection 接口也没有继承 Iterable(可迭代的)接口,所以不可以直接对 Map 使用 for-each 遍历操作

  • 其中 value 就表示存储的数据,而 key 就是这个 value 的名字

9.1、Map 常用的 API

9.1.1、添加操作

  1. boolean put(Object key,Object value):存储一个键值对到 Map 中

  2. boolean putAll(Map m):把 m 中的所有键值对添加到当前 Map 中

9.1.2、删除操作

Object remove(Object key):从 Map 中删除指定 key 的键值对,并返回被删除 key 对应的 value

9.1.3、修改操作

​ 无专门的方法,可以调用 put 方法,存储相同 key,不同 value 的键值对,可以覆盖原来的。

9.1.4、查询操作

  1. int size():返回当前 Map 中键值对个数

  2. boolean isEmpty():判断当前 Map 中键值对个数是否为 0.

  3. Object get(Object key):返回 Map 中指定 key 对应的 value 值,如果不存在该 key,返回 null

  4. boolean containsKey(Object key):判断 Map 中是否包含指定 key

  5. boolean containsValue(Object value):判断 Map 中是否包含指定 value

  6. Set keySet():返回 Map 中所有 key 所组成的 Set 集合

  7. Collection values():返回 Map 中所有 value 所组成的 Collection 集合

  8. Set entrySet():返回 Map 中所有键值对所组成的 Set 集合

9.1.5、Lambda 8 表达式集合遍历

map.forEach((k,v)->{   System.out.println(k+" "+v); });
复制代码

9.2、HashMap

HashMap 底层是基于哈希表算法,Map 中存储的 key 对象和 HashCode 值决定了在哈希表中存储的位置,因为 Map 中的 key 是 Set,所以不能保证添加的先后顺序,也不允许重复


import java.util.HashMap;import java.util.Map;
public class HashMapDemo1{ public static void main(String[] args) { Map<String, String> map = new HashMap<>(); map.put("girl1", "西施"); map.put("girl2", "王昭君"); map.put("girl3", "貂蝉"); map.put("girl4", "杨玉环"); System.out.println("map中有多少键值对:"+map.size()); System.out.println(map); System.out.println("是否包含key为girl1:"+map.containsKey("girl1")); System.out.println("是否包含value为貂蝉:"+map.containsValue("貂蝉"));//替换key为girl3的value值 map.put("girl3", "小乔"); System.out.println(map);//删除key为girl3的键值对 map.remove("girl3"); System.out.println(map); }}
复制代码

9.2.1、Map 的迭代遍历

//获取Map中所有的keySet<String> keys = map.keySet();System.out.println("Map中所有key:"+keys);//获取Map中所有的valueCollection<String> values = map.values();System.out.println("Map中所有value:"+values);//获取Map中所有的key-value(键值对)Set<Entry<String, String>> entrys = map.entrySet();for (Entry<String, String> entry : entrys) {  String key = entry.getKey();  String value = entry.getValue();  System.out.println(key+"->"+value);}
复制代码

9.2.2、练习

​ 统计一个字符串中每隔字符出现次数


package day14_ArrayList2.classing;
import java.util.HashMap;import java.util.Scanner;
/** * @author Xiao_Lin * @version 1.0 * @date 2020/12/17 16:54 */public class Test1 { //需求:统计一个字符串中每一个字符出现的次数 public static void main(String[] args) { Scanner scanner = new Scanner(System.in); String str = scanner.nextLine(); HashMap<Character, Integer> map = new HashMap();//新建一个map,map的key存储字符,value存储出现的次数 //进行map的遍历 for (int i = 0 ;i<str.length();i++){ char c = str.charAt(i);//将str字符串遍历并且强转为字符 if (map.containsKey(c)){//map是否包含遍历的字符 Integer value = map.get(c);//如果包含就将原来的字符对应的value(次数)值取出来并且自增 value+=1;//次数自增 map.put(c,value);//将修改后的value放入map集合中 }else { map.put(c,1);//否则将字符放进集合中,并且将次数置为1 } } System.out.println(map); }}
复制代码

9.3、TreeMap

​ TreeMap 底层的 key 是基于红黑树,因为 Map 中的 key 也是一个 Set 集合,所以不能保证添加的先后顺序,且也不允许重复,因为 Map 中存储的 key 会默认使用自然排序(从小到大),和 TreeSet 一样,除了可以使用自然排序也可以自定义自己的排序


import java.util.Map;import java.util.TreeMap;
public class App { public static void main(String[] args) { Map<String, String> map = new TreeMap<>(); map.put("girl4", "杨玉环"); map.put("girl2", "王昭君"); map.put("key1", "西施"); map.put("key3", "貂蝉"); System.out.println(map);//------------------------------------------- map = new TreeMap<>(map); System.out.println(map); }}
复制代码


package day14_ArrayList2.classing;
import java.util.Comparator;import java.util.TreeSet;
/** * @author Xiao_Lin * @version 1.0 * @date 2020/12/17 15:55 */public class TreeSet1 {
public static void main(String[] args) { //使用自定义的比较器,需要传入一个Comparator接口的实现类,这里使用匿名内部类 TreeSet<String> set = new TreeSet<String>(new Comparator<String>() { @Override public int compare(String s1, String s2) { if (s1.length()==s2.length()){ return s1.compareTo(s2); }else { return s1.length()-s2.length(); } } }); set.add("s12"); set.add("see"); set.add("s1234"); System.out.println(set); }}
复制代码


发布于: 刚刚阅读数: 2
用户头像

XiaoLin_Java

关注

问啥啥都会,干啥啥不行。 2021.11.08 加入

问啥啥都会,干啥啥不行。🏆CSDN原力作者🏆,🏆掘金优秀创作者🏆,🏆InfoQ签约作者🏆

评论

发布
暂无评论
你了解集合?那你倒是给我说说啊!【3】