javaSE基础复习之Map集合

课程笔记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,智障

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值