课程笔记Day15
- Map集合
- 泛型
- Collections
第一章 Map集合
第01节 基础理论
Map集合是什么?
Collection 是单列集合。每次存放的是一个数据。例如: List集合 或 Set集合。
有些情况下,可能数据之间存在关联关系。不是一个,而是成对出现。这种情况下,就需要使用 Map集合。
Map集合也叫作 双列集合。相当于是夫妻对。
一夫一妻制。 一个丈夫,就对应一个媳妇。
Map集合是双列集合,由键值对组成(key-value)其中 key 不可以重复的,value是可以重复的。(Map集合的特点)
第02节 HashMap集合
快速入门
import java.util.HashMap;
//目标:学习HashMap集合的快速入门
public class Test01 {
public static void main(String[] args) {
//创建对象
HashMap<String,String> map = new HashMap<>();
//添加数据
map.put("吴亦凡","都美竹");
map.put("吴亦凡","未满18少女");
map.put("皮几万","李小璐");
map.put("宋吉吉","马蓉");
//展示结果
System.out.println(map);
//{皮几万=李小璐, 宋吉吉=马蓉, 吴亦凡=未满18少女}
}
}
底层原理
JDK版本 | 底层原理 |
---|---|
JDK7之前的版本 (包括JDK7) | 数组 + 链表 |
JDK8之后的版本 (包括JDK8) | 数组 + 链表 + 红黑树 |
简述说明:从JDK7版本到 JDK8版本。主要是把红黑树引入进来了,好处:提高了查询的效率。
例如:猜数字游戏,采用的是二分查找,而红黑树,也可以采用二分查找的方式。
常用API
方法介绍
方法API | 方法说明 |
---|---|
public V put(K,V) | 将指定的键和指定的值,添加到Map集合当中 |
public V remove(Object) | 将指定的键和所对应的值,删除,返回被删除元素 |
public V get(Object) | 根据指定的键,在Map集合当中获取对应的值 |
public boolean containsKey(Object key) | 判断集合当中是否包含指定的键 |
public Set keySet() | 获取Map集合当中所有的键,存储到Set集合 |
public Set<Map.Entry<K,V>> entrySet() | 获取Map集合当中所有键值对对象,存储到Set集合 |
遍历方式
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
//目标:学习Map集合的遍历方式
public class Test03 {
public static void main(String[] args) {
HashMap<String, String> map = new HashMap<>();
map.put("玄慈", "叶二娘");
map.put("段正淳", "很多");
map.put("韦小宝", "七个");
System.out.println("方式1:keySet() 召集所有的丈夫,去找到各自的媳妇");
//调用keySet()方法,返回的是所有键的集合,集合是一个Set集合
Set<String> keys = map.keySet();
for (String key : keys) {
//通过键key 获取值value
String value = map.get(key);
//展示结果
System.out.println("丈夫:" + key + ",媳妇:" + value);
}
System.out.println("方式2:entrySet() 获取到结婚证对象,通过结婚证找到键值");
//调用 entrySet() 方法,返回的是所有 (键值)组合的 entry 的Set集合
Set<Map.Entry<String, String>> entries = map.entrySet();
//获取到结婚证 entry的对象,在entry当中包含有 键 和 值
for (Map.Entry<String, String> entry : entries) {
//拿到结婚证,直接获取到丈夫和媳妇
String key = entry.getKey();
String value = entry.getValue();
//展示结果
System.out.println("丈夫:" + key + ",媳妇:" + value);
}
}
}
练习题
题目
键盘录入一个字符串,统计每个字符出现的次数。
例如:
请输入一个字符串:
川哥,你真帅啊,666
展示的结果是:
川 1次
哥 1次
, 2次
你 1次
真 1次
帅 1次
啊 1次
6 3次
分析
我们可以看到,这里是一种 "一一对应问题" 因为是一一对应的问题,所以我们需要考虑,Map集合。
既然使用的是 Map 集合,你就要考虑,创建对象。 需要考虑 键和值 怎么写?
键: Character 包装类。 因为键不能重复。
值: Integer 包装类。 值可以重复
创建对象的格式: HashMap<Character,Integer> map = new HashMap<>();
问题: 我们怎么去统计个数呢?
这里是字符串 String类型,我们操作的是 char 类型,如果是字符串问题,将字符串打散成为字符。
可以对字符串进行遍历操作,获取到单独的字符。针对于字符进行判断。
问题:如何判断字符出现的次数?
每次出现,我们可以让一个变量 +1
怎么判断出现呢?
前面刚刚学习过的一个方法,containsKey(Object key) 如果存在则+1
代码
//练习:键盘录入字符串,获取每个字符出现的次数
public class Test04 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个字符串:");
String str = sc.nextLine();
//因为是一一对应问题,考虑Map集合完成
HashMap<Character, Integer> map = new HashMap<>();
//需要将字符串,打散,成为单独的字符,进行操作。
for (int i = 0; i < str.length(); i++) {
//获取到单独的字符
char ch = str.charAt(i);
// 在这里 ch 有两重身份。
// 身份1:他是字符串的其中一个字符。身份2: 他是Map集合键
//判断在 Map集合当中的 key 是否包含有 ch
if (map.containsKey(ch)) {
//如果之前包含有,表示不是第一次出现。
Integer count = map.get(ch);
count++;
map.put(ch, count);
} else {
//如果之前不包含,表示是第一次出现。
map.put(ch, 1);
}
}
//输出结果(可以进行遍历集合的操作)
Set<Character> keys = map.keySet();
for (Character key : keys) {
Integer value = map.get(key);
System.out.println("字符:" + key + " 出现了" + value + "次");
}
}
}
第03节 TreeMap集合
作用
用法和 HashMap 用法几乎一样。
作用:可以进行排序的操作。可以针对于 键进行排序的操作。(涉及到比较器的概念)
练习
//练习:键盘录入字符串,获取每个字符出现的次数
@SuppressWarnings("all")
public class Test05 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个字符串:");
String str = sc.nextLine();
//因为是一一对应问题,考虑Map集合完成
TreeMap<Character, Integer> map = new TreeMap<>(new Comparator<Character>() {
@Override
public int compare(Character o1, Character o2) {
//降序排列
int result = o2 - o1;
return result;
}
});
//需要将字符串,打散,成为单独的字符,进行操作。
for (int i = 0; i < str.length(); i++) {
//获取到单独的字符
char ch = str.charAt(i);
// 在这里 ch 有两重身份。
// 身份1:他是字符串的其中一个字符。身份2: 他是Map集合键
//判断在 Map集合当中的 key 是否包含有 ch
if (map.containsKey(ch)) {
//如果之前包含有,表示不是第一次出现。
Integer count = map.get(ch);
count++;
map.put(ch, count);
} else {
//如果之前不包含,表示是第一次出现。
map.put(ch, 1);
}
}
//输出结果(可以进行遍历集合的操作)
Set<Character> keys = map.keySet();
for (Character key : keys) {
Integer value = map.get(key);
System.out.println("字符:" + key + " 出现了" + value + "次");
}
}
}
第二章 泛型
第01节 基础理论
什么泛型?
泛型是 用来规定我们可能需要的数据类型,可以将运行时问题,提前到编译时期。
如果我们不使用泛型,容易出现类型转换的异常 ClassCastException 出现运行时问题。
解决问题最好的方式是提前预防,如果在代码刚刚写完,就能够预警,提示修复,那是最好的,也就是编译时就能报错。
泛型就是未知的数据类型。
大家需要转换一个角度去考虑问题,站在 集合设计者的角度去考虑,而不是集合的使用者角度考虑。
泛型的分类
1. 泛型类
2. 泛型方法
3. 泛型接口
4. 泛型通配符
第02节 泛型类
定义泛型类
//盒子类,定义成为泛型类
public class Box<VIP> {
//定义数据类型
private VIP ip;
//生成方法
public VIP getIp() {
//查看类型通过字节码的对象
System.out.println(ip.getClass());
return ip;
}
public void setIp(VIP ip) {
this.ip = ip;
//查看类型通过字节码的对象
System.out.println(ip.getClass());
}
}
使用泛型类
//测试类
public class Test02 {
public static void main(String[] args) {
Box<String> one = new Box<>();
one.setIp("嘻嘻");
String ip1 = one.getIp();
System.out.println("ip1 = " + ip1);
System.out.println("----------");
Box<Boolean> two = new Box<>();
two.setIp(true);
Boolean ip2 = two.getIp();
System.out.println("ip2 = " + ip2);
}
}
//class java.lang.String
//class java.lang.String
//ip1 = 嘻嘻
//----------
//class java.lang.Boolean
//class java.lang.Boolean
//ip2 = true
第03节 泛型方法
案例代码
//目标:学习泛型方法的使用
public class Test03 {
public static void main(String[] args) {
//调用方法
method(6.6666);
//调用方法
method("hello");
}
//这就是泛型方法,前面的<VIP>是为了告诉 JVM 这是方法的泛型
public static <VIP> void method(VIP ip){
System.out.println(ip.getClass());
System.out.println(ip);
}
}
第04节 泛型接口
案例代码
接口
//泛型接口
public interface JieKou<ONE,TWO> {
//定义了抽象方法
public abstract void methodAbstract(ONE o,TWO t);
}
实现类
//实现了泛型接口的 泛型的实现类
public class ShiXian<ONE,TWO> implements JieKou<ONE,TWO> {
@Override
public void methodAbstract(ONE o, TWO t) {
System.out.println(o.getClass());
System.out.println(t.getClass());
System.out.println(o);
System.out.println(t);
}
}
测试类
public class Test04 {
public static void main(String[] args) {
//创建对象
ShiXian<String,Integer> sx1 = new ShiXian<>();
sx1.methodAbstract("hello",666);
System.out.println("------------");
//创建对象
ShiXian<Double,Boolean> sx2 = new ShiXian<>();
sx2.methodAbstract(6.6666,true);
}
}
//class java.lang.String
//class java.lang.Integer
//hello
//666
//------------
//class java.lang.Double
//class java.lang.Boolean
//6.6666
//true
第05节 泛型通配符
基础理论
有时候,泛型我们自己也不能确定,有可能是父子关系。一般在方法当中使用的话,我们可以采用泛型的通配符写法。
格式是 <?> 泛型通配符。
引出两个概念:
(1)泛型的上限限定 <? extends 类名称>
(2)泛型的下限限定 <? super 类名称>
案例代码
//目标:泛型的通配符(泛型的上限限定和泛型的下限限定)
public class Test05 {
//知晓一个知识点:
//class Integer extends Number extends Object{ ... }
public static void main(String[] args) {
ArrayList<Object> array1 = new ArrayList<>();
ArrayList<Number> array2 = new ArrayList<>();
ArrayList<Integer> array3 = new ArrayList<>();
ArrayList<String> array4 = new ArrayList<>();
//调用下面的方法,分别作为参数传递。
//limitUp(array1); //报错误
limitUp(array2);
limitUp(array3);
//limitUp(array4); //报错误
System.out.println("-------");
limitDown(array1);
limitDown(array2);
//limitDown(array3); //报错误
//limitDown(array4); //报错误
}
//定义一个普通的方法【上限限定】 指的是 儿子和自己是可以的。(最高是他自己)
public static void limitUp(ArrayList<? extends Number> array){
}
//定义一个普通的方法【下限限定】 指的是 自己和父亲是可以的。(最低是他自己)
public static void limitDown(ArrayList<? super Number> array){
}
}
第三章 Collections
第01节 基础理论
Collections 是我们单列集合的工具类。
对比 Collection 和 Collections 有什么区别呢?
1. Collection 单列集合的顶层父接口
2. Collections我们集合的工具类
常用方法
方法API | 方法说明 |
---|---|
public static boolean addAll(Collection,T…) | 集合批量添加 |
public static void shuffle(List<?>) | 打乱集合顺序 |
public static void sort(List) | 集合顺序默认排序 |
public static void sort(List,Comparator<? super T>) | 集合指定规则排序 |
public static void reverse(List<?>) | 反转指定列表中顺序 |
public static min(Collection<? extends T>) | 返回集合当中最小元素 |
public static max(Collection<? extends T>) | 返回集合当中最大元素 |
public static void swap(List<?>,int,int) | 交换列表中对应位置元素 |
public static binarySearch(List,T) | 二分查找,条件:提前排序 |
public static boolean replaceAll(List,T,T) | 列表当中元素替换操作 |
第02节 案例代码
//目标:学习Collections的 addAll()方法
public class Test01 {
public static void main(String[] args) {
//定义一个集合
List<String> list = new ArrayList<>();
list.add("徐峰");
list.add("周健");
list.add("肖传轩");
list.add("金铎");
//调用 shuffle 方法
Collections.shuffle(list);
System.out.println("list = " + list);
}
public static void testAddAll() {
//定义一个集合
List<String> list = new ArrayList<>();
list.add("java");
list.add("hello");
list.add("world");
//集合数据的批量添加
Collections.addAll(list,"嘻嘻","嘿嘿","呵呵");
System.out.println("list = " + list);
}
}
今日总结
今日主要是学HashMap集合
先来点简单的,介绍下 HashMap 的底层数据结构吧。
面试官:为什么要改成“数组+链表+红黑树”?
浩哥: 主要是为了提升在 hash 冲突严重时(链表过长)的查找性能,使用链表的查找性能是 O(n),而使用红黑树是 O(logn)。
面试官: 那在什么时候用链表?什么时候用红黑树?
浩哥:对于插入,默认情况下是使用链表节点。当同一个索引位置的节点在新增后达到9个(阈值8):如果此时数组长度大于等于 64,则会触发链表节点转红黑树节点(treeifyBin);而如果数组长度小于64,则不会触发链表转红黑树,而是会进行扩容,因为此时的数据量还比较小。
》
对于移除,当同一个索引位置的节点在移除后达到 6 个,并且该索引位置的节点为红黑树节点,会触发红黑树节点转链表节点(untreeify)。
面试官: 为什么链表转红黑树的阈值是8?
浩哥: 我们平时在进行方案设计时,必须考虑的两个很重要的因素是:时间和空间。对于 HashMap 也是同样的道理,简单来说,阈值为8是在时间和空间上权衡的结果(这 B 我装定了)。
红黑树节点大小约为链表节点的2倍,在节点太少时,红黑树的查找性能优势并不明显,付出2倍空间的代价作者觉得不值得。
理想情况下,使用随机的哈希码,节点分布在 hash 桶中的频率遵循泊松分布,按照泊松分布的公式计算,链表中节点个数为8时的概率为 0.00000006(跟大乐透一等奖差不多,中大乐透?不存在的),这个概率足够低了,并且到8个节点时,红黑树的性能优势也会开始展现出来,因此8是一个较合理的数字。
面试官: 那为什么转回链表节点是用的6而不是复用8?
浩哥: 如果我们设置节点多于8个转红黑树,少于8个就马上转链表,当节点个数在8徘徊时,就会频繁进行红黑树和链表的转换,造成性能的损耗。
面试官: 那 HashMap 有哪些重要属性?分别用于做什么的?
浩哥: 除了用来存储我们的节点 table 数组外,
HashMap 还有以下几个重要属性:
1)size:HashMap 已经存储的节点个数;
2)threshold:扩容阈值,当 HashMap 的个数达到该值,触发扩容。
3)loadFactor:负载因子,扩容阈值 = 容量 * 负载因子
面试官: HashMap 的默认初始容量是多少?HashMap 的容量有什么限制吗?
默认初始容量是16。HashMap 的容量必须是2的N次方,HashMap 会根据我们传入的容量计算一个大于等于该容量的最小的2的N次方,例如传 9,容量为16。
面试官: 你说 HashMap 的容量必须是 2 的 N 次方,这是为什么?
浩哥: 计算索引位置的公式为:(n - 1) & hash,当 n 为 2 的 N 次方时,n - 1 为低位全是 1 的值,此时任何值跟 n - 1 进行 & 运算的结果为该值的低 N 位,达到了和取模同样的效果,实现了均匀分布。实际上,这个设计就是基于公式:x mod 2^n = x & (2^n - 1),因为 & 运算比 mod 具有更高的效率。
面试官: 刚才说的负载因子默认初始值又是多少?
浩哥: 负载因子默认值是0.75。
面试官: 为什么是0.75而不是其他的?
浩哥:(又问这种憨逼问题)这个也是在时间和空间上权衡的结果。如果值较高,例如1,此时会减少空间开销,但是 hash 冲突的概率会增大,增加查找成本;而如果值较低,例如 0.5 ,此时 hash 冲突会降低,但是有一半的空间会被浪费,所以折衷考虑 0.75 似乎是一个合理的值。
面试官: HashMap 的插入流程是怎么样的?
浩哥: Talk is cheap. Show you the picture。
面试官: 除了 HashMap,还用过哪些 Map,在使用时怎么选择?
面试官:HashMap 为什么这么吊???
浩哥: 为什么会问这么傻逼的问题,真他妈的操蛋,不面了,md,智障