Map接口

Map集合

1. map是一个双列集合,每次添加元素都是按对添加,分为键和值。

2. 键是唯一的,不可以重复,值可以重复。键和值之间是一一对应的关系。

3. 一个键和值我们称之为"键值对"或者是"键值对对象",在java中叫做"Entry对象"

Map中的体系结构

image-20230130210434834

Map中常见API

Map是双列集合的顶层接口,它的功能是全部双列集合都可以继承使用的。

方法名称说明
V put(K key,V value)添加元素
V remove(Object key)根据键删除键值对元素
void clear()移除所有的键值对元素
boolean containsKey(Object key)判断集合是否包含指定的键
boolean containsValue(Object value)判断集合是否包含指定的值
boolean isEmpty()判断集合是否为空
int size()集合的长度,也就是集合中键值对的个数

Map的遍历方式

键找值

  1. 先获取所有键的单列集合
  2. 遍历单列集合
  3. 通过遍历出来的键使用get方法找到对应的值
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) {
Map<String,String> map = new HashMap<>();
map.put("尹志平","小龙女");
map.put("郭靖","穆念慈");
map.put("欧阳克","黄蓉");
//通过键找值
Set<String> keys = map.keySet();
//增强for遍历
for(String key:keys){
System.out.println("增强for:"+map.get(key));
}
//迭代器遍历
Iterator<String> iterator = keys.iterator();
while (iterator.hasNext()){
System.out.println("迭代器:"+map.get(iterator.next()));
}
//Lambda表达式
keys.forEach(s-> {
System.out.println("Lambda表达式:"+map.get(s));
}
);
}

依次获取键值对

  1. 依次获取键值对对象(Entry对象)
  2. 通过getKey()获取键,通过getValue()获取值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static void main(String[] args) {
Map<String,String> map = new HashMap<>();
map.put("尹志平","小龙女");
map.put("郭靖","穆念慈");
map.put("欧阳克","黄蓉");
//依次遍历键值对对象
//Map.Entry:Entry是Map接口的内部接口,通过这种方式去访问
Set<Map.Entry<String, String>> entries = map.entrySet();
//增强for
for (Map.Entry<String, String> entry : entries) {
System.out.println(entry.getKey()+":"+entry.getValue());
}
//Lambda表达式
entries.forEach((stringStringEntry) ->{
System.out.println(stringStringEntry.getKey()+":"+stringStringEntry.getValue());
});
//迭代器
Iterator<Map.Entry<String, String>> iterator = entries.iterator();
while (iterator.hasNext()){
Map.Entry<String, String> next = iterator.next();
System.out.println(next.getKey()+":"+next.getValue());
}
}

Lambda表达式

底层就是利用第二种方式(依次获取键值对)遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void main(String[] args) {
HashMap<String, String> map = new HashMap<>();

map.put("鲁迅","这句话是我说的");
map.put("曹操","不可能绝对不可能");
map.put("刘备","接着奏乐接着舞");
map.put("柯镇恶","看我颜色行事");
//底层就是获取entrySet依次遍历
map.forEach(new BiConsumer<String, String>() {
@Override
public void accept(String key, String value) {
System.out.println(key+"="+value);
}
});

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

map.forEach((String key, String value) ->{
System.out.println(key+"="+value);
});

HashMap

1.HashMap是Map里面的一个实现类。

2.没有额外需要学习的特有方法,直接使用Map里面的方法就可以了

3.特点都是有键决定的:存取无序、键不重复、无索引

4.HashMap跟HashSet底层原理是一模一样的,都是哈希表结构

底层原理

  1. 创建一个长度为16,默认加载因子为0.75
  2. 利用put方法添加数据时,会先创建一个Entry对象,利用键计算哈希值,跟值无关,判断哈希值在数组中的索引位置
    1. 如果该位置为null则直接存入
    2. 如果该位置不为null,会调用equals方法比较键的属性值,如果键里面的属性值一样,就会覆盖原有的Entry对象
      1. 如果键的属性值不一样,就会添加新的Entry对象
      2. 在Jdk8以前,新元素会添加在数组中,原先的会挂在下面,形成链表
      3. jdk8以后就直接添加在原先元素的下面,形成链表
      4. jdk8以后,为了提高查询速度,当链表长度超过8并且数组长度大于等于64,自动转为红黑树

小结

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

练习

需求:创建一个HashMap集合,键是学生对象,值是籍贯
存储三个键值对元素并遍历
要求:同姓名,同年龄认为是同一个学生

核心点:HashMap的键位置如果存储的是自定义对象,需要重写hashCode和equals方法

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
package com.zhuixun.demo2;

import java.util.HashMap;
import java.util.Objects;
import java.util.function.BiConsumer;

/**
* @Author: zhuixun
* @Date: 2023/1/30 22:10
* @Version 1.0
*/
public class MapDemo4 {
/**
* 需求:创建一个HashMap集合,键是学生对象,值是籍贯
* 存储三个键值对元素并遍历
* 要求:同姓名,同年龄认为是同一个学生
* @param args
*/
public static void main(String[] args) {
HashMap<Student, String> map = new HashMap<>();
map.put(new Student("小明",15),"南昌");
map.put(new Student("小红",22),"无锡");
map.put(new Student("小熊",15),"深圳");
map.put(new Student("小熊",15),"深圳");
map.forEach((Student student, String s)-> {
System.out.println("籍贯:"+s);
System.out.println("姓名:"+student.getName());
System.out.println("年龄:"+student.getAge());
System.out.println("------------");
}
);
}
}
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 String toString() {
return "Student{" +
"name='" + name + '\'' +
", 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;
return age == student.age &&
Objects.equals(name, student.name);
}

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

需求:

​ 某班级80名学生,现在需要组成秋游活动,班长提供四个景点依次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
49
package com.zhuixun.demo2;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.function.BiConsumer;

/**
* @Author: zhuixun
* @Date: 2023/1/30 22:24
* @Version 1.0
*/
public class MapDemo5 {
public static void main(String[] args) {
//定义一个数组存取A、B、C、D
String[] str = {"A","B","C","D"};
//定义一个HashMap
HashMap<String, Integer> map = new HashMap<>();
//学生随机投票
Random random = new Random();
for (int i =0;i<80;i++){
String s = str[random.nextInt(4)];
if (map.containsKey(s)){
Integer integer = map.get(s);
map.put(s,integer.intValue()+1);
}else{
map.put(s,1);
}
}
String jq;
int max =0;
//遍历求最大值
Set<Map.Entry<String, Integer>> entries = map.entrySet();
for (Map.Entry<String, Integer> entry : entries) {
System.out.println(entry.getKey()+":"+entry.getValue());
if (entry.getValue()>max){
max=entry.getValue();
}
}
System.out.println("最大值"+max);
for (Map.Entry<String, Integer> entry : entries) {
if (entry.getValue()==max){
System.out.println(entry.getKey()+"景区的票数最多");
}
}
}
}

源码解析

快捷键CTRL+F12

image-20230207203941714

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
171
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;
}

LinkedHashMap

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

TreeMap

  • TreeMap跟TreeSet底层原理一样,都是红黑树结构的,增删改查性能较好
  • 由键决定特性:不重复、无索引、可排序
  • 可排序:对键进行排序
  • 注意:默认按照键的从小到大进行排序,也可以自己规定键的排序规则

代码书写两种排序规则

  • 实现Comparable接口,指定比较规则
  • 创建集合时传递Comparator比较器对象,指定比较规则
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
package com.zhuixun.demo2;

import java.util.Comparator;
import java.util.TreeMap;
import java.util.TreeSet;

/**
* @Author: zhuixun
* @Date: 2023/2/6 22:50
* @Version 1.0
*/
public class TreeMapDemo {
public static void main(String[] args) {
/**
* TreeMap集合基本应用:
* 需求1:
* 键:整数表示id
* 值:字符串表示商品名称
* 要求1:按照id的升序排序
* 要求2:按照id的降序排序
*/

//Integer Double默认情况下都是按照升序排列的
TreeMap<Integer,String> tm = new TreeMap<>();
tm.put(5,"可口可乐");
tm.put(4,"雪碧");
tm.put(3,"六个核桃");
tm.put(2,"康师傅");
tm.put(1,"营养快线");
//{1=营养快线, 2=康师傅, 3=六个核桃, 4=雪碧, 5=可口可乐}
System.out.println(tm);

//为了降序排序需要传递Comparator对象,重写比较规则
TreeMap<Integer,String> tm2 = new TreeMap<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
});
tm2.put(5,"可口可乐");
tm2.put(4,"雪碧");
tm2.put(3,"六个核桃");
tm2.put(2,"康师傅");
tm2.put(1,"营养快线");
//{5=可口可乐, 4=雪碧, 3=六个核桃, 2=康师傅, 1=营养快线}
System.out.println(tm2);
}
}

需求:
字符串:”aababcabcdabcde”
统计字符串中每一个字符出现的吃书,并按照格式输出
a(5)b(4)c(3)d(2)e(1)

新的统计思想:

  1. 利用Map集合进行统计

  2. 如果题目中没有要求对结果进行排序:默认使用HashMap

  3. 要求对结果进行排序,请使用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
package com.zhuixun.demo2;

import java.util.StringJoiner;
import java.util.TreeMap;
import java.util.function.BiConsumer;

/**
* @Author: zhuixun
* @Date: 2023/2/6 23:13
* @Version 1.0
*/
public class TreeMapDemo2 {
public static void main(String[] args) {
/**
* 需求:
* 字符串:"aababcabcdabcde"
* 统计字符串中每一个字符出现的吃书,并按照格式输出
* a(5)b(4)c(3)d(2)e(1)
*
* 新的统计思想:利用Map集合进行统计
*
* 如果题目中没有要求对结果进行排序:默认使用HashMap
* 如果题目中要求对结果进行排序,请使用TreeMap
*
* 键:表示要统计的内容
* 值:表示次数
*/
String s = "aababcabcdabcde";
TreeMap<Character, Integer> tm = new TreeMap<>();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
System.out.println(c);
if (tm.containsKey(c)){
int count = tm.get(c);
count++;
tm.put(c,count);
}else{
tm.put(c,1);
}
}

//遍历集合,按照指定格式拼接
StringBuilder sb = new StringBuilder();
StringJoiner sj = new StringJoiner("","","");
tm.forEach((Character key, Integer value)-> sb.append(key).append("(").append(value).append(")"));
tm.forEach((Character key, Integer value)-> sj.add(key+"").add("(").add(value+"").add(")"));
System.out.println("sb:"+sb);
System.out.println("sj:"+sj);
}
}

源码解析

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
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;
}



思考问题

  • TreeMap添加元素的时候,键是否需要重写hashCode和equals方法?

    回答:此时是不需要重写的。因为hashCode和equals与hashmap中的键有关,并且在TreeMap源码中也没看到重写hashCode和equals方法

  • HashMap是哈希表结构的,JDK8开始由数组,链表,红黑树组成的。既然有红黑树,HashMap的键是否需要实现Compareable接口或者传递比较器对象呢?

​ 回答:不需要的。因为在HashMap的底层,默认是利用哈希值的大小关系来创建红黑树的

  • TreeMap和HashMap谁的效率更高?

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

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

1
2
3
习惯:
boolean类型的变量控制,一般只有AB两面,因为boolean只有两个值
int类型的变量控制,一般至少有三面,因为int可以取多个值。
  • 三种双列集合,以后如何选择?
    HashMap LinkedHashMap TreeMap
1
2
3
默认:HashMap(效率最高)
如果要保证存取有序:LinkedHashMap
如果要进行排序:TreeMap

可变参数

  1. 可变参数本质上就是一个数组

  2. 作用:在形参中接收多个数据

  3. 格式:数据类型…参数名称

    举例:int…a

  4. 注意事项:

    • 形参列表中可变参数只能有一个
    • 可变参数必须放在形参列表的最后面

Collections

1. java.util.Collections:是集合工具类

2. 作用:Collections不是集合,而是集合的工具类

Collections常用的API

方法名称说明
public staticboolean addAll(Collectionc,T…elements)批量添加元素
public static void shuffle(List<?> list)打乱List集合元素的顺序
public staticvoid sort(Listlist)排序
public staticvoid sort(Listlist,Comparatorc)根据指定规则排序
public staticint binarySearch(Listlist,T key)以二分查找法查找元素
public staticvoid copy(Listdest,Listsrc)拷贝集合中的元素
public staticint fill(Listlist,T obj)使用指定元素填充集合
public staticvoid max/min(Collectioncoll)根据默认的自然排序获取最大/小值
public staticvoid swap(List<?> list, int i, int j)交换集合中指定位置的元素

综合练习

  1. 班级有N个学生

    需求:70%的概率随机到男生,30%的概率到女生

    思路:Random随机抽取某一个数,这个是随机点。概率问题就是一个随机面(可以把1,1,1,1,1,1,1,0,0,0放入一个数组,再去Random随机,1就去再去随机男生的list,0就去随机女生的list)

    image-20230208205914075

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
package com.zhuixun.demo2;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;

/**
* @Author: zhuixun
* @Date: 2023/2/8 21:02
* @Version 1.0
*/
public class Test {
public static void main(String[] args) {
//1。创建集合
ArrayList<Integer> list = new ArrayList<>();
ArrayList<String> maleList = new ArrayList<>();
ArrayList<String> girlList = new ArrayList<>();
//2.添加数据
Collections.addAll(list,1,1,1,1,1,1,1,0,0,0);
Collections.addAll(maleList,"男1","男2","男3","男4","男5");
Collections.addAll(girlList,"女1","女2","女3","女4","女5");
//随机
Random random = new Random();
for (int j = 0; j < 10; j++) {
//在10个数中随机抽取一个数,根据1还是0判断从女生list抽取还是从男生list抽取
int i = random.nextInt(list.size());
if (list.get(i)==1){
//是男生,随机获取一个男生
int i1 = random.nextInt(maleList.size());
System.out.println(maleList.get(i1));
}else{
int i1 = random.nextInt(girlList.size());
System.out.println(girlList.get(i1));
}
}

}
}

  1. 班级有N个学生

    要求:

    被点到的学生不会再被点到,但是如果班级中所有的学生都点完了,

    需要重新开启第二轮点名。

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
package com.zhuixun.demo2;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;

/**
* @Author: zhuixun
* @Date: 2023/2/8 21:18
* @Version 1.0
*/
public class Test2 {
public static void main(String[] args) {
//1.创建list
ArrayList<String> nameList = new ArrayList<>();
ArrayList<String> alreadyNameList = new ArrayList<>();
//2.添加数据
Collections.addAll(nameList,"范闲","范建","范统","杜子腾","杜祺眼","宋合泛","朱轶群");
for (int j = 0; j < 5; j++) {
System.out.println("=========第"+(j+1)+"轮点名开始============");
int size = nameList.size();
Random random = new Random();
for (int i1 = 0; i1 < size; i1++) {
int i = random.nextInt(nameList.size());
String s = nameList.get(i);
System.out.println("被点到的:"+s);
alreadyNameList.add(s);
nameList.remove(i);

}
//重新赋值
nameList.addAll(alreadyNameList);
alreadyNameList.clear();
System.out.println("新一轮的原始数据:"+nameList);
}

}
}


Map接口
http://example.com/2023/01/30/Java基础/集合/Map接口/
作者
zhuixun
发布于
2023年1月30日
许可协议