一、单列集合

1.Collection集合

1.1数组和集合的区别

  • 相同点

    都是容器,可以存储多个数据

  • 不同点

    • 数组的长度是不可变的,集合的长度是可变的

    • 数组可以存基本数据类型和引用数据类型

      集合只能存引用数据类型,如果要存基本数据类型,需要存对应的包装类

1.2集合类体系结构

01_集合类体系结构图

1.3Collection 集合概述和使用

  • Collection集合概述

    • 是单例集合的顶层接口,它表示一组对象,这些对象也称为Collection的元素
    • JDK 不提供此接口的任何直接实现.它提供更具体的子接口(如Set和List)实现
  • 创建Collection集合的对象

    • 多态的方式
    • 具体的实现类ArrayList
  • Collection集合常用方法

    方法名 说明
    boolean add(E e) 添加元素
    boolean remove(Object o) 从集合中移除指定的元素
    boolean removeIf(Object o) 根据条件进行移除
    void clear() 清空集合中的元素
    boolean contains(Object o) 判断集合中是否存在指定的元素
    boolean isEmpty() 判断集合是否为空
    int size() 集合的长度,也就是集合中元素的个数

1.4Collection集合的遍历

1.4.1 迭代器遍历

  • 迭代器介绍

    • 迭代器,集合的专用遍历方式
    • Iterator iterator(): 返回此集合中元素的迭代器,通过集合对象的iterator()方法得到
  • Iterator中的常用方法

    ​ boolean hasNext(): 判断当前位置是否有元素可以被取出
    ​ E next(): 获取当前位置的元素,将迭代器对象移向下一个索引位置

  • Collection集合的遍历

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class IteratorDemo1 {
    public static void main(String[] args) {
    //创建集合对象
    Collection<String> c = new ArrayList<>();

    //添加元素
    c.add("hello");
    c.add("world");
    c.add("java");
    c.add("javaee");

    //Iterator<E> iterator():返回此集合中元素的迭代器,通过集合的iterator()方法得到
    Iterator<String> it = c.iterator();

    //用while循环改进元素的判断和获取
    while (it.hasNext()) {
    String s = it.next();
    System.out.println(s);
    }
    }
    }
  • 迭代器中删除的方法

    ​ void remove(): 删除迭代器对象当前指向的元素

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public class IteratorDemo2 {
    public static void main(String[] args) {
    ArrayList<String> list = new ArrayList<>();
    list.add("a");
    list.add("b");
    list.add("b");
    list.add("c");
    list.add("d");

    Iterator<String> it = list.iterator();
    while(it.hasNext()){
    String s = it.next();
    if("b".equals(s)){
    //指向谁,那么此时就删除谁.
    it.remove();
    }
    }
    System.out.println(list);
    }
    }

1.4.2 增强for

  • 介绍

    • 它是JDK5之后出现的,其内部原理是一个Iterator迭代器
    • 实现Iterable接口的类才可以使用迭代器和增强for
    • 简化数组和Collection集合的遍历
  • 格式

    ​ for(集合/数组中元素的数据类型 变量名 : 集合/数组名) {

    ​ // 已经将当前遍历到的元素封装到变量中了,直接使用变量即可

    ​ }

  • 代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class MyCollectonDemo1 {
    public static void main(String[] args) {
    ArrayList<String> list = new ArrayList<>();
    list.add("a");
    list.add("b");
    list.add("c");
    list.add("d");
    list.add("e");
    list.add("f");

    //1,数据类型一定是集合或者数组中元素的类型
    //2,str仅仅是一个变量名而已,在循环的过程中,依次表示集合或者数组中的每一个元素,仅仅是第三方变量,修改之后不改变集合原本的数据
    //3,list就是要遍历的集合或者数组
    for(String str : list){
    System.out.println(str);
    }
    }
    }
  • 细节点注意:

1.报错NoSuchElementException

2.迭代器遍历完毕,指针不会复位

3.循环中只能用一次next方法

4.迭代器遍历时,不能用集合的方法进行增加或者删除

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
35
36
37
38
39
40
41
42
43
44
public class A04_CollectionDemo4 {
public static void main(String[] args) {
/*
迭代器的细节注意点:
1.报错NoSuchElementException
2.迭代器遍历完毕,指针不会复位
3.循环中只能用一次next方法
4.迭代器遍历时,不能用集合的方法进行增加或者删除
暂时当做一个结论先行记忆,在今天我们会讲解源码详细的再来分析。
如果我实在要删除:那么可以用迭代器提供的remove方法进行删除。
如果我要添加,暂时没有办法。(只是暂时)
*/

//1.创建集合并添加元素
Collection<String> coll = new ArrayList<>();
coll.add("aaa");
coll.add("bbb");
coll.add("ccc");
coll.add("ddd");

//2.获取迭代器对象
//迭代器就好比是一个箭头,默认指向集合的0索引处
Iterator<String> it = coll.iterator();
//3.利用循环不断的去获取集合中的每一个元素
while(it.hasNext()){
//4.next方法的两件事情:获取元素并移动指针
String str = it.next();
System.out.println(str);
}

//当上面循环结束之后,迭代器的指针已经指向了最后没有元素的位置
//System.out.println(it.next());//NoSuchElementException

//迭代器遍历完毕,指针不会复位
System.out.println(it.hasNext());

//如果我们要继续第二次遍历集合,只能再次获取一个新的迭代器对象
Iterator<String> it2 = coll.iterator();
while(it2.hasNext()){
String str = it2.next();
System.out.println(str);
}
}
}

1.4.3 lambda表达式

​ 利用forEach方法,再结合lambda表达式的方式进行遍历

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
public class A07_CollectionDemo7 {
public static void main(String[] args) {
/*
lambda表达式遍历:
default void forEach(Consumer<? super T> action):
*/

//1.创建集合并添加元素
Collection<String> coll = new ArrayList<>();
coll.add("zhangsan");
coll.add("lisi");
coll.add("wangwu");
//2.利用匿名内部类的形式
//底层原理:
//其实也会自己遍历集合,依次得到每一个元素
//把得到的每一个元素,传递给下面的accept方法
//s依次表示集合中的每一个数据
/* coll.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});*/

//lambda表达式
coll.forEach(s -> System.out.println(s));
}
}

2.List集合

2.1List集合的概述和特点

  • List集合的概述
    • 有序集合,这里的有序指的是存取顺序
    • 用户可以精确控制列表中每个元素的插入位置,用户可以通过整数索引访问元素,并搜索列表中的元素
    • 与Set集合不同,列表通常允许重复的元素
  • List集合的特点
    • 存取有序
    • 可以重复
    • 有索引

2.2List集合的特有方法

  • 方法介绍

    方法名 描述
    void add(int index,E element) 在此集合中的指定位置插入指定的元素
    E remove(int index) 删除指定索引处的元素,返回被删除的元素
    E set(int index,E element) 修改指定索引处的元素,返回被修改的元素
    E get(int index) 返回指定索引处的元素
  • 示例代码

    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    public class MyListDemo {
    public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("aaa");
    list.add("bbb");
    list.add("ccc");
    //method1(list);
    //method2(list);
    //method3(list);
    //method4(list);
    }

    private static void method4(List<String> list) {
    // E get(int index) 返回指定索引处的元素
    String s = list.get(0);
    System.out.println(s);
    }

    private static void method3(List<String> list) {
    // E set(int index,E element) 修改指定索引处的元素,返回被修改的元素
    //被替换的那个元素,在集合中就不存在了.
    String result = list.set(0, "qqq");
    System.out.println(result);
    System.out.println(list);
    }

    private static void method2(List<String> list) {
    // E remove(int index) 删除指定索引处的元素,返回被删除的元素
    //在List集合中有两个删除的方法
    //第一个 删除指定的元素,返回值表示当前元素是否删除成功
    //第二个 删除指定索引的元素,返回值表示实际删除的元素
    String s = list.remove(0);
    System.out.println(s);
    System.out.println(list);
    }

    private static void method1(List<String> list) {
    // void add(int index,E element) 在此集合中的指定位置插入指定的元素
    //原来位置上的元素往后挪一个索引.
    list.add(0,"qqq");
    System.out.println(list);
    }
    }

2.3List集合的五种遍历方式

  1. 迭代器
  2. 列表迭代器
  3. 增强for
  4. Lambda表达式
  5. 普通for循环

代码示例:

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//创建集合并添加元素
List<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");

//1.迭代器
/*Iterator<String> it = list.iterator();
while(it.hasNext()){
String str = it.next();
System.out.println(str);
}*/


//2.增强for
//下面的变量s,其实就是一个第三方的变量而已。
//在循环的过程中,依次表示集合中的每一个元素
/* for (String s : list) {
System.out.println(s);
}*/

//3.Lambda表达式
//forEach方法的底层其实就是一个循环遍历,依次得到集合中的每一个元素
//并把每一个元素传递给下面的accept方法
//accept方法的形参s,依次表示集合中的每一个元素
//list.forEach(s->System.out.println(s) );


//4.普通for循环
//size方法跟get方法还有循环结合的方式,利用索引获取到集合中的每一个元素
/*for (int i = 0; i < list.size(); i++) {
//i:依次表示集合中的每一个索引
String s = list.get(i);
System.out.println(s);
}*/

// 5.列表迭代器
//获取一个列表迭代器的对象,里面的指针默认也是指向0索引的

//额外添加了一个方法:在遍历的过程中,可以添加元素
ListIterator<String> it = list.listIterator();
while(it.hasNext()){
String str = it.next();
if("bbb".equals(str)){
//qqq
it.add("qqq");
}
}
System.out.println(list);

2.4 细节点注意:

List系列集合中的两个删除的方法

1
2
1.直接删除元素
2.通过索引进行删除

代码示例:

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
//List系列集合中的两个删除的方法
//1.直接删除元素
//2.通过索引进行删除

//1.创建集合并添加元素
List<Integer> list = new ArrayList<>();

list.add(1);
list.add(2);
list.add(3);


//2.删除元素
//请问:此时删除的是1这个元素,还是1索引上的元素?
//为什么?
//因为在调用方法的时候,如果方法出现了重载现象
//优先调用,实参跟形参类型一致的那个方法。

//list.remove(1);


//手动装箱,手动把基本数据类型的1,变成Integer类型
Integer i = Integer.valueOf(1);

list.remove(i);

System.out.println(list);

3.List集合的实现类

3.1ArrayList

ArrayList集合

​ 底层是数组结构实现,查询快、增删慢

  • 构造方法

    构造方法摘要
    **[ArrayList](../../java/util/ArrayList.html#ArrayList())**() 构造一个初始容量为 10 的空列表。
    **[ArrayList](../../java/util/ArrayList.html#ArrayList(java.util.Collection))**(Collection<? extends E> c) 构造一个包含指定 collection 的元素的列表,这些元素是按照该 collection 的迭代器返回它们的顺序排列的。
    **[ArrayList](../../java/util/ArrayList.html#ArrayList(int))**(int initialCapacity) 构造一个具有指定初始容量的空列表。
  • 成员方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    增:add
    删:remove
    改:set
    查:get
    获取集合长度,元素个数:size

    例:
    ArrayList<String> list = new ArrayList<>();
    System.out.println(list.size());
    list.add("11");
    list.add("111");
    list.add("11");
    System.out.println(list);
    list.remove("111");
    System.out.println(list);
    System.out.println(list.size());
    list.set(0,"22");
    String s = list.get(1);
    System.out.println(s);
    for (int i = 0; i < list.size(); i++) {
    System.out.pr8intln(list.get(i));
    }

3.1.1ArrayList业务场景

添加用户
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void main(String[] args) {
ArrayList<Student> list = new ArrayList<>();

Scanner sc = new Scanner(System.in);

for (int i = 0; i < 3; i++) {
Student s = new Student();
System.out.println("请输入学生姓名: ");
s.setName(sc.next());
System.out.println("请输入学生年龄");
s.setAge(sc.nextInt());
list.add(s);
}


for (int i = 0; i < list.size(); i++) {
Student stu = list.get(i);
System.out.println(stu.getName() + ", " + stu.getAge());
}


}
查询用户
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package com.yyh.arraylist;

import java.util.ArrayList;

public class UserTest {

public static void main(String[] args) {
ArrayList<User> list = new ArrayList<>();

User u1 = new User("a1", "zhangsan", "123456");
User u2 = new User("a2", "lisi", "1234567");
User u3 = new User("a3", "wangwu", "1234568");

list.add(u1);
list.add(u2);
list.add(u3);


System.out.println(getIndex(list, "a1")); //0
System.out.println(getIndex(list, "a4")); //-1
System.out.println(contains(list, "a1")); //true
System.out.println(contains(list, "a4")); //false


}

/**
* 查找list集合中是否有该用户id如果有就返回索引,没有就返回-1
* @param list
* @param id
* @return
*/
public static int getIndex(ArrayList<User> list, String id){
for (int i = 0; i < list.size(); i++) {
User user = list.get(i);
String uid = user.getId();
if(uid.equals(id)){
return i;
}
}
return -1;
}


/**
* 查找list集合中是否有改用户的id
* @param list
* @param id
* @return
*/
public static boolean contains(ArrayList<User> list, String id){
for (int i = 0; i < list.size(); i++) {
User user = list.get(i);
String uid = user.getId();
if(uid.equals(id)){
return true;
}
}
return false;

//直接写这个也可以
//return getIndex(list,id) >= 0;
}

}

生成26个英文字母
1
2
3
4
5
6
7
8
9
10
private static String getCode(){
ArrayList<Character> list = new ArrayList<>();

for (int i = 0; i < 26; i++) {
list.add((char)('a' + i));
}

System.out.println(list);
return "";
}

3.2LinkedList集合

LinkedList集合

​ 底层是链表结构实现,查询慢、增删快

  • 特有方法

    方法名 说明
    public void addFirst(E e) 在该列表开头插入指定的元素
    public void addLast(E e) 将指定的元素追加到此列表的末尾
    public E getFirst() 返回此列表中的第一个元素
    public E getLast() 返回此列表中的最后一个元素
    public E removeFirst() 从此列表中删除并返回第一个元素
    public E removeLast() 从此列表中删除并返回最后一个元素
  • 示例代码

    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    public class MyLinkedListDemo4 {
    public static void main(String[] args) {
    LinkedList<String> list = new LinkedList<>();
    list.add("aaa");
    list.add("bbb");
    list.add("ccc");
    // public void addFirst(E e) 在该列表开头插入指定的元素
    //method1(list);

    // public void addLast(E e) 将指定的元素追加到此列表的末尾
    //method2(list);

    // public E getFirst() 返回此列表中的第一个元素
    // public E getLast() 返回此列表中的最后一个元素
    //method3(list);

    // public E removeFirst() 从此列表中删除并返回第一个元素
    // public E removeLast() 从此列表中删除并返回最后一个元素
    //method4(list);

    }

    private static void method4(LinkedList<String> list) {
    String first = list.removeFirst();
    System.out.println(first);

    String last = list.removeLast();
    System.out.println(last);

    System.out.println(list);
    }

    private static void method3(LinkedList<String> list) {
    String first = list.getFirst();
    String last = list.getLast();
    System.out.println(first);
    System.out.println(last);
    }

    private static void method2(LinkedList<String> list) {
    list.addLast("www");
    System.out.println(list);
    }

    private static void method1(LinkedList<String> list) {
    list.addFirst("qqq");
    System.out.println(list);
    }
    }

4. List实现类源码分析

4.1 ArrayList源码分析:

核心步骤:

  1. 创建ArrayList对象的时候,他在底层先创建了一个长度为0的数组。

    数组名字:elementDate,定义变量size。

    size这个变量有两层含义:
    ①:元素的个数,也就是集合的长度
    ②:下一个元素的存入位置

  2. 添加元素,添加完毕后,size++

扩容时机一:

  1. 当存满时候,会创建一个新的数组,新数组的长度,是原来的1.5倍,也就是长度为15.再把所有的元素,全拷贝到新数组中。如果继续添加数据,这个长度为15的数组也满了,那么下次还会继续扩容,还是1.5倍。

扩容时机二:

  1. 一次性添加多个数据,扩容1.5倍不够,怎么办呀?

    如果一次添加多个元素,1.5倍放不下,那么新创建数组的长度以实际为准。

举个例子:
在一开始,如果默认的长度为10的数组已经装满了,在装满的情况下,我一次性要添加100个数据很显然,10扩容1.5倍,变成15,还是不够,

怎么办?

此时新数组的长度,就以实际情况为准,就是110

具体分析过程可以参见视频讲解。

添加一个元素时的扩容:

第一次添加数据

添加多个元素时的扩容:

第11次添加数据

4.2 LinkedList源码分析:

底层是双向链表结构

核心步骤如下:

  1. 刚开始创建的时候,底层创建了两个变量:一个记录头结点first,一个记录尾结点last,默认为null
  2. 添加第一个元素时,底层创建一个结点对象,first和last都记录这个结点的地址值
  3. 添加第二个元素时,底层创建一个结点对象,第一个结点会记录第二个结点的地址值,last会记录新结点的地址值

具体分析过程可以参见视频讲解。

LinkedList源码分析

4.3 迭代器源码分析:

迭代器遍历相关的三个方法:

  • Iterator iterator() :获取一个迭代器对象

  • boolean hasNext() :判断当前指向的位置是否有元素

  • E next() :获取当前指向的元素并移动指针

迭代器源码分析


5.Set集合

无序、不重复、无索引

2.1Set集合概述和特点

  • 不可以存储重复元素
  • 没有索引,不能使用普通for循环遍历

2.2Set集合的使用

存储字符串并遍历

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
public class MySet1 {
public static void main(String[] args) {
//创建集合对象
Set<String> set = new TreeSet<>();
//添加元素
set.add("ccc");
set.add("aaa");
set.add("aaa");
set.add("bbb");

// for (int i = 0; i < set.size(); i++) {
// //Set集合是没有索引的,所以不能使用通过索引获取元素的方法
// }

//遍历集合
Iterator<String> it = set.iterator();
while (it.hasNext()){
String s = it.next();
System.out.println(s);
}
System.out.println("-----------------------------------");
for (String s : set) {
System.out.println(s);
}
}
}

6.TreeSet集合

底层红黑树的数据结构实现排序

  • 数值类型:从小到大

  • 字符、字符串类型:按照ASCII码表中的数字升序排序

    1
    字符串是按照先首字母,首字母如果相同就按照第二个字母

6.1TreeSet集合概述和特点

  • 不可以存储重复元素
  • 没有索引
  • 可以将元素按照规则进行排序
    • TreeSet():根据其元素的自然排序进行排序
    • TreeSet(Comparator comparator) :根据指定的比较器进行排序

6.2TreeSet集合基本使用

存储Integer类型的整数并遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class TreeSetDemo01 {
public static void main(String[] args) {
//创建集合对象
TreeSet<Integer> ts = new TreeSet<Integer>();

//添加元素
ts.add(10);
ts.add(40);
ts.add(30);
ts.add(50);
ts.add(20);

ts.add(30);

//遍历集合
for(Integer i : ts) {
System.out.println(i);
}
}
}

6.3自然排序Comparable的使用

image-20240413121537329

  • 案例需求

    • 存储学生对象并遍历,创建TreeSet集合使用无参构造方法
    • 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序
  • 实现步骤

    1. 使用空参构造创建TreeSet集合
      • 用TreeSet集合存储自定义对象,无参构造方法使用的是自然排序对元素进行排序的
    2. 自定义的Student类实现Comparable接口
      • 自然排序,就是让元素所属的类实现Comparable接口,重写compareTo(T o)方法
    3. 重写接口中的compareTo方法
      • 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写
  • 代码实现

    学生类

    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    public class Student implements Comparable<Student>{
    private String name;
    private int age;

    public Student() {
    }

    public Student(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 "Student{" +
    "name='" + name + '\'' +
    ", age=" + age +
    '}';
    }

    @Override
    public int compareTo(Student o) {
    //按照对象的年龄进行排序
    //主要判断条件: 按照年龄从小到大排序
    int result = this.age - o.age;
    //次要判断条件: 年龄相同时,按照姓名的字母顺序排序
    result = result == 0 ? this.name.compareTo(o.getName()) : result;
    return result;
    }
    }

    测试类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public class MyTreeSet2 {
    public static void main(String[] args) {
    //创建集合对象
    TreeSet<Student> ts = new TreeSet<>();
    //创建学生对象
    Student s1 = new Student("zhangsan",28);
    Student s2 = new Student("lisi",27);
    Student s3 = new Student("wangwu",29);
    Student s4 = new Student("zhaoliu",28);
    Student s5 = new Student("qianqi",30);
    //把学生添加到集合
    ts.add(s1);
    ts.add(s2);
    ts.add(s3);
    ts.add(s4);
    ts.add(s5);
    //遍历集合
    for (Student student : ts) {
    System.out.println(student);
    }
    }
    }

6.4比较器排序Comparator的使用

  • 案例需求

    • 存储老师对象并遍历,创建TreeSet集合使用带参构造方法
    • 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序
  • 实现步骤

    • 用TreeSet集合存储自定义对象,带参构造方法使用的是比较器排序对元素进行排序的
    • 比较器排序,就是让集合构造方法接收Comparator的实现类对象,重写compare(T o1,T o2)方法
    • 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写
  • 代码实现

    老师类

    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
    35
    36
    public class Teacher {
    private String name;
    private int age;

    public Teacher() {
    }

    public Teacher(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 "Teacher{" +
    "name='" + name + '\'' +
    ", age=" + age +
    '}';
    }
    }

    测试类

    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
    public class MyTreeSet4 {
    public static void main(String[] args) {
    //创建集合对象
    TreeSet<Teacher> ts = new TreeSet<>(new Comparator<Teacher>() {
    @Override
    public int compare(Teacher o1, Teacher o2) {
    //o1表示现在要存入的那个元素
    //o2表示已经存入到集合中的元素

    //主要条件
    int result = o1.getAge() - o2.getAge();
    //次要条件
    result = result == 0 ? o1.getName().compareTo(o2.getName()) : result;
    return result;
    }
    });
    //创建老师对象
    Teacher t1 = new Teacher("zhangsan",23);
    Teacher t2 = new Teacher("lisi",22);
    Teacher t3 = new Teacher("wangwu",24);
    Teacher t4 = new Teacher("zhaoliu",24);
    //把老师添加到集合
    ts.add(t1);
    ts.add(t2);
    ts.add(t3);
    ts.add(t4);
    //遍历集合
    for (Teacher teacher : ts) {
    System.out.println(teacher);
    }
    }
    }

6.5两种比较方式总结

  • 两种比较方式小结
    • 自然排序: 自定义类实现Comparable接口,重写compareTo方法,根据返回值进行排序
    • 比较器排序: 创建TreeSet对象的时候传递Comparator的实现类对象,重写compare方法,根据返回值进行排序
    • 在使用的时候,默认使用自然排序,当自然排序不满足现在的需求时,必须使用比较器排序
  • 两种方式中关于返回值的规则
    • 如果返回值为负数,表示当前存入的元素是较小值,存左边
    • 如果返回值为0,表示当前存入的元素跟集合中元素重复了,不存
    • 如果返回值为正数,表示当前存入的元素是较大值,存右边

7.HashSet集合

7.1HashSet集合概述和特点

  • 底层数据结构是哈希表
  • 存取无序
  • 不可以存储重复元素
  • 没有索引,不能使用普通for循环遍历

7.2HashSet集合的基本应用

存储字符串并遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class HashSetDemo {
public static void main(String[] args) {
//创建集合对象
HashSet<String> set = new HashSet<String>();

//添加元素
set.add("hello");
set.add("world");
set.add("java");
//不包含重复元素的集合
set.add("world");

//遍历
for(String s : set) {
System.out.println(s);
}
}
}

7.3哈希值

  • 哈希值简介

    ​ 是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值

  • 如何获取哈希值

    ​ Object类中的public int hashCode():返回对象的哈希码值

  • 哈希值的特点

    • 同一个对象多次调用hashCode()方法返回的哈希值是相同的
    • 默认情况下,不同对象的哈希值是不同的。而重写hashCode()方法,可以实现让不同对象的哈希值相同

7.4哈希表结构

  • JDK1.8以前

    ​ 数组 + 链表

    1
    2
    3
    int index = (数组长度 - 1) & 哈希值;

    新元素存入数组,老元素挂再新元素下面

    14_JKD8以前哈希表

  • JDK1.8以后

    1
    2
    3
    4
    5
    6
    新元素直接挂在老元素下面

    加载因子是哈希的扩容机制 16 * 0.75 = 12

    当链表长度大于8而且数组长度大于64时候链表会自动转成红黑树
    从Java 11开始,这个阈值增加到了16。需要注意的是,这个阈值是指同一个槽位中的元素数量,而不是 HashMap 中的总元素数量。
    • 节点个数少于等于8个

      ​ 数组 + 链表

    • 节点个数多于8个

      ​ 数组 + 红黑树

    15_JKD8以后哈希表

7.5HashSet集合存储学生对象并遍历

  • 案例需求

    • 创建一个存储学生对象的集合,存储多个学生对象,使用程序实现在控制台遍历该集合
    • 要求:学生对象的成员变量值相同,我们就认为是同一个对象
  • 代码实现

    学生类

    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    public class Student {
    private String name;
    private int age;

    public Student() {
    }

    public Student(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 boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    Student student = (Student) o;

    if (age != student.age) return false;
    return name != null ? name.equals(student.name) : student.name == null;
    }

    @Override
    public int hashCode() {
    int result = name != null ? name.hashCode() : 0;
    result = 31 * result + age;
    return result;
    }
    }

    测试类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public class HashSetDemo02 {
    public static void main(String[] args) {
    //创建HashSet集合对象
    HashSet<Student> hs = new HashSet<Student>();

    //创建学生对象
    Student s1 = new Student("林青霞", 30);
    Student s2 = new Student("张曼玉", 35);
    Student s3 = new Student("王祖贤", 33);

    Student s4 = new Student("王祖贤", 33);

    //把学生添加到集合
    hs.add(s1);
    hs.add(s2);
    hs.add(s3);
    hs.add(s4);

    //遍历集合(增强for)
    for (Student s : hs) {
    System.out.println(s.getName() + "," + s.getAge());
    }
    }
    }
  • 总结

    ​ HashSet集合存储自定义类型元素,要想实现元素的唯一,要求必须重写hashCode方法和equals方法

8.LinkedHashSet

  • 有序、不重复、无索引

  • 父类是HashSet

  • 原理:底层是哈希表,只是每个元素又额外的多了一个双链表的机制记录存储的顺序

  • 当添加元素的时候,添加第二个元素的时候会记录第一个数据的地址值

LinkedHashSet.png

9. :star: 使用场景

如果想要集合中的元素可重复

  • 使用ArrayList集合,基于数组的

==如果想要集合中的元素可重复,而且当前的增删操作明显多于查询==

  • 使用LinkedList集合,基于链表的

==如果想要对集合中的元素去重==

  • 使用HashSet集合,基于哈希表

==如果相对集合中的元素去重,而且保证存取顺序==

  • 使用LinkedHashSet集合,基于哈希表和双链表,效率低于HashSet

==如果想对集合中的元素进行排序==

  • 使用TreeSet集合,基于红黑树。后续也可以使用List集合实现排序


二、双列集合

双列集合的特点

  • 双列集合一次需要存一对数据,分别为键和值
  • 键不能重复,值可以重复
  • 键和值是一一对应的,每一个键只能找到自己对应的值
  • 键 + 值这个整体 我们称为“键值对” 或 “键值对对象”,在Java中称为 “Entry对象”

1.Map集合

1.1Map集合概述和特点

  • Map集合概述

    1
    interface Map<K,V>  K:键的类型;V:值的类型
  • Map集合的特点

    • 双列集合,一个键对应一个值
    • 键不可以重复,值可以重复
  • Map集合的基本使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class MapDemo01 {
    public static void main(String[] args) {
    //创建集合对象
    Map<String,String> map = new HashMap<String,String>();

    //V put(K key, V value) 将指定的值与该映射中的指定键相关联
    map.put("itheima001","林青霞");
    map.put("itheima002","张曼玉");
    map.put("itheima003","王祖贤");
    map.put("itheima003","柳岩");

    //输出集合对象
    System.out.println(map);
    }
    }

1.2Map集合的基本功能

  • 方法介绍

    方法名 说明
    V put(K key,V value) 添加元素,并且当数据被覆盖时候会返回被覆盖的数据value,如果是第一次添加则返回null,所以存在 添加覆盖 操作
    V remove(Object key) 根据键删除键值对元素
    void clear() 移除所有的键值对元素
    boolean containsKey(Object key) 判断集合是否包含指定的键
    boolean containsValue(Object value) 判断集合是否包含指定的值
    boolean isEmpty() 判断集合是否为空
    int size() 集合的长度,也就是集合中键值对的个数
  • 示例代码

    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
    public class MapDemo02 {
    public static void main(String[] args) {
    //创建集合对象
    Map<String,String> map = new HashMap<String,String>();

    //V put(K key,V value):添加元素
    map.put("张无忌","赵敏");
    map.put("郭靖","黄蓉");
    map.put("杨过","小龙女");

    //V remove(Object key):根据键删除键值对元素
    // System.out.println(map.remove("郭靖"));
    // System.out.println(map.remove("郭襄"));

    //void clear():移除所有的键值对元素
    // map.clear();

    //boolean containsKey(Object key):判断集合是否包含指定的键
    // System.out.println(map.containsKey("郭靖"));
    // System.out.println(map.containsKey("郭襄"));

    //boolean isEmpty():判断集合是否为空
    // System.out.println(map.isEmpty());

    //int size():集合的长度,也就是集合中键值对的个数
    System.out.println(map.size());

    //输出集合对象
    System.out.println(map);
    }
    }

1.3Map集合的获取功能

  • 方法介绍

    方法名 说明
    V get(Object key) 根据键获取值
    Set keySet() 获取所有键的集合
    Collection values() 获取所有值的集合
    Set<Map.Entry<K,V>> entrySet() 获取所有键值对对象的集合
  • 示例代码

    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
    public class MapDemo03 {
    public static void main(String[] args) {
    //创建集合对象
    Map<String, String> map = new HashMap<String, String>();

    //添加元素
    map.put("张无忌", "赵敏");
    map.put("郭靖", "黄蓉");
    map.put("杨过", "小龙女");

    //V get(Object key):根据键获取值
    // System.out.println(map.get("张无忌"));
    // System.out.println(map.get("张三丰"));

    //Set<K> keySet():获取所有键的集合
    // Set<String> keySet = map.keySet();
    // for(String key : keySet) {
    // System.out.println(key);
    // }

    //Collection<V> values():获取所有值的集合
    Collection<String> values = map.values();
    for(String value : values) {
    System.out.println(value);
    }
    }
    }

1.4Map集合的遍历(方式1)

  • 遍历思路

    • 我们刚才存储的元素都是成对出现的,所以我们把Map看成是一个夫妻对的集合
      • 把所有的丈夫给集中起来
      • 遍历丈夫的集合,获取到每一个丈夫
      • 根据丈夫去找对应的妻子
  • 步骤分析

    • 获取所有键的集合。用keySet()方法实现
    • 遍历键的集合,获取到每一个键。用增强for实现
    • 根据键去找值。用get(Object key)方法实现
  • 代码实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public class MapDemo01 {
    public static void main(String[] args) {
    //创建集合对象
    Map<String, String> map = new HashMap<String, String>();

    //添加元素
    map.put("张无忌", "赵敏");
    map.put("郭靖", "黄蓉");
    map.put("杨过", "小龙女");

    //获取所有键的集合。用keySet()方法实现
    Set<String> keySet = map.keySet();
    //遍历键的集合,获取到每一个键。用增强for实现
    for (String key : keySet) {
    //根据键去找值。用get(Object key)方法实现
    String value = map.get(key);
    System.out.println(key + "," + value);
    }
    }
    }

1.5Map集合的遍历(方式2)

  • 遍历思路

    • 我们刚才存储的元素都是成对出现的,所以我们把Map看成是一个夫妻对的集合
      • 获取所有结婚证的集合
      • 遍历结婚证的集合,得到每一个结婚证
      • 根据结婚证获取丈夫和妻子
  • 步骤分析

    • 获取所有键值对对象的集合
      • Set<Map.Entry<K,V>> entrySet():获取所有键值对对象的集合
    • 遍历键值对对象的集合,得到每一个键值对对象
      • 用增强for实现,得到每一个Map.Entry
    • 根据键值对对象获取键和值
      • 用getKey()得到键
      • 用getValue()得到值
  • 代码实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class MapDemo02 {
    public static void main(String[] args) {
    //创建集合对象
    Map<String, String> map = new HashMap<String, String>();

    //添加元素
    map.put("张无忌", "赵敏");
    map.put("郭靖", "黄蓉");
    map.put("杨过", "小龙女");

    //获取所有键值对对象的集合
    Set<Map.Entry<String, String>> entrySet = map.entrySet();
    //遍历键值对对象的集合,得到每一个键值对对象
    for (Map.Entry<String, String> me : entrySet) {
    //根据键值对对象获取键和值
    String key = me.getKey();
    String value = me.getValue();
    System.out.println(key + "," + value);
    }
    }
    }

2.HashMap集合

键:无序、不重复、无索引

2.1HashMap集合概述和特点

  • HashMap底层是哈希表结构的
  • 依赖hashCode方法和equals方法保证键的唯一
  • 如果键要存储的是自定义对象,需要重写hashCode和equals方法

2.2HashMap集合应用案例

  • 案例需求

    • 创建一个HashMap集合,键是学生对象(Student),值是居住地 (String)。存储多个元素,并遍历。
    • 要求保证键的唯一性:如果学生对象的成员变量值相同,我们就认为是同一个对象
  • 代码实现

    学生类

    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    public class Student {
    private String name;
    private int age;

    public Student() {
    }

    public Student(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 boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    Student student = (Student) o;

    if (age != student.age) return false;
    return name != null ? name.equals(student.name) : student.name == null;
    }

    @Override
    public int hashCode() {
    int result = name != null ? name.hashCode() : 0;
    result = 31 * result + age;
    return result;
    }
    }

    测试类

    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
    public class HashMapDemo {
    public static void main(String[] args) {
    //创建HashMap集合对象
    HashMap<Student, String> hm = new HashMap<Student, String>();

    //创建学生对象
    Student s1 = new Student("林青霞", 30);
    Student s2 = new Student("张曼玉", 35);
    Student s3 = new Student("王祖贤", 33);
    Student s4 = new Student("王祖贤", 33);

    //把学生添加到集合
    hm.put(s1, "西安");
    hm.put(s2, "武汉");
    hm.put(s3, "郑州");
    hm.put(s4, "北京");

    //遍历集合
    Set<Student> keySet = hm.keySet();
    for (Student key : keySet) {
    String value = hm.get(key);
    System.out.println(key.getName() + "," + key.getAge() + "," + value);
    }
    }
    }

2.3HashMap综合案例

现在需要统计A、B、C、D景点最喜欢的人数,并给出最最喜欢的景点

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.yyh.hashmap;

import java.util.*;

public class HashMapDemo02 {
public static void main(String[] args) {

String[] arr = {"A", "B", "C", "D"};
ArrayList<String> list = new ArrayList<>();
Random r = new Random();
for (int i = 0; i < 80; i++) {
int index = r.nextInt(arr.length);
list.add(arr[index]);
}

HashMap<String, Integer> hm = new HashMap<>();

for (String s : list) {
if (hm.containsKey(s)) {
Integer count = hm.get(s);
count++;
hm.put(s, count);
} else {
hm.put(s, 1);
}
}

System.out.println(hm);

int max = 0;
String loveSport = null;

Set<Map.Entry<String, Integer>> entries = hm.entrySet();
for (Map.Entry<String, Integer> entry : entries) {
String love = entry.getKey();
Integer value = entry.getValue();
if (value > max) {
loveSport = love;
max = value;
}
}

System.out.println("最喜欢的景点: " + loveSport + ", 人数: " + max);


}
}

3.LinkedHashMap

  • 由键决定:有序、不重复、无索引
  • 这里的有序指的是保证存储和取出的元素顺序一致
  • 原理:底层数据结构是依然哈希表,只是每个键值对元素又额外的多了一个双链表的机制记录存储的顺序

4.TreeMap集合

4.1TreeMap集合概述和特点

  • TreeMap底层是红黑树结构
  • 依赖自然排序或者比较器排序,对键进行排序
  • 如果键存储的是自定义对象,需要实现Comparable接口或者在创建TreeMap对象时候给出比较器排序规则

4.2TreeMap集合应用案例

  • 案例需求

    • 创建一个TreeMap集合,键是学生对象(Student),值是籍贯(String),学生属性姓名和年龄,按照年龄进行排序并遍历
    • 要求按照学生的年龄进行排序,如果年龄相同则按照姓名进行排序
  • 代码实现

    学生类

    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    public class Student implements Comparable<Student>{
    private String name;
    private int age;

    public Student() {
    }

    public Student(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 "Student{" +
    "name='" + name + '\'' +
    ", age=" + age +
    '}';
    }

    @Override
    public int compareTo(Student o) {
    //按照年龄进行排序
    int result = o.getAge() - this.getAge();
    //次要条件,按照姓名排序。
    result = result == 0 ? o.getName().compareTo(this.getName()) : result;
    return result;
    }
    }

    测试类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public class Test1 {
    public static void main(String[] args) {
    // 创建TreeMap集合对象
    TreeMap<Student,String> tm = new TreeMap<>();

    // 创建学生对象
    Student s1 = new Student("xiaohei",23);
    Student s2 = new Student("dapang",22);
    Student s3 = new Student("xiaomei",22);

    // 将学生对象添加到TreeMap集合中
    tm.put(s1,"江苏");
    tm.put(s2,"北京");
    tm.put(s3,"天津");

    // 遍历TreeMap集合,打印每个学生的信息
    tm.forEach(
    (Student key, String value)->{
    System.out.println(key + "---" + value);
    }
    );
    }
    }

5. :star2: 双列集合底层原理

5.1 :star: HashMap

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
1.看源码之前需要了解的一些内容

Node<K,V>[] table 哈希表结构中数组的名字,存放地址值

DEFAULT_INITIAL_CAPACITY: 数组默认长度16

DEFAULT_LOAD_FACTOR: 默认加载因子0.75



HashMap里面每一个对象包含以下内容:
1.1 链表中的键值对对象
包含:
int hash; //键的哈希值
final K key; //键
V value; //值
Node<K,V> next; //下一个节点的地址值


1.2 红黑树中的键值对对象
包含:
int hash; //键的哈希值
final K key; //键
V value; //值
TreeNode<K,V> parent; //父节点的地址值
TreeNode<K,V> left; //左子节点的地址值
TreeNode<K,V> right; //右子节点的地址值
boolean red; //节点的颜色



2.添加元素
HashMap<String,Integer> hm = new HashMap<>();
hm.put("aaa" , 111);
hm.put("bbb" , 222);
hm.put("ccc" , 333);
hm.put("ddd" , 444);
hm.put("eee" , 555);

添加元素的时候至少考虑三种情况:
2.1数组位置为null
2.2数组位置不为null,键不重复,挂在下面形成链表或者红黑树
2.3数组位置不为null,键重复,元素覆盖



//参数一:键
//参数二:值

//返回值:被覆盖元素的值,如果没有覆盖,返回null
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}


//利用键计算出对应的哈希值,再把哈希值进行一些额外的处理
//简单理解:返回值就是返回键的哈希值
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

//参数一:键的哈希值
//参数二:键
//参数三:值
//参数四:如果键重复了是否保留
// true,表示老元素的值保留,不会覆盖
// false,表示老元素的值不保留,会进行覆盖
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
//定义一个局部变量,用来记录哈希表中数组的地址值。
Node<K,V>[] tab;

//临时的第三方变量,用来记录键值对对象的地址值
Node<K,V> p;

//表示当前数组的长度
int n;

//表示索引
int i;

//把哈希表中数组的地址值,赋值给局部变量tab
tab = table;

if (tab == null || (n = tab.length) == 0){
//1.如果当前是第一次添加数据,底层会创建一个默认长度为16,加载因子为0.75的数组
//2.如果不是第一次添加数据,会看数组中的元素是否达到了扩容的条件
//如果没有达到扩容条件,底层不会做任何操作
//如果达到了扩容条件,底层会把数组扩容为原先的两倍,并把数据全部转移到新的哈希表中
tab = resize();
//表示把当前数组的长度赋值给n
n = tab.length;
}

//拿着数组的长度跟键的哈希值进行计算,计算出当前键值对对象,在数组中应存入的位置
i = (n - 1) & hash;//index
//获取数组中对应元素的数据
p = tab[i];


if (p == null){
//底层会创建一个键值对对象,直接放到数组当中
tab[i] = newNode(hash, key, value, null);
}else {
Node<K,V> e;
K k;

//等号的左边:数组中键值对的哈希值
//等号的右边:当前要添加键值对的哈希值
//如果键不一样,此时返回false
//如果键一样,返回true
boolean b1 = p.hash == hash;

if (b1 && ((k = p.key) == key || (key != null && key.equals(k)))){
e = p;
} else if (p instanceof TreeNode){
//判断数组中获取出来的键值对是不是红黑树中的节点
//如果是,则调用方法putTreeVal,把当前的节点按照红黑树的规则添加到树当中。
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
} else {
//如果从数组中获取出来的键值对不是红黑树中的节点
//表示此时下面挂的是链表
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
//此时就会创建一个新的节点,挂在下面形成链表
p.next = newNode(hash, key, value, null);
//判断当前链表长度是否超过8,如果超过8,就会调用方法treeifyBin
//treeifyBin方法的底层还会继续判断
//判断数组的长度是否大于等于64
//如果同时满足这两个条件,就会把这个链表转成红黑树
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
break;
}
//e: 0x0044 ddd 444
//要添加的元素: 0x0055 ddd 555
//如果哈希值一样,就会调用equals方法比较内部的属性值是否相同
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))){
break;
}

p = e;
}
}

//如果e为null,表示当前不需要覆盖任何元素
//如果e不为null,表示当前的键是一样的,值会被覆盖
//e:0x0044 ddd 555
//要添加的元素: 0x0055 ddd 555
if (e != null) {
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null){

//等号的右边:当前要添加的值
//等号的左边:0x0044的值
e.value = value;
}
afterNodeAccess(e);
return oldValue;
}
}

//threshold:记录的就是数组的长度 * 0.75,哈希表的扩容时机 16 * 0.75 = 12
if (++size > threshold){
resize();
}

//表示当前没有覆盖任何元素,返回null
return null;
}

5.2 :star: TreeMap

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
1.TreeMap中每一个节点的内部属性
K key; //键
V value; //值
Entry<K,V> left; //左子节点
Entry<K,V> right; //右子节点
Entry<K,V> parent; //父节点
boolean color; //节点的颜色




2.TreeMap类中中要知道的一些成员变量
public class TreeMap<K,V>{

//比较器对象
private final Comparator<? super K> comparator;

//根节点
private transient Entry<K,V> root;

//集合的长度
private transient int size = 0;



3.空参构造
//空参构造就是没有传递比较器对象
public TreeMap() {
comparator = null;
}



4.带参构造
//带参构造就是传递了比较器对象。
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}


5.添加元素
public V put(K key, V value) {
return put(key, value, true);
}

参数一:键
参数二:值
参数三:当键重复的时候,是否需要覆盖值
true:覆盖
false:不覆盖

private V put(K key, V value, boolean replaceOld) {
//获取根节点的地址值,赋值给局部变量t
Entry<K,V> t = root;
//判断根节点是否为null
//如果为null,表示当前是第一次添加,会把当前要添加的元素,当做根节点
//如果不为null,表示当前不是第一次添加,跳过这个判断继续执行下面的代码
if (t == null) {
//方法的底层,会创建一个Entry对象,把他当做根节点
addEntryToEmptyMap(key, value);
//表示此时没有覆盖任何的元素
return null;
}
//表示两个元素的键比较之后的结果
int cmp;
//表示当前要添加节点的父节点
Entry<K,V> parent;

//表示当前的比较规则
//如果我们是采取默认的自然排序,那么此时comparator记录的是null,cpr记录的也是null
//如果我们是采取比较去排序方式,那么此时comparator记录的是就是比较器
Comparator<? super K> cpr = comparator;
//表示判断当前是否有比较器对象
//如果传递了比较器对象,就执行if里面的代码,此时以比较器的规则为准
//如果没有传递比较器对象,就执行else里面的代码,此时以自然排序的规则为准
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else {
V oldValue = t.value;
if (replaceOld || oldValue == null) {
t.value = value;
}
return oldValue;
}
} while (t != null);
} else {
//把键进行强转,强转成Comparable类型的
//要求:键必须要实现Comparable接口,如果没有实现这个接口
//此时在强转的时候,就会报错。
Comparable<? super K> k = (Comparable<? super K>) key;
do {
//把根节点当做当前节点的父节点
parent = t;
//调用compareTo方法,比较根节点和当前要添加节点的大小关系
cmp = k.compareTo(t.key);

if (cmp < 0)
//如果比较的结果为负数
//那么继续到根节点的左边去找
t = t.left;
else if (cmp > 0)
//如果比较的结果为正数
//那么继续到根节点的右边去找
t = t.right;
else {
//如果比较的结果为0,会覆盖
V oldValue = t.value;
if (replaceOld || oldValue == null) {
t.value = value;
}
return oldValue;
}
} while (t != null);
}
//就会把当前节点按照指定的规则进行添加
addEntry(key, value, parent, cmp < 0);
return null;
}



private void addEntry(K key, V value, Entry<K, V> parent, boolean addToLeft) {
Entry<K,V> e = new Entry<>(key, value, parent);
if (addToLeft)
parent.left = e;
else
parent.right = e;
//添加完毕之后,需要按照红黑树的规则进行调整
fixAfterInsertion(e);
size++;
modCount++;
}



private void fixAfterInsertion(Entry<K,V> x) {
//因为红黑树的节点默认就是红色的
x.color = RED;

//按照红黑规则进行调整

//parentOf:获取x的父节点
//parentOf(parentOf(x)):获取x的爷爷节点
//leftOf:获取左子节点
while (x != null && x != root && x.parent.color == RED) {


//判断当前节点的父节点是爷爷节点的左子节点还是右子节点
//目的:为了获取当前节点的叔叔节点
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
//表示当前节点的父节点是爷爷节点的左子节点
//那么下面就可以用rightOf获取到当前节点的叔叔节点
Entry<K,V> y = rightOf(parentOf(parentOf(x)));
if (colorOf(y) == RED) {
//叔叔节点为红色的处理方案

//把父节点设置为黑色
setColor(parentOf(x), BLACK);
//把叔叔节点设置为黑色
setColor(y, BLACK);
//把爷爷节点设置为红色
setColor(parentOf(parentOf(x)), RED);

//把爷爷节点设置为当前节点
x = parentOf(parentOf(x));
} else {

//叔叔节点为黑色的处理方案


//表示判断当前节点是否为父节点的右子节点
if (x == rightOf(parentOf(x))) {

//表示当前节点是父节点的右子节点
x = parentOf(x);
//左旋
rotateLeft(x);
}
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateRight(parentOf(parentOf(x)));
}
} else {
//表示当前节点的父节点是爷爷节点的右子节点
//那么下面就可以用leftOf获取到当前节点的叔叔节点
Entry<K,V> y = leftOf(parentOf(parentOf(x)));
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
if (x == leftOf(parentOf(x))) {
x = parentOf(x);
rotateRight(x);
}
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateLeft(parentOf(parentOf(x)));
}
}
}

//把根节点设置为黑色
root.color = BLACK;
}







6.课堂思考问题:
6.1TreeMap添加元素的时候,键是否需要重写hashCode和equals方法?
此时是不需要重写的。


6.2HashMap是哈希表结构的,JDK8开始由数组,链表,红黑树组成的。
既然有红黑树,HashMap的键是否需要实现Compareable接口或者传递比较器对象呢?
不需要的。
因为在HashMap的底层,默认是利用哈希值的大小关系来创建红黑树的




6.3TreeMap和HashMap谁的效率更高?
如果是最坏情况,添加了8个元素,这8个元素形成了链表,此时TreeMap的效率要更高
但是这种情况出现的几率非常的少。
一般而言,还是HashMap的效率要更高。



6.4你觉得在Map集合中,java会提供一个如果键重复了,不会覆盖的put方法呢?
此时putIfAbsent本身不重要。
传递一个思想:
代码中的逻辑都有两面性,如果我们只知道了其中的A面,而且代码中还发现了有变量可以控制两面性的发生。
那么该逻辑一定会有B面。

习惯:
boolean类型的变量控制,一般只有AB两面,因为boolean只有两个值
int类型的变量控制,一般至少有三面,因为int可以取多个值。




6.5三种双列集合,以后如何选择?
HashMap LinkedHashMap TreeMap

默认:HashMap(效率最高)
如果要保证存取有序:LinkedHashMap
如果要进行排序:TreeMap





6. Properties

常用作配置文件properties,和IO操作有关,可以方便的读取和存储properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package all;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Properties;

public class propertiesDemo {
public static void main(String[] args) throws IOException {

Properties prop = new Properties();
prop.put("aaa", "111");
prop.put("bbb", "222");
prop.put("ccc", "333");
prop.put("ddd", "444");

FileOutputStream fos = new FileOutputStream("io\\src\\all\\a.properties");
prop.store(fos,"test");
fos.close();


}
}

三、集合嵌套

1
HashMap<String, ArrayList<String>> hm = new HashMap<>();

四、不可变集合

不可以修改、添加、删除集合中的内容,只可以查询

1
2
3
4
List.of("1", "2", "3");
Map.of() -> 不可以重复、键值对数量最多十个
Map.ofEntries() -> 键值对数量多个
Set.of() -> 元素不能重复

1.1 什么是不可变集合

​ 是一个长度不可变,内容也无法修改的集合

1.2 使用场景

​ 如果某个数据不能被修改,把它防御性地拷贝到不可变集合中是个很好的实践。

​ 当集合对象被不可信的库调用时,不可变形式是安全的。

简单理解:

​ 不想让别人修改集合中的内容

比如说:

1,斗地主的54张牌,是不能添加,不能删除,不能修改的

2,斗地主的打牌规则:单张,对子,三张,顺子等,也是不能修改的

3,用代码获取的操作系统硬件信息,也是不能被修改的

1.3 不可变集合分类

  • 不可变的list集合
  • 不可变的set集合
  • 不可变的map集合

1.4 不可变的list集合

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
35
36
37
38
39
40
41
42
public class ImmutableDemo1 {
public static void main(String[] args) {
/*
创建不可变的List集合
"张三", "李四", "王五", "赵六"
*/

//一旦创建完毕之后,是无法进行修改的,在下面的代码中,只能进行查询操作
List<String> list = List.of("张三", "李四", "王五", "赵六");

System.out.println(list.get(0));
System.out.println(list.get(1));
System.out.println(list.get(2));
System.out.println(list.get(3));

System.out.println("---------------------------");

for (String s : list) {
System.out.println(s);
}

System.out.println("---------------------------");


Iterator<String> it = list.iterator();
while(it.hasNext()){
String s = it.next();
System.out.println(s);
}
System.out.println("---------------------------");

for (int i = 0; i < list.size(); i++) {
String s = list.get(i);
System.out.println(s);
}
System.out.println("---------------------------");

//list.remove("李四");
//list.add("aaa");
list.set(0,"aaa");
}
}

1.5 不可变的Set集合

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
public class ImmutableDemo2 {
public static void main(String[] args) {
/*
创建不可变的Set集合
"张三", "李四", "王五", "赵六"


细节:
当我们要获取一个不可变的Set集合时,里面的参数一定要保证唯一性
*/

//一旦创建完毕之后,是无法进行修改的,在下面的代码中,只能进行查询操作
Set<String> set = Set.of("张三", "张三", "李四", "王五", "赵六");

for (String s : set) {
System.out.println(s);
}

System.out.println("-----------------------");

Iterator<String> it = set.iterator();
while(it.hasNext()){
String s = it.next();
System.out.println(s);
}

System.out.println("-----------------------");
//set.remove("王五");
}
}

1.6 不可变的Map集合

1.6.1:键值对个数小于等于10

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
35
public class ImmutableDemo3 {
public static void main(String[] args) {
/*
创建Map的不可变集合
细节1:
键是不能重复的
细节2:
Map里面的of方法,参数是有上限的,最多只能传递20个参数,10个键值对
细节3:
如果我们要传递多个键值对对象,数量大于10个,在Map接口中还有一个方法
*/

//一旦创建完毕之后,是无法进行修改的,在下面的代码中,只能进行查询操作
Map<String, String> map = Map.of("张三", "南京", "张三", "北京", "王五", "上海",
"赵六", "广州", "孙七", "深圳", "周八", "杭州",
"吴九", "宁波", "郑十", "苏州", "刘一", "无锡",
"陈二", "嘉兴");

Set<String> keys = map.keySet();
for (String key : keys) {
String value = map.get(key);
System.out.println(key + "=" + value);
}

System.out.println("--------------------------");

Set<Map.Entry<String, String>> entries = map.entrySet();
for (Map.Entry<String, String> entry : entries) {
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key + "=" + value);
}
System.out.println("--------------------------");
}
}

1.6.2:键值对个数大于10

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
35
36
37
38
39
40
41
42
public class ImmutableDemo4 {
public static void main(String[] args) {

/*
创建Map的不可变集合,键值对的数量超过10个
*/

//1.创建一个普通的Map集合
HashMap<String, String> hm = new HashMap<>();
hm.put("张三", "南京");
hm.put("李四", "北京");
hm.put("王五", "上海");
hm.put("赵六", "北京");
hm.put("孙七", "深圳");
hm.put("周八", "杭州");
hm.put("吴九", "宁波");
hm.put("郑十", "苏州");
hm.put("刘一", "无锡");
hm.put("陈二", "嘉兴");
hm.put("aaa", "111");

//2.利用上面的数据来获取一个不可变的集合
/*
//获取到所有的键值对对象(Entry对象)
Set<Map.Entry<String, String>> entries = hm.entrySet();
//把entries变成一个数组
Map.Entry[] arr1 = new Map.Entry[0];
//toArray方法在底层会比较集合的长度跟数组的长度两者的大小
//如果集合的长度 > 数组的长度 :数据在数组中放不下,此时会根据实际数据的个数,重新创建数组
//如果集合的长度 <= 数组的长度:数据在数组中放的下,此时不会创建新的数组,而是直接用
Map.Entry[] arr2 = entries.toArray(arr1);
//不可变的map集合
Map map = Map.ofEntries(arr2);
map.put("bbb","222");*/


//Map<Object, Object> map = Map.ofEntries(hm.entrySet().toArray(new Map.Entry[0]));

Map<String, String> map = Map.copyOf(hm);
map.put("bbb","222");
}
}