八、Set 接口
	Set 是 Collection 的子接口,Set 接口定义了一种规范,也就是说明该容器不记录元素的添加顺序,也不允许元素重复。
8.1、Set 集合的特点
- 不允许元素重复 
- 不会记录元素添加的先后顺序 
- 	set 只包含从 Collection 继承的方法,不过 Set 无法记住添加的顺序,也不允许元素重复,当将两个相同的元素添加进 Set 集合的时候,添加操作失败,添加方法(- add())返回 false
 
8.2、Set 接口常用的实现类
- HashSet 类:底层采用哈希表实现 
- TreeSet 类:底层采用红黑书实现,可以对集合中的元素排序 
8.3、HashSet
8.3.1、HashSet 原理
	HashSet 底层采用哈希表实现,元素对象的 HashCode 值决定了在哈希表中存储的位置,当往 HashSet 中添加新元素的时候,先会判断该位置是否有元素:
- 如果没有,直接把该新的对象存储到 hashCode 指定的位置 
- 如果有,再继续判断新对象和集合对象的 equals 作比较: 
- 若 equals 为 true,则视为同一个对象,不保存,add()方法返回 false 
- 如果 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、添加操作
- boolean put(Object key,Object value):存储一个键值对到 Map 中 
- 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、查询操作
- int size():返回当前 Map 中键值对个数 
- boolean isEmpty():判断当前 Map 中键值对个数是否为 0. 
- Object get(Object key):返回 Map 中指定 key 对应的 value 值,如果不存在该 key,返回 null 
- boolean containsKey(Object key):判断 Map 中是否包含指定 key 
- boolean containsValue(Object value):判断 Map 中是否包含指定 value 
- Set keySet():返回 Map 中所有 key 所组成的 Set 集合 
- Collection values():返回 Map 中所有 value 所组成的 Collection 集合 
- 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);  }}
   复制代码
 
评论