八、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中所有的key
Set<String> keys = map.keySet();
System.out.println("Map中所有key:"+keys);
//获取Map中所有的value
Collection<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);
}
}
复制代码
评论