前置知识

Set系列集合

  1. 无序:存取顺序不一致
  2. 不重复:可以去重
  3. 无索引:没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取元素

Set集合的实现类

  • HashSet:无序、不重复、无索引
  • LinkedHashSet:有序、不重复、无索引
  • TreeSet:可排序、不重复、无索引

Set接口中的方法基本上与Collection的API一致
Collection是单列集合的祖宗接口,他的功能是全部单列集合都可以继承使用

方法 功能
public boolean add(E e) 把指定的对象添加到当前集合中
public void clear() 清空集合中所有的元素
public boolean remove(E e) 把给定的对象在当前集合中删除
public boolean contains(Object obj) 判断当前集合中是否包含给定对象
public boolean isEmpty() 判断当前集合是否为空
public int size() 返回集合中元素的个数/集合的长度

利用Set的性质去重

注:本文用到的一个JavaBean类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class Student {  
private String name;
private int 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;
}

public Student(String name, int age) {
this.name = name;
this.age = age;
}

public Student() {
}


@Override
public int hashCode() {
return Objects.hash(name, age);
}
}

去重代码演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//1.创建一个Set集合的对象  
Set<String> s = new HashSet<>();

//2.添加元素
//如果当前元素是第一次添加,那么可以添加成功,返回true
//如果当前元素是第二次添加,那么添加失败,返回false
//所以我们可以利用set集合对数据进行去重
s.add("张三");
s.add("张三");
s.add("李四");
s.add("王五");

//3.打印集合
//无序
System.out.println(s);

对set集合进行遍历

利用三种方式对集合遍历

  • 迭代器
  • 增强for
  • lambda
    代码演示:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    //迭代器遍历  
    Iterator<String> it = s.iterator();
    while(it.hasNext()){
    String str = it.next();
    System.out.println(str);
    }

    //增强for
    for(String str : s){
    System.out.println(str);
    }

    //匿名内部类
    s.forEach(new Consumer<String>() {
    @Override
    public void accept(String str) {
    System.out.println(str);
    }
    });

    //利用lambda简化
    s.forEach(str-> System.out.println(str));

HashSet

哈希值:

对象的整数表现形式  
1.如果没有重写hashCode方法,不同对象计算出的哈希值是不同的 (会根据对象的**地址值**来进行计算) 
2.如果已经**重写hashCode方法**,不同对象只要**属性值相同**,计算出的哈希值**就是一样**的  
3.但是在**小部分情况**下,不同的属性值或者不同的地址值计算出来的哈希值也**有可能是一样**的(**哈希碰撞**)

对上述特性进行代码演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
//1.创建对象  
Student st1 = new Student("张三",10);
Student st2 = new Student("张三",10);

//2.如果没有重写hashCode方法,不同对象计算出的哈希值是不同的
// 如果已经重写hashCode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的
System.out.println(st1.hashCode());//24022530
System.out.println(st2.hashCode());//24022530

//在小部分情况下、不同的属性值或者不同的地址值计算出来的哈希值也有可能是一样的
//哈希碰撞
System.out.println("abc".hashCode());//96354
System.out.println("acD".hashCode());//96354

HashSet底层原理

  1. 创建一个默认长度16,默认加载因子0.75的数组,数组名table
    1. 根据元素的哈希值跟数组的长度计算出应存入的位置
    2. 判断当前位置是否为null,如果是null直接存入
    3. 如果位置不为null,表示有元素,则调用equals方法比较方法属性值
    4. 一样:不存 不一样:存入数组,形成链表
      JDK8以前:新元素存入数组,老元素挂在新元素下面
      JDK8以后:新元素直接挂在老元素下面

JDK8以后,当链表长度超过8,而且数组长度大于等于64时,自动转换为红黑树
如果集合中储存的是自定义对象,必须要重写hashCode和equals方法

LinkedHashSet

LinkedHashSet继承了HashSet
LinkedHashSet底层原理

  1. 有序、不重复、无索引
  2. 这里的有序值得是保证存储和取出的元素顺序一致
  3. 原理:底层数据结构依然是哈希表,只是每个元素又额外多了一个双链表的机制记录存储的顺序
    代码演示:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //1.创建学生对象  
    Student s1 = new Student("zhangsan",18);
    Student s2 = new Student("lisi",18);
    Student s3 = new Student("wangwu",18);
    Student s4 = new Student("zhangsan",18);

    //2.创建LinkedHashSet对象
    LinkedHashSet<Student> lhs1 = new LinkedHashSet<>();

    //3.添加元素
    System.out.println(lhs1.add(s1));
    System.out.println(lhs1.add(s2));
    System.out.println(lhs1.add(s3));
    System.out.println(lhs1.add(s4));

    //4.打印集合
    System.out.println(lhs1);