• 重学 JavaSE 高阶



    我本无心入江南,奈何江南入我心


    在这里插入图片描述


    一、时间日期

    1. data

    ⑴. 时间分类

    • 世界标准时间: 格林尼治时间 / 格林威治时间(Greenwich Mean Time)简称 GMT
    • 中国的标准时间: 世界标准时间 + 8小时
    • 计算机中的起始时间: 1970年1月1日 00:00:00

    1969年8月,贝尔实验室的程序员肯汤普逊利用妻儿离开一个月的机会,开始着手创造一个全新的革命性的操作系统,他使用B编译语言在老旧的PDP-7机器上开发出了Unix的一个版本。 随后,汤普逊和同事丹尼斯里奇改进了B语言,开发出了C语言,重写了UNIX。 1970年1月1日 算C语言的生日


    ⑵. 构造方法和常用方法

    方法名描述
    public Date()阿创建一个Date对象,表示默认时间发
    public Date(long date)阿创建一个Date对象,表示指定时间发
    public long getTime()获取时间对象的毫秒值
    public void setTime(long time)设置时间,传递毫秒值

    示例:

    public class test01 {
        public static void main(String[] args) {
            // 电脑当前时间
            Date date = new Date();
            System.out.println("date => " + date);
            // date => Fri Jul 08 19:55:05 CST 2022
    
            // 计算机时间原点
            Date date1 = new Date(0L);
            System.out.println("date1 => " + date1);
            // date1 => Thu Jan 01 08:00:00 CST 1970
    
            // 指定时间 (计算机原点时间 + 9 个小时)
            Date date2 = new Date(60L * 60 * 1000);
            System.out.println("date2 =>" + date2);
    
            // 系统当前时间(毫秒数)
            long date3 = System.currentTimeMillis();
            System.out.println(date3);
            // => 1657286218008
    
            // getTime
            Date date4 = new Date();
            long time1 = date4.getTime();
            System.out.println(time1);
            // => 1657286218008
    
            // setTime
            Date date5 = new Date();
            date5.setTime(0L);
            System.out.println(date5);
            // => Thu Jan 01 08:00:00 CST 1970
        }
    }
    
    • 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


    2. SimpleDateFormat

    方法名描述
    public SimpleDateFormat ()构造一个SimpleDateFormat,使用默认格式
    public SimpleDateFormat (String pattern)构造一个SimpleDateFormat,使用指定的格式
    public final String format(Date date)把时间按照固定格式进行展示

    示例:

    public class test01 {
        public static void main(String[] args) throws ParseException {
            // 格式化:将日期格式化成日期/时间字符串
            Date date = new Date();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy年-MM月-dd日 HH:mm:ss");
            String time1 = sdf.format(date);
            System.out.println(time1);
            // => 2022年-07月-08日 21:30:47
    
            // 解析:从给定字符串的开始解析文本以生成日期
            String time2 = "2023-02-27";
            SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd");
            Date date2 = sdf2.parse(time2);
            System.out.println(date2);
            // => Mon Feb 27 00:00:00 CST 2023
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17


    3. 实例

    需求: 活动时间是 2022年11月11日 0:0:0 ~ 2022年11月11日 10:00:00,zoe当天8:03下单,tony当天10:21下单,请问谁成功了,用毫秒值进行分析

    public class test {
        public static void main(String[] args) throws ParseException {
            String zoe = "2022年11月11日 8:03:00";
            String tony = "2022年11月11日 10:21:00";
    
            secKill(zoe);
            secKill(tony);
        }
    
        public static void secKill(String time) throws ParseException {
            String start = "2022年11月11日 0:0:0";
            String end = "2022年11月11日 10:00:00";
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
            long startTime = sdf.parse(start).getTime();
            long endTime = sdf.parse(end).getTime();
            // System.out.println(startTime);
            // => 1668096000000
            // System.out.println(endTime);
            // => 1668132000000
    
            long timeDate = sdf.parse(time).getTime();
            if(timeDate > endTime || timeDate < startTime) {
                System.out.println("不好意思,未能秒杀成功");
            } else {
                System.out.println("恭喜你,秒杀成功了");
            }
        }
    
    • 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




    二、集合 List

    1. 概述

    • 数组的长度是不可变的,集合的长度是可变
    • 数组可以存基本数据类型和引用数据类型
    • 集合只能存引用数据类型,如果要存基本数据类型,需要存对应的包装类

    示例:

    public class test1 {
        public static void main(String[] args) {
            // 数组可以储存基础数据类型,也可以储存引用数据类型
            int[] arr1 = {1, 2, 3};
            String[] arr2 = {"a", "b", "c"};
            System.out.println(Arrays.toString(arr1));
            // => [1, 2, 3]
            System.out.println(Arrays.toString(arr2));
            // => [a, b, c]
    
            // 如果集合要储存基础数据类型,那么储存的是他们的包装类
            ArrayList<Integer> list1 = new ArrayList<>();
            list1.add(1);
            list1.add(2);
            list1.add(3);
            System.out.println(list1);
            // => [1, 2, 3]
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    继承体系:

    在这里插入图片描述



    2. Collection集合

    ⑴. 概述

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

    ⑵. 常用方法

    方法名描述
    boolean add(E e)添加元素
    boolean remove(Object o)从集合中移除指定的元素
    boolean removeif(Object o)根据条件进行删除
    void clear()清空集合
    boolean contains(Object o)判断集合中是否存在指定的元素
    boolean isEmpty()判断集合是否为空

    示例:

    public class demo1 {
        public static void main(String[] args) {
            Collection<String> collection = new ArrayList<>();
            collection.add("aa");
            collection.add("bbb");
            collection.add("cccc");
            System.out.println(collection);
            // => [aa, bbb, cccc]
    
    
            // boolean remove(Object o)	    从集合中移除指定的元素
            boolean res1 = collection.remove("aa");
            System.out.println(collection);
            // => [bbb, cccc]
    
            // boolean removeif(Object o)	根据条件进行删除
            collection.add("ddddd");
            collection.add("eeeeee");
            collection.removeIf((String str) -> {
                return str.length() == 5;
            });
            System.out.println(collection);
            // => [bbb, cccc, eeeeee]
    
            // void clear()			清空集合
            collection.clear();
            System.out.println(collection);
            // => []
    
            // boolean contains(Object o)	判断集合中是否存在指定的元素
            collection.add("a");
            collection.add("bb");
            collection.add("ccc");
            collection.add("dddd");
            boolean result2 = collection.contains("ccc");
            System.out.println(result2);
            // => true
    
            // boolean isEmpty()		判断集合是否为空
            boolean result3 = collection.isEmpty();
            System.out.println(result3);
            // => false
    
            // int size()			集合的长度,也就是集合中元素的个数
            int result4 = collection.size();
            System.out.println(result4);
            // => 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
    • 45
    • 46
    • 47
    • 48
    • 49

    ⑶. 集合遍历

    • Iterator:迭代器,集合的专用遍历方式
    • Iterator iterator():返回集合中的迭代器对象,该迭代器对象默认指向当前集合的0索引
    • Iterator中的常用方法:
      • boolean hasNext():判断当前位置是否有元素可以被取出
      • E next():获取当前位置的元素,将迭代器对象移向下一个索引位置

    示例:

    public class demo3 {
        public static void main(String[] args) {
            Collection<String> collection = new ArrayList<>();
            collection.add("a");
            collection.add("b");
            collection.add("c");
            collection.add("d");
    
            // 迭代器
            // 迭代器对象一旦被创建出来,默认指向集合的0索引处
            Iterator it = collection.iterator();
            System.out.println(it.hasNext());
            // => true
    
            // System.out.println(it.next());
            // 取出当前位置的元素  + 将迭代器往后移动一个索引的位置
            // => a
            // System.out.println(it.next());
            // => b
            // System.out.println(it.next());
            // => c
            // System.out.println(it.next());
            // => d
            // System.out.println(it.next());
            // => NoSuchElementException
    
    
            Collection<String> list = new ArrayList<>();
            list.add("aa");
            list.add("bb");
            list.add("cc");
            Iterator it2 = list.iterator();
            while (it2.hasNext()) {
                System.out.print(it2.next() + ",");
            }
            // => aa,bb,cc,
        }
    }
    
    • 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

    ⑷. 增强 for

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

    格式定义:

    // for(元素数据类型 变量名 : 数组或者Collection集合) {
         // 在此处使用变量即可,该变量就是元素
    // }
    
    ArrayList<String> list = new ArrayList<>();
    // 添加一些元素
    for(String s : list) {
         System.out.println(i);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    示例:

    public class demo4 {
        public static void main(String[] args) {
            ArrayList<String> list = new ArrayList<>();
            list.add("aa");
            list.add("bb");
            list.add("cc");
            list.add("dd");
    
             for (String s: list) {
                 System.out.println(s);
             }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    ⑸. 三种循环的使用场景

    • 如果需要操作索引,使用普通for循环
    • 如果在遍历的过程中需要删除元素,请使用迭代器
    • 如果仅仅想遍历,那么使用增强for

    示例:

    public class demo1 {
        public static void main(String[] args) {
            LinkedList<String> list = new LinkedList<>();
            list.add("a");
            list.add("bb");
            list.add("ccc");
    
            // for 循环
            for (int i = 0; i < list.size(); i++) {
                System.out.println(list.get(i));
            }
    
            // 加强for
            for (String s : list) {
                System.out.println(s);
            }
    
            // 迭代器
            Iterator<String> it = list.iterator();
            while (it.hasNext()) {
                System.out.println(it.next());
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    ⑹. 实例

    需求: 创建一个Collection集合存储学生对象的集合,存储3个学生对象,使用程序实现在控制台遍历该集合

    public class demo1 {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
    
            // void add(int index,E element)	在此集合中的指定位置插入指定的元素
            list.add("bb");
            list.add("ccc");
            list.add(0, "a");
            System.out.println(list);
            // => [a, bb, ccc]
    
            // E remove(int index)		删除指定索引处的元素,返回被删除的元素
            String s = list.remove(1);
            System.out.println(s);
            // => bb
            System.out.println(list);
            // => [a, ccc]
    
            // E set(int index,E element)	修改指定索引处的元素,返回被修改的元素
            String s2 = list.set(1, "dddd");
            System.out.println(s2);
            // => ccc
            System.out.println(list);
            // => [a, dddd]
    
            // E get(int index)		返回指定索引处的元素
            String s3 = list.get(0);
            System.out.println(s3);
            // => a
        }
    }
    
    • 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


    3. List集合

    ⑴. 概述

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

    ⑵. 特有方法

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

    示例:

    public class demo1 {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
    
            // void add(int index,E element)	在此集合中的指定位置插入指定的元素
            list.add("bb");
            list.add("ccc");
            list.add(0, "a");
            System.out.println(list);
            // => [a, bb, ccc]
    
            // E remove(int index)		删除指定索引处的元素,返回被删除的元素
            String s = list.remove(1);
            System.out.println(s);
            // => bb
            System.out.println(list);
            // => [a, ccc]
    
            // E set(int index,E element)	修改指定索引处的元素,返回被修改的元素
            String s2 = list.set(1, "dddd");
            System.out.println(s2);
            // => ccc
            System.out.println(list);
            // => [a, dddd]
    
            // E get(int index)		返回指定索引处的元素
            String s3 = list.get(0);
            System.out.println(s3);
            // => a
        }
    }
    
    • 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


    4. ArrayList集合

    数据结构是计算机存储、组织数据的方式,是指相互之间存在一种或多种特定关系的数据元素的集合
    一个程序 = 算法 + 数据结构,数据结构是实现算法的基础,选择合适的数据结构可以带来更高的运行或者存储效率

    数据元素相互之间的关系称为结构,根据数据元素之间关系的不同特性,通常有如下四类基本的结构

    • 集合结构: 该结构的数据元素间的关系是“属于同一个集合”
    • 线性结构: 该结构的数据元素之间存在着一对一的关系
    • 树型结构: 该结构的数据元素之间存在着一对多的关系
    • 图形结构: 该结构的数据元素之间存在着多对多的关系,也称网状结构

    由于数据结构种类太多,逻辑结构可以再分成为:

    • 线性结构: 有序数据元素的集合,其中数据元素之间的关系是一对一的关系,除了第一个和最后一个数据元素之外,其它数据元素都是首尾相接的
    • 非线性结构: 各个数据元素不再保持在一个线性序列中,每个数据元素可能与零个或者多个其他数据元素发生关联

    在这里插入图片描述


    1. 数组

    在程序设计中,为了处理方便, 一般情况把具有相同类型的若干变量按有序的形式组织起来,这些按序排列的同类数据元素的集合称为数组

    2. 栈

    一种特殊的线性表,只能在某一端插入和删除的特殊线性表,按照先进后出的特性存储数据
    先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据

    3. 队列

    跟栈基本一致,也是一种特殊的线性表,其特性是先进先出,只允许在表的前端进行删除操作,而在表的后端进行插入操作

    4. 链表

    是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的
    链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成
    一般情况,每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针

    5. 树

    树是典型的非线性结构,在树的结构中,有且仅有一个根结点,该结点没有前驱结点。在树结构中的其他结点都有且仅有一个前驱结点,而且可以有两个以上的后继结点

    6. 图

    一种非线性结构。在图结结构中,数据结点一般称为顶点,而边是顶点的有序偶对。如果两个顶点之间存在一条边,那么就表示这两个顶点具有相邻关系

    7. 堆

    堆是一种特殊的树形数据结构,每个结点都有一个值,特点是根结点的值最小(或最大),且根结点的两个子树也是一个堆

    8. 散列表

    若结构中存在关键字和K相等的记录,则必定在f(K)的存储位置上,不需比较便可直接取得所查记录



    5. LinkedList集合

    ⑴. 概述

    • List集合常用子类:
      • ArrayList: 底层数据结构是数组,查询快,增删慢
      • LinkedList: 底层数据结构是链表,查询慢,增删快

    ⑵. 特有方法

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

    示例:

    public class demo3 {
        public static void main(String[] args) {
            LinkedList<String> list = new LinkedList<>();
            list.add("a");
            list.add("bb");
            list.add("ccc");
    
            // public void addFirst(E e)	在该列表开头插入指定的元素
            list.addFirst("first");
            System.out.println(list);
            // => [first, a, bb, ccc]
    
            // public void addLast(E e)	将指定的元素追加到此列表的末尾
            list.addLast("last");
            System.out.println(list);
            // => [first, a, bb, ccc, last]
    
            // public E getFirst()		返回此列表中的第一个元素
            String s1 = list.getFirst();
            System.out.println(s1);
            // => first
    
            // public E getLast()		返回此列表中的最后一个元素
            String s2 = list.getLast();
            System.out.println(s2);
            // => last
    
            // public E removeFirst()		从此列表中删除并返回第一个元素
            String s3 = list.removeFirst();
            System.out.println(s3);
            // => first
            System.out.println(list);
            // => [a, bb, ccc, last]
    
            // public E removeLast()		从此列表中删除并返回最后一个元素
            String s4 = list.removeLast();
            System.out.println(s4);
            // => last
            System.out.println(list);
            // =>[a, bb, ccc]
        }
    }
    
    • 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


    6. 泛型

    是JDK5中引入的特性,它提供了编译时类型安全检测机制

    • 泛型的好处:
      • 把运行时期的问题提前到了编译期间
      • 避免了强制类型转换
    • 特点:
      • 如果一个类的后面有,表示这个类是一个泛型类
      • 创建泛型类的对象时,必须要给这个泛型确定具体的数据类型

    泛型类:

    # 修饰符 class 类名<类型> { }
    # 此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
     public class Generic<T> {  }
    
    • 1
    • 2
    • 3

    泛型方法:

    # 修饰符 <类型> 返回值类型 方法名(类型 变量名) {  }
    public <T> void show(T t) {  }
    
    • 1
    • 2

    泛型接口:

    # 修饰符 interface 接口名<类型> {  } 
    public interface Generic<T> {  }
    
    • 1
    • 2

    类型通配符:

    • 类型通配符:
    • ArrayList:表示元素类型未知的ArrayList,它的元素可以匹配任何的类型
    • 但是并不能把元素添加到ArrayListList中了,获取出来的也是父类类型
    • 类型通配符上限:
    • 比如: ArrayListList :它表示的类型是Number或者其子类型类型通配符下限:
    • 比如: ArrayListList :它表示的类型是Number或者其父类型




    三、集合 Set

    集合体系结构:
    在这里插入图片描述

    1. Set

    Set集合特点:

    • 可以去除重复
    • 存取顺序不一致
    • 没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取,删除Set集合里面的元素Set集合练习存储字符串并遍历

    需求: 储存字符串并遍历

    public class demo1 {
        public static void main(String[] args) {
            Set<String> set = new TreeSet<>();
            set.add("ccc");
            set.add("a");
            set.add("a");
            set.add("bb");
    
            // set 集合没有索引,不能通过普通 for 循环遍历
    
            // 迭代器
            Iterator<String> it = set.iterator();
            while (it.hasNext()) {
                System.out.print(it.next() + ",");
            }
            // => a,bb,ccc,
    
            // 增强 for
            for (String s : set) {
                System.out.print(s + ",");
            }
            // => a,bb,ccc,
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24


    2. TreeSet

    ⑴. 概述

    TreeSet集合特点:

    • 不包含重复元素的集合
    • 没有带索引的方法
    • 可以将元素按照规则进行排序

    ⑵. Comparable 自然排序

    • 使用空参构造创建TreeSet集合
    • 自定义的Student类实现Comparable接口
    • 重写里面的compareTo​方法

    示例:

    public class demo3 {
        public static void main(String[] args) {
            TreeSet<Student> students = new TreeSet<>();
            Student s1 = new Student("zoe", 18);
            Student s2 = new Student("tony", 65);
            Student s3 = new Student("ami", 11);
            Student s4 = new Student("skate", 11);
            students.add(s1);
            students.add(s2);
            students.add(s3);
            students.add(s4);
            System.out.println(students);
            // => [Student{name='ami', age=11}, Student{name='skate', age=11}, Student{name='zoe', age=18}, Student{name='tony', age=65}]
        }
    }
    
    class Student implements Comparable<Student> {
        private String name;
        private int age;
    
        public Student(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        public Student() {
        }
    
        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
    • 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

    ⑶. Comparator 比较器排序

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

    示例:

    public class demo4 {
        public static void main(String[] args) {
    //        TreeSet teachers = new TreeSet<>(new Comparator() {
    //            @Override
    //            // o1:要存入的元素、o2:集合中的元素
    //            public int compare(Teacher o1, Teacher o2) {
    //                int result = o1.getAge() - o2.getAge();
    //                result = (result == 0 ? o1.getName().compareTo(o2.getName()) : result);
    //                return result;
    //            }
    //        });
    
            // 简化
            TreeSet<Teacher> teachers = new TreeSet<>(
                    (Teacher o1, Teacher o2) -> {
                        int result = o1.getAge() - o2.getAge();
                        result = (result == 0 ? o1.getName().compareTo(o2.getName()) : result);
                        return result;
                    }
            );
            
            Teacher t1 = new Teacher("zoe", 18);
            Teacher t2 = new Teacher("tony", 43);
            Teacher t3 = new Teacher("ami", 12);
            Teacher t4 = new Teacher("skate", 12);
            teachers.add(t1);
            teachers.add(t2);
            teachers.add(t3);
            teachers.add(t4);
            System.out.println(teachers);
            // => [Teacher{name='ami', age=12}, Teacher{name='skate', age=12}, Teacher{name='zoe', age=18}, Teacher{name='tony', age=43}]
        }
    }
    
    class Teacher {
        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 Teacher(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        public Teacher() {
        }
    
        @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
    • 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

    ⑷. 两者比较

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


    3. HashSet

    ⑴. 概述

    • HashSet集合特点:
    • 底层数据结构是哈希表
    • 对集合的迭代顺序不作任何保证,也就是说不保证存储和取出的元素顺序一致
    • 没有带索引的方法,所以不能使用普通for循环遍历
    • 由于是Set集合,所以元素唯一

    示例:

    public class demo1 {
        public static void main(String[] args) {
            HashSet<String> hs = new HashSet<>();
            hs.add("a");
            hs.add("ccc");
            hs.add("bb");
            hs.add("bb");
    
            System.out.println(hs);
            // => [bb, a, ccc]
    
            // 迭代器
            Iterator<String> it = hs.iterator();
            while (it.hasNext()) {
                System.out.println(it.next());
            }
            // => a bb ccc
    
            // 增强 for
            for (String h : hs) {
                System.out.println(h);
            }
            // => a bb ccc
        }
    }
    
    • 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

    ⑵. 哈希值

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

    # 获取对象的哈希值
    public int hashCode​():返回对象的哈希码值
    
    • 1
    • 2

    对象的哈希值特点:

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

    HashSet1.8版本原理解析:

    • 底层结构: 哈希表(数组、链表、红黑树的结合体)
    • 当挂在下面的元素过多,那么不利于查询,所以在JDK8以后,当链表长度超过8的时候,自动转换为红黑树,存储流程不变

    ⑶. 实例

    需求: 创建储存对象的数据集合,如果属性相同即判定为一个对象,不再存入

    public class demo3 {
        public static void main(String[] args) {
            HashSet<Student2> hs = new HashSet<>();
            Student2 s1 = new Student2("zoe", 18);
            Student2 s2 = new Student2("tony", 56);
            Student2 s3 = new Student2("tony", 56);
            hs.add(s1);
            hs.add(s2);
            hs.add(s3);
    
            // 对象地址计算出来的 哈希值
            System.out.println(s1.hashCode());
            // => 3744322
            System.out.println(s1.hashCode());
            // => 3744322
            System.out.println(s2.hashCode());
            // => 110544754
    
            System.out.println(hs);
            // => [Student{name='tony', age=56}, Student{name='zoe', age=18}]
        }
    }
    
    class Student2 {
        private String name;
        private int age;
    
        public Student2(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        public Student2() {
        }
    
        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;
    
            Student2 student2 = (Student2) o;
    
            if (age != student2.age) return false;
            // return name != null ? name.equals(student2.name) : student2.name == null;
            return Objects.equals(name, student2.name);
        }
    
        @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
    • 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




    四、集合 Map

    1. Map

    ⑴. 概述

    • Interface Map K:键的数据类型;V:值的数据类型
    • 键不能重复,值可以重复
    • 键和值是一一对应的,每一个键只能找到自己对应的值
    • (键 + 值) 这个整体 我们称之为“键值对”或者“键值对对象”,在Java中叫做“Entry对象”。

    示例:

    public class demo1 {
        public static void main(String[] args) {
            Map<String, Integer> map = new HashMap<>();
            map.put("zoe", 18);
            map.put("tony", 53);
            map.put("ami", 62);
            System.out.println(map);
            // => {tony=53, zoe=18, ami=62}
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    ⑵. 常用方法

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

    示例:

    public class demo2 {
        public static void main(String[] args) {
            Map<String, Integer> map = new HashMap<>();
    
            // V put(K key,V value)   添加元素
            map.put("zoe", 18);
            map.put("tony", 56);
            map.put("ami", 47);
            map.put("jack", 87);
            System.out.println(map);
            // => {tony=56, zoe=18, ami=47, jack=87}
    
            // 如果要添加的键是存在的,那么会覆盖原先的值,把原先值当做返回值进行返回。
            int age1 = map.put("zoe", 34);
            System.out.println(age1);
            // => 18
            System.out.println(map);
            // => {tony=56, zoe=34, ami=47, jack=87}
    
            // V remove(Object key)   根据键删除键值对元素
            int age2 = map.remove("zoe");
            System.out.println(age2);
            // => 34
            System.out.println(map);
            // => {tony=56, ami=47, jack=87}
    
            // void clear()   移除所有的键值对元素
            // map.clear();
            // System.out.println(map);
            // => {}
    
            // boolean containsKey(Object key)   判断集合是否包含指定的键
            boolean result1 = map.containsKey("zoe");
            System.out.println(result1);
            // => false
    
            //  boolean isEmpty()   判断集合是否为空
            boolean result2 = map.isEmpty();
            System.out.println(result2);
            // => false
    
            // int size()   集合的长度,也就是集合中键值对的个数
            int size = map.size();
            System.out.println(size);
            // => 3
        }
    }
    
    • 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

    ⑶. 遍历 map 集合

    方法名描述
    V get(Object key)根据键获取值
    Set keySet()获取所有键的集合
    Set> entrySet()获取所有键值对对象的集合
    K getKey()获得键
    V getValue()获得值

    示例:

    public class demo3 {
        public static void main(String[] args) {
            // 遍历 map 的键值对
            Map<String, String> map = new HashMap<>();
            map.put("1号键", "1号值");
            map.put("2号键", "2号值");
            map.put("3号键", "3号值");
            map.put("4号键", "4号值");
            map.put("5号键", "5号值");
            System.out.println(map);
            // => {4号键=4号值, 3号键=3号值, 2号键=2号值, 1号键=1号值, 5号键=5号值}
    
            // 获取所有的键
            Set<String> keys = map.keySet();
            for (String key : keys) {
                // 通过键 获取到对应的值
                String value = map.get(key);
                System.out.println(key + "---" + value);
                // => 4号键---4号值
                // => 3号键---3号值
                // => 2号键---2号值
                // => 1号键---1号值
                // => 5号键---5号值
            }
    
            // 第二种遍历方式
            // 获取所有的键值对对象
            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);
                // => 4号键---4号值
                // => 3号键---3号值
                // => 2号键---2号值
                // => 1号键---1号值
                // => 5号键---5号值
            }
        }
    }
    
    • 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


    2. HashMap

    ⑴. 概述

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

    特点:

    • HashMap是Map里面的一个实现类
    • 没有额外需要学习的特有方法,直接使用Map里面的方法就可以了
    • HashMap跟HashSet一样底层是哈希表结构的

    ⑵. 实例

    需求: 创建一个HashMap集合,键是学生对象(Student),值是籍贯(String)。存储三个键值对元素,并遍历

    public class demo4 {
        public static void main(String[] args) {
            HashMap<Student, String> hm = new HashMap<>();
            Student s1 = new Student("zoe", 18);
            Student s2 = new Student("tony", 53);
            Student s3 = new Student("ami", 37);
            hm.put(s1, "北京");
            hm.put(s2, "上海");
            hm.put(s3, "武汉");
            System.out.println(hm);
            // => {Student{name='zoe', age=18}=北京, Student{name='tony', age=53}=上海, Student{name='ami', age=37}=武汉}
    
            // 遍历 1:获取所有的键,再通过键找对应的值
            Set<Student> keys = hm.keySet();
            for (Student key : keys) {
                String value = hm.get(key);
                System.out.println(key + "---" + value);
                // => Student{name='zoe', age=18}---北京
                // => Student{name='tony', age=53}---上海
                // => Student{name='ami', age=37}---武汉
            }
    
            // 遍历 2:先获取键值对对象,再获取里面的键和值
            Set<Map.Entry<Student, String>> entries = hm.entrySet();
            for (Map.Entry<Student, String> entry : entries) {
                Student key = entry.getKey();
                String value = entry.getValue();
                System.out.println(key + "---" + value);
                // => Student{name='zoe', age=18}---北京
                // => Student{name='tony', age=53}---上海
                // => Student{name='ami', age=37}---武汉
            }
    
            // 遍历 3:forEach 方法
            hm.forEach(
                    (Student key, String value) -> {
                        System.out.println(key + "---" + value);
                    }
            );
            // => Student{name='zoe', age=18}---北京
            // => Student{name='tony', age=53}---上海
            // => Student{name='ami', age=37}---武汉
        }
    }
    
    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;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    
        public void setAge(int age) {
            this.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
    • 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


    3. TreeMap

    ⑴. 概述

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

    特点:

    • TreeMap是Map里面的一个实现类。
    • 没有额外需要学习的特有方法,直接使用Map里面的方法就可以了
    • TreeMap跟TreeSet一样底层是红黑树结构的

    ⑵. 实例

    需求: 创建一个TreeMap集合,键是学生对象(Student),值是籍贯(String);学生属性姓名和年龄,按照年龄进行排序并遍历。

    public class demo5 {
        public static void main(String[] args) {
            TreeMap<Student2, String> tm = new TreeMap<>();
            Student2 s1 = new Student2("zoe", 18);
            Student2 s2 = new Student2("tony", 62);
            Student2 s3 = new Student2("ami", 39);
            tm.put(s1, "北京");
            tm.put(s2, "上海");
            tm.put(s3, "武汉");
            System.out.println(tm);
            // => {Student2{name='zoe', age=18}=北京, Student2{name='ami', age=39}=武汉, Student2{name='tony', age=62}=上海}
        }
    }
    
    class Student2 implements Comparable<Student2> {
        private String name;
        private int age;
    
        public Student2() {
        }
    
        @Override
        public String toString() {
            return "Student2{" +
                    "name='" + name + '\'' +
                    ", 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;
        }
    
        public Student2(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        @Override
        // 自定义排序
        public int compareTo(Student2 o) {
            int result = this.getAge() - o.getAge();
            result = (result == 0 ? this.getName().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
    • 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


    4. 可变参数

    可变参数: 就是形参的个数是可以变化的

    # 修饰符 返回值类型 方法名(数据类型… 变量名) {  }
    public static int sum(int… a) {  }
    
    • 1
    • 2

    特点:

    • 可变参数注意事项
    • 这里的变量其实是一个数组
    • 如果一个方法有多个参数,包含可变参数,可变参数要放在最后

    示例:

    需求: 实现一个两数相加的方法、实现一个三个数相加的方法, 定义一个方法,求 n 个数相加

    public class demo6 {
        public static void main(String[] args) {
            int num1 = 10;
            int num2 = 20;
            int num3 = 30;
    
            int result1 = getSum(num1, num2);
            System.out.println(result1);
            // => 30
            int result2 = getSum(num1, num2, num3);
            System.out.println(result2);
            // => 60
    
            int[] arr1 = {1, 2, 3, 4, 5};
            int result3 = getSum(arr1);
            System.out.println(result3);
            // => 15
    
            int result4 = getSum2(1, 2, 4, 5, 6);
            System.out.println(result4);
            // => 18
        }
    
        public static int getSum(int a, int b) {
            return a + b;
        }
    
        public static int getSum(int a, int b, int c) {
            return a + b + c;
        }
    
        public static int getSum(int[] arr) {
            int sum = 0;
            for (int i = 0; i < arr.length; i++) {
                sum += arr[i];
            }
            return sum;
        }
    
        public static int getSum2(int... arr) {
            int sum = 0;
            for (int i = 0; i < arr.length; i++) {
                sum += arr[i];
            }
            return sum;
        }
    }
    
    • 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


    5. 创建不可变的集合

    方法名描述
    static List of(E…elements)创建一个具有指定元素的List集合对象
    static Set of(E…elements)创建一个具有指定元素的Set集合对象
    static Map of(E…elements)创建一个具有指定元素的Map集合对象

    特点:

    • 在List、Set、Map接口中,都存在of方法,可以创建一个不可变的集合
    • 这个集合不能添加,不能删除,不能修改
    • 但是可以结合集合的带参构造,实现集合的批量添加

    示例:

    public class demo7 {
        public static void main(String[] args) {
            // static   List  of(E…elements)  创建一个具有指定元素的List集合对象
            List list = List.of("a", "b", "c");
            // 报错:java.lang.UnsupportedOperationException 不支持功能异常
            // list.add("d");
            System.out.println(list);
            // => [a, b, c]
    
            // 集合的批量添加
            ArrayList<String> arrayList = new ArrayList<>(List.of("a", "bb", "ccc", "dddd"));
            System.out.println(arrayList);
            // => [a, bb, ccc, dddd]
    
            //static   Set  of(E…elements)    创建一个具有指定元素的Set集合对象
            // 报错:java.lang.IllegalArgumentException 非法论据异常
            // Set set = Set.of("a", "b", "c", "a");
            Set<String> set = Set.of("a", "b", "c");
            System.out.println(set);
            // => [a, b, c]
    
            //static    Map  of(E…elements)
            Map<String, Integer> map = Map.of("zoe", 18, "tony", 65, "ami", 84);
            System.out.println(map);
            // => {ami=84, zoe=18, tony=65}
    
            // ofEntries
            Map<String, Integer> map2 = Map.ofEntries(Map.entry("zoe", 18), Map.entry("tony", 45), Map.entry("ami", 65));
            System.out.println(map2);
            // => {tony=45, ami=65, zoe=18}
        }
    }
    
    • 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




    五、Steam流

    1. 对比

    需求: 将数组中 z 开头、长度为 3 的名字,循环打印出来

    public class demo1 {
        public static void main(String[] args) {
            ArrayList<String> list1 = new ArrayList<>(List.of("zoe", "tony", "ami", "alen", "zero", "zoo"));
            System.out.println(list1);
            // => [zoe, tony, ami, alen, zero]
    
            // 将 z 开头的名字加入 list2 中
            ArrayList<String> list2 = new ArrayList<>();
            for (String s : list1) {
                if(s.startsWith("z")) {
                    list2.add(s);
                }
            }
            System.out.println(list2);
            // => [zoe, zero]
    
            // 将 list2 中 字符为 3 的名称加入 list3 中, 再循环打印出来
            ArrayList<String> list3 = new ArrayList<>();
            for (String s : list2) {
                if(s.length() == 3) {
                    list3.add(s);
                }
            }
    
            // 循环打印
            for (String s : list3) {
                System.out.println(s);
            }
            // => zoe, ami
    
    
            // Stream 方法
            list1.stream()
                    .filter(s -> s.startsWith("z"))
                    .filter(s -> s.length() == 3)
                    .forEach(s -> System.out.println(s));
                    // => zoe, ami
        }
    }
    
    • 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


    2. 获取方法

    • 单列集合:
      • 可以使用Collection接口中的默认方法stream​()生成流
      • default Stream stream​()
    • 双列集合:
      • 间接的生成流
      • 可以先通过keySet或者entrySet获取一个Set集合,再获取Stream流
    • 数组:
      • Arrays中的静态方法stream 生成流
    • 同种数据类型的多个数据:
      • 使用Stream.of(T…values)生成流

    示例:

    public class demo2 {
        public static void main(String[] args) {
            // 单列集合
            ArrayList<String> list = new ArrayList<>(List.of("zoe", "tony", "ami"));
            // Stream stream = list.stream();
            // stream.forEach(s -> System.out.println(s));
            list.stream().forEach(s -> System.out.println(s));
            // => zoe,tony,ami
    
    
            // 双列集合
            // HashMap hm = new HashMap(List.of("zoe", 18, "tony", 45, "ami", 45));
            Map<String, Integer> hm = Map.of("zoe", 18, "tony", 45, "ami", 23);
    
            // keySet() 先获取所有的键,在将集合 Set 放到 Steam 流中
            hm.keySet().stream().forEach(s -> System.out.println(s));
            // => ami,zoe,tony
    
            // entrySet() 先获取所有的键值对 对象,再将集合 Set中的键值对 对象 放到 Steam 流中
            hm.entrySet().stream().forEach(s -> System.out.println(s));
            // => tony=45,zoe=18,ami=23
    
    
            // 数组
            int[] arr = {1, 2, 3, 4, 5};
            Arrays.stream(arr).forEach(s -> System.out.println(s));
            // => 1, 2, 3, 4, 5
    
    
            // 同种数据类型的多个数据
            Stream.of(1, 2, 3, 4, 5, 6).forEach(s -> System.out.println(s));
            // => 1, 2, 3, 4, 5, 6
        }
    }
    
    • 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


    3. 中间操作方法

    方法名描述
    Stream filter​(Predicate predicate)用于对流中的数据进行过滤
    Predicate接口中的方法
    boolean test​(T t)对给定的参数进行判断,返回一个布尔值
    Stream limit​(long maxSize)截取指定参数个数的数据
    Stream skip​(long n)跳过指定参数个数的数据
    static Stream concat​(Stream a, Stream b)合并a和b两个流为一个流
    Stream distinct​()去除流中重复的元素。依赖(hashCode和equals方法)
    void forEach​(Consumer action)对此流的每个元素执行操作
    Consumer接口中的方法
    void accept​(T t)对给定的参数执行此操作
    long count​()返回此流中的元素数
    public static Collector toList​()把元素收集到List集合中
    public static Collector toSet​()把元素收集到Set集合中
    public static Collector toMap​(Function keyMapper,Function valueMapper)把元素收集到Map集合中

    示例:

    public class demo3 {
        public static void main(String[] args) {
            ArrayList<String> list = new ArrayList<>(List.of("zoe", "tony", "alen", "zoo", "zoo"));
            System.out.println(list);
            // => [zoe, tony, ami, alen, zero, zoo, zoo]
    
            // long count():返回此流中的元素数
            long count = list.stream().count();
            System.out.println(count);
            // => 5
    
            // void forEach(Consumer action):对此流的每个元素执行操作
            list.stream().forEach(s -> System.out.println(s));
            // => zoe,tony, alen, zoo, zoo
    
            // Stream limit(long maxSize):截取指定参数个数的数据
            list.stream()
                    .limit(3)
                    .forEach(s -> System.out.println(s));
            // => zoe,tony, alen
    
            // Stream skip(long n):跳过指定参数个数的数据
            list.stream()
                    .skip(3)
                    .forEach(s -> System.out.println(s));
            // => zoo, zoo
    
            // filter 过滤
            list.stream()
                    .filter(s -> s.length() == 3)
                    .forEach(s -> System.out.println(s));
            // => zoe,zoo, zoo
    
            // Stream distinct():去除流中重复的元素。依赖(hashCode和equals方法)
            list.stream()
                    .distinct()
                    .forEach(s -> System.out.println(s));
            // => zoe,tony, alen, zoo
    
            // static  Stream concat(Stream a, Stream b):合并a和b两个流为一个流
            ArrayList<String> list2 = new ArrayList<>();
            list2.add("other");
            Stream.concat(list.stream(), list2.stream()).forEach(s -> System.out.println(s));
            // => zoe,tony, alen, zoo, zoo, other
        }
    }
    
    • 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


    4. 收集操作方法

    方法名描述
    public static Collector toList​()把元素收集到List集合中
    public static Collector toSet​()把元素收集到Set集合中
    public static Collector toMap​(Function keyMapper,Function valueMapper)把元素收集到Map集合中

    需求: 有一个数组 {1, 2, 3, 4, 5}, 去掉其中的奇数,遍历出来

    public class demo4 {
        public static void main(String[] args) {
            // 需求: 有一个数组 {1, 2, 3, 4, 5}, 去掉其中的奇数,遍历出来
            // 方法一:
            ArrayList<Integer> list = new ArrayList<>(List.of(1, 2, 3, 4, 5));
            list.stream()
                    .filter(s -> s % 2 == 0)
                    .forEach(s -> System.out.println(s));
            // => 2,4
    
            // collect 负责收集数据,获取流中剩余的数据;但是不负责创建容器,也不负责数据添加到容器
            List<Integer> list2 = list.stream()
                    .filter(s -> s % 2 == 0)
                    .collect(Collectors.toList());
            System.out.println(list2);
            // => [2, 4]
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18




    六、 File类

    1. 概述

    File: 它是文件和目录路径名的抽象表示

    • 文件和目录可以通过 File 封装成对象
    • File封装的对象仅仅是一个路径名;它可以是存在的,也可以是不存在的

    2. 构造方法

    方法名描述
    File (String pathname)通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例
    File (String parent, String child)从父路径名字符串和子路径名字符串创建新的 File实例
    File (File parent, String child)从父抽象路径名和子路径名字符串创建新的 File实例

    示例:

    public class demo1 {
        public static void main(String[] args) {
            // File(String pathname) 通过将给定的路径名字符串转换为抽象路径名来创建新的File实例
            String path = "src/file/file/txt1.txt";
            File file1 = new File(path); // 为了使用File类里面的方法
            System.out.println(file1);
            // => src/file/file/txt1.txt
    
            // File(String parent, String child) 从父路径名字符串和子路径名字符串创建新的File实例
            String path1 = "src/file/file";
            String path2 = "txt1.txt";
            File file2 = new File(path1, path2);
            System.out.println(file2);
            // => src/file/file/txt1.txt
    
            // File(File parent, String child) 从父抽象路径名和子路径名字符串创建新的File实例
            File file3 = new File("src/file/file");
            String path3 = "txt1.txt";
            File file4 = new File(file3, path3);
            System.out.println(file4);
            // => src/file/file/txt1.txt
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    3. 创建、删除功能

    方法名描述
    public boolean createNewFile()创建一个新的空的文件
    public boolean mkdir()创建一个单级文件夹
    public boolean mkdirs()创建一个多级文件夹
    public boolean delete​()删除由此抽象路径名表示的文件或目录

    示例:

    public class demo2 {
        public static void main(String[] args) throws IOException {
            // public boolean createNewFile()  创建一个新的空的文件
            File file1 = new File("src/file/file/a.txt");
            boolean res1 = file1.createNewFile();
            System.out.println(res1);
            // => true (如果当前目录存在该文件路径,则创建文件失败)
    
            // public boolean mkdir()  创建一个单级文件夹
            File file2 = new File("src/file/file/a");
            boolean res2 = file2.mkdir();
            System.out.println(res2);
            // => true
    
            // public boolean mkdirs()  创建一个多级文件夹
            File file3 = new File("src/file/file/bb/cc/dd");
            boolean res3 = file3.mkdirs();
            System.out.println(res3);
            // => true
    
            // delete 删除文件,只能删除文件、空文件夹
            File file4 = new File("src/file/file");
            boolean res4 = file4.delete();
            System.out.println(res4);
            // => false
        }
    }
    
    • 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

    4. 判断和获取功能

    方法名描述
    public boolean isDirectory()测试此抽象路径名表示的File是否为目录
    public boolean isFile()测试此抽象路径名表示的File是否为文件
    public boolean exists()测试此抽象路径名表示的File是否存在
    public String getAbsolutePath()返回此抽象路径名的绝对路径名字符串
    public String getPath()将此抽象路径名转换为路径名字符串
    public String getName()返回由此抽象路径名表示的文件或目录的名称

    示例:

    public class demo3 {
        public static void main(String[] args) {
            //public boolean isDirectory()  测试此抽象路径名表示的File是否为目录
            File file1 = new File("src/file/file");
            boolean directory = file1.isDirectory();
            System.out.println(directory);
            // => true
    
            //public boolean isFile()  测试此抽象路径名表示的File是否为文件
            File file2 = new File("src/file/file/a.txt");
            boolean file = file2.isFile();
            System.out.println(file);
            // => true
    
            //public boolean exists()  测试此抽象路径名表示的File是否存在
            File file3 = new File("src/file/file/b.txt");
            boolean exists = file3.exists();
            System.out.println(exists);
            // => false
    
            //public String getName()  返回由此抽象路径名表示的文件或目录的名称
            File file4 = new File("src/file/file/b.txt");
            String name = file4.getName();
            System.out.println(name);
            // => b.txt (当前文件不存在)
        }
    }
    
    • 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

    5. 高级获取功能

    方法名描述
    public File[] listFiles()返回此抽象路径名表示的目录中的文件和目录的File对象数组

    示例:

    public class demo4 {
        public static void main(String[] args) {
            File file = new File("src/file/file");
            File[] files = file.listFiles();
            for (File file1 : files) {
                System.out.println(file1);
            }
            // => src/file/file/bb
            // => src/file/file/a
            // => src/file/file/a.txt
            // => src/file/file/txt1.txt
    
            //进入文件夹,获取这个文件夹里面所有的文件和文件夹的File对象,并把这些File对象都放在一个数组中返回.
            //包括隐藏文件和隐藏文件夹都可以获取.
    
    
            // 需求:删除多级文件夹
            File src = new File("src/file/bb");
            deleteDir(src);
        }
    
        public static void deleteDir(File file) {
            // 获取文件夹的文件对象
            File[] files = file.listFiles();
            for (File file1 : files) {
                // 如果是文件直接删除
                if(file1.isFile()) {
                    file1.delete();
                } else {
                    // 递归,重复遍历子文件夹,删除子文件夹文件
                    deleteDir(file1);
                }
            }
            // 最后再删除这个文件夹
            file.delete();
        }
    }
    
    • 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




    七、 IO

    1. 概述

    • I 表示 intput,是数据从硬盘进内存的过程,称之为
    • O 表示 output,是数据从内存到硬盘的过程。称之为

    分类:

    • 流向分: 输入流、输出流
    • 按数据类型分(一般都是按数据类型分类):
    • 字节流:操作所有类型的文件
    • 字符流:只能操作纯文本文件


    2. 字节流

    ⑴. 写数据流程

    public class demo1 {
        public static void main(String[] args) throws IOException {
            // 创建字节输出流对象
            // FileOutputStream fos = new FileOutputStream(new File("src/io/file/a.txt"));
            FileOutputStream fos = new FileOutputStream("src/io/file/a.txt");
            // 写入数据
            fos.write(99);
            // 释放空间
            fos.close();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    ⑵. 写数据的3种方式

    方法名描述
    void write (int b)一次写一个字节数据
    void write (byte[] b)一次写一个字节数组数据
    void write (byte[] b, int off, int len)一次写一个字节数组的部分数据

    示例:

    public class demo2 {
        public static void main(String[] args) throws IOException {
            FileOutputStream fos = new FileOutputStream("src/io/file/b.txt");
            byte[] bytes = {97, 98, 99};
            // fos.write(bytes);
            // fos.close();
            // => 新增了 b.txt 文件,并添加了内容 abc
    
            // write(写入的数组,从第几个索引,写入个数)
            byte[] bytes2 = {97, 98, 99, 100, 101};
            fos.write(bytes2, 1, 2);
            fos.close();
            //  => bc (a 对应的是 97 --- 字节数)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    ⑶. 写数据拓展功能

    1. 实现字节换行
    2. 实现数据追加

    public class demo3 {
        public static void main(String[] args) throws IOException {
            FileOutputStream fos = new FileOutputStream("src/io/file/c.txt");
            fos.write(97);
            // 1. 如何实现字节换行
            // windows: \r\n, mac: \r, linux: \n
            // getBytes(): 将字符串转换成 对应的 字节码
            fos.write("\r".getBytes());
            fos.write(98);
            fos.close();
    
    
            // 2. 如何实现数据追加
            // 第二个参数为 续写开关,默认为 false
            FileOutputStream fos2 = new FileOutputStream("src/io/file/d.txt", true);
            fos2.write(97);
            fos2.write(98);
            fos2.write(99);
            fos2.close();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    3. 数字转码(码表)

    public class demo4 {
        public static void main(String[] args) throws IOException {
            FileInputStream fis = new FileInputStream("src/io/file/c.txt");
            int read = fis.read();
            System.out.println(read);
            // => 97
    
            // 返回的是字符对应的数字,可以通过 char 强转成 码表对应的 字符
            System.out.println((char)read);
            fis.close();
            // => a
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    ⑷. 文件的读取和复制

    需求: 读取文件多个字节流

    public class demo5 {
        public static void main(String[] args) throws IOException {
            FileInputStream fis = new FileInputStream("src/io/file/study.txt");
            int b;
            while ((b = fis.read()) != -1) {
                System.out.println((char)b);
            }
            fis.close();
            // => good good study day day up
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    需求: 复制文件

    public class demo6 {
        public static void main(String[] args) throws IOException {
            FileInputStream fis = new FileInputStream("src/io/file/study.txt");
            FileOutputStream fos = new FileOutputStream("src/io/file/study_copy.txt");
            int b;
            while ((b = fis.read()) != -1 ) {
                fos.write(b);
            }
            fis.close();
            fos.close();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    需求: 复制多个字节

    public class demo7 {
        public static void main(String[] args) throws IOException {
            FileInputStream fis = new FileInputStream("src/io/file/study.txt");
            FileOutputStream fos = new FileOutputStream("src/io/file/study_copy2.txt");
            byte [] bytes = new byte[1024];
            int len;
            while ((len = fis.read(bytes)) != -1 ) {
                fos.write(bytes, 0, len);
            }
            fis.close();
            fos.close();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    ⑸. 字节缓冲流

    public class demo8 {
        public static void main(String[] args) throws IOException {
            // 字节缓冲流
            BufferedInputStream bis = new BufferedInputStream(new FileInputStream("src/io/file/study.txt"));
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("src/io/file/study_copy3.txt"));
            int len;
            while ((len = bis.read()) != -1) {
                bos.write(len);
            }
            bis.close();
            bos.close();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    需求: 缓冲流结合数据进行文件拷贝

    public class demo9 {
        public static void main(String[] args) throws IOException {
            BufferedInputStream bis = new BufferedInputStream(new FileInputStream("src/io/file/study.txt"));
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("src/io/file/study_copy4.txt"));
            int b;
            while ((b = bis.read()) != -1) {
                bos.write(b);
            }
            bis.close();
            bos.close();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12


    3. 字符流

    ⑴. 编码表

    • 计算机中储存的信息都是用二进制数表示的
    • 我们在屏幕上看到的英文、汉字等字符是二进制数转换之后的结果
    • 按照某种规则,将字符存储到计算机中,称为编码
    • 按照同样的规则,将存储在计算机中的二进制数解析显示出来,称为解码

    分类:

    • ASCII字符集: ASCII(American Standard Code for Information Interchange,美国信息交换标准代码):包括了数字,大小写字符和一些常见的标点符号(无中文)
    • GBK: window系统默认的码表。兼容ASCII码表,也包含了21003个汉字,并支持繁体汉字以及部分日韩文字(中国的码表,一个中文以两个字节的形式存储。但不包含世界上所有国家的文字)
    • Unicode码表: 由国际组织ISO 制定,是统一的万国码,计算机科学领域里的一项业界标准,容纳世界上大多数国家的所有常见文字和符号(: Unicode是万国码,以UTF-8编码后一个中文以三个字节的形式存储)

    汉字存储和展示过程解析:
    在这里插入图片描述


    ⑵. 编码&解码

    类型方法名描述
    编码byte[] getBytes​()使用平台的默认字符集将该 String编码为一系列字节,将结果存储到新的字节数组中
    编码byte[] getBytes​(String charsetName)使用指定的字符集将该 String编码为一系列字节,将结果存储到新的字节数组中
    解码String​(byte[] bytes)通过使用平台的默认字符集解码指定的字节数组来构造新的 String
    解码String​(byte[] bytes, String charsetName)通过指定的字符集解码指定的字节数组来构造新的 String

    ⑶. 字符流写数据方法

    方法名描述
    void write (int c)写一个字符
    void write (char[] cbuf)写入一个字符数组
    void write (char[] cbuf, int off, int len)写入字符数组的一部分
    void write (String str)写一个字符串
    void write (String str, int off, int len)写一个字符串的一部分

    示例:

    public class demo1 {
        public static void main(String[] args) throws IOException {
            // 创建字符输出流的对象
            FileWriter fw = new FileWriter("src/io/file/u.txt");
    
            // void write(int c)  写一个字符
            fw.write(97);
    
            // void write(char[] cbuf)  写出一个字符数组
            char [] chars = {97, 98, 99};
            fw.write(chars);
    
            // void write(char[] cbuf, int off, int len)  写出字符数组的一部分
            char [] chars2 = {97, 98, 99, 100};
            // 三个参数:数组,开始索引,截取个数
            fw.write(chars2, 1, 2);
    
            // void write(String str)  写一个字符串
            String poetry = "我本将心向明月";
            fw.write(poetry);
    
            // void write(String str, int off, int len)  写一个字符串的一部分
            String poetry2 = "奈何明月向沟渠";
            fw.write(poetry2, 1, 3);
    
            fw.close();
        }
    }
    
    • 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

    ⑷. 拓展方法

    方法名描述
    flush()刷新流,还可以继续写数据
    close()关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据

    示例:

    public class demo2 {
        public static void main(String[] args) throws IOException {
            FileWriter fw = new FileWriter("src/io/file/x.txt");
    
            //flush()刷新流。刷新完毕之后,还可以继续写数据
            fw.flush();
            fw.write(98);
    
            //close()关闭流。释放资源。一旦关闭,就不能写数据
            fw.close();
            // IOException 读写数据异常
            // fw.write(99);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    ⑸. 读数据方法

    方法名描述
    int read ()一次读一个字符数据
    int read (char[] cbuf)一次读一个字符数组数据

    示例:

    public class demo3 {
        public static void main(String[] args) throws IOException {
            FileReader fr = new FileReader("src/io/file/x.txt");
            int ch;
            while ((ch = fr.read()) != -1) {
                System.out.println((char) ch);
            }
            // => b
            fr.close();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    ⑹. 案例

    需求: 键盘输入用户名和密码,保存本地储存,用户名和密码独占一行

    public class demo4 {
        public static void main(String[] args) throws IOException {
            Scanner sc = new Scanner(System.in);
            System.out.println("请输入用户名:");
            String userName = sc.next();
            System.out.println("请输入密码:");
            String passWord = sc.next();
            FileWriter fw = new FileWriter("src/io/file/y.txt");
            fw.write(userName);
            fw.write("\n");
            fw.write(passWord);
            fw.flush();
            fw.close();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    ⑺. 字符缓冲流

    方法名构造方法描述
    BufferedWriterBufferedWriter​(Writer out)将文本写入字符输出流,缓冲字符,以提供单个字符,数组和字符串的高效写入,可以指定缓冲区大小,或者可以接受默认大小。默认值足够大,可用于大多数用途
    BufferedReaderBufferedReader​(Reader in)从字符输入流读取文本,缓冲字符,以提供字符,数组和行的高效读取,可以指定缓冲区大小,或者可以使用默认大小。 默认值足够大,可用于大多数用途

    - 字符缓冲输出流

    public class demo5 {
        public static void main(String[] args) throws IOException {
            BufferedReader br = new BufferedReader(new FileReader("src/io/file/y.txt"));
            char [] chars = new char[1024];
            int len;
            while ((len = br.read(chars)) != -1) {
                System.out.println(new String(chars, 0, len));
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    - 字符缓冲输入流

    public class demo6 {
        public static void main(String[] args) throws IOException {
            BufferedWriter fw = new BufferedWriter(new FileWriter("src/io/file/z.txt"));
    
            fw.write(97);
            char [] chars = {97, 98, 99};
            fw.write(chars);
            fw.write(chars, 1, 2);
            String poetry = "我本将心向明月";
            fw.write(poetry);
            fw.write(poetry, 2, 3);
    
            fw.flush();
            fw.close();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    ⑻. 字符缓冲流特有功能

    方法名构造方法描述
    BufferedWritervoid newLine​()写一行行分隔符,行分隔符字符串由系统属性定义
    BufferedReaderpublic String readLine​()读一行文字。 结果包含行的内容的字符串,不包括任何行终止字符,如果流的结尾已经到达,则为null

    示例:

    public class demo7 {
        public static void main(String[] args) throws IOException {
            BufferedWriter bw = new BufferedWriter(new FileWriter("src/io/file/v.txt"));
            bw.write("我本将心向明月");
            // 跨平台的换行符
            bw.newLine();
            bw.write("奈何明月照沟渠");
            bw.flush();
    
    
            BufferedReader br = new BufferedReader(new FileReader("src/io/file/v.txt"));
            // readline 读一整行数据
            String s1 = br.readLine();
            System.out.println(s1);
            // => 我本将心向明月
            String s2 = br.readLine();
            System.out.println(s2);
            // => 奈何明月照沟渠
            String s3 = br.readLine();
            System.out.println(s3);
            // => null
    
            // 改进
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
    
            bw.close();
        }
    }
    
    • 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 demo8 {
        public static void main(String[] args) throws IOException {
            // 1. 读
            BufferedReader br = new BufferedReader(new FileReader("src/io/file/sort.txt"));
            String line = br.readLine();
            System.out.println(line);
            // => 8 2 9 4 7 1 5 6 3
    
            // 1.2 类型转换
            String[] split = line.split(" ");
            int[] arr = new int[split.length];
            for (int i = 0; i < split.length; i++) {
                arr[i] = Integer.parseInt(split[i]);
            }
    
            // 2. 排序
            Arrays.sort(arr);
            System.out.println(Arrays.toString(arr));
            // => [1, 2, 3, 4, 5, 6, 7, 8, 9]
    
            // 3. 写入
            BufferedWriter bw = new BufferedWriter(new FileWriter("src/io/file/sort.txt"));
            for (int i = 0; i < arr.length; i++) {
                bw.write(arr[i] + " ");
            }
    
            bw.close();
        }
    }
    
    • 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




    八、IO 其他流

    1. 转换流

    转换流就是来进行字节流和字符流之间转换的

    • 输出流: InputStreamReader,从字节流到字符流的桥梁
    • 输入流: OutputStreamWriter,从字符流到字节流的桥梁

    示例:

    package io.io2;
    
    import java.io.*;
    import java.nio.charset.Charset;
    
    public class demo9 {
        public static void main(String[] args) throws IOException {
            // 乱码原因:文件是GBK码表,而idea默认的是UTF-8编码格式
            FileReader fr = new FileReader("src/io/file/gbk.txt");
            int len;
            while ((len = fr.read()) != -1) {
                System.out.println((char) len);
            }
    
            // 解决乱码问题
            InputStreamReader isr = new InputStreamReader(new FileInputStream("src/io/file/gbk.txt"), "GBK");
            int ch;
            while ((ch = isr.read()) != -1) {
                System.out.println((char) ch);
            }
    
            // JDK11 后推出的构造方法,用于指定码表
            InputStreamReader isr2 = new InputStreamReader(new FileInputStream("src/io/file/gbk.txt"), Charset.forName("GBK"));
            int ch2;
            while ((ch2 = isr2.read()) != -1) {
                System.out.println((char) ch2);
            }
        }
    }
    
    • 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


    2. 对象操作流

    可以把对象以字节的形式写到本地文件,直接打开文件,是读不懂的,需要再次用对象操作流读到内存中。

    • 对象操作输入流: ObjectInputStream,就是将对象写到本地文件中,或者在网络中传输对象
    • 对象操作输出流(对象反序列化流): ObjectOutputStream,把写到本地文件中的对象读到内存中,或者接收网络中传输的对象

    - 写对象 — 序列化

    public class demo10 {
        public static void main(String[] args) throws IOException {
            // 写对象 --- 序列化
            User user = new User("yf", 18);
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src/io/file/w.txt"));
            oos.writeObject(user);
            oos.close();
        }
    }
    
    // 类实现序列号,需要实现一个接口 Serializable
    class User implements Serializable {
        private String userName;
        private int age;
        public String getUserName() {
            return userName;
        }
        public void setUserName(String userName) {
            this.userName = userName;
        }
        public int getAge() {
            return age;
        }
        public void setAge(int age) {
            this.age = age;
        }
        public User(String userName, int age) {
            this.userName = userName;
            this.age = age;
        }
        public User() {
        }
        @Override
        public String toString() {
            return "User{" +
                    "userName='" + userName + '\'' +
                    ", 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
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    - 读对象 — 反序列化

    public class demo11 {
        public static void main(String[] args) throws IOException, ClassNotFoundException {
            // 读对象 --- 反序列化
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("src/io/file/w.txt"));
            Object o = ois.readObject();
            System.out.println(o);
            // => User{userName='yf', age=18}
            ois.close();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10


    3. Properties

    • 是一个Map体系的集合类
    • Properties中有跟IO相关的方法
    • 只存字符串

    ⑴. 集合常用方法

    public class demo12 {
        public static void main(String[] args) {
            Properties prop = new Properties();
    
            // 增
            prop.put("zoe", 18);
            prop.put("tony", 63);
            prop.put("ami", 34);
            System.out.println(prop);
            // => {tony=63, zoe=18, ami=34}
    
            // 删
            prop.remove("zoe");
            System.out.println(prop);
            // => {tony=63, ami=34}
    
            // 改
            prop.put("tony", 81);
            System.out.println(prop);
            // => {tony=81, ami=34}
    
            // 查
            Object ami = prop.get("ami");
            System.out.println(ami);
            // => 34
    
            // 遍历
            Set<Object> objects = prop.keySet();
            for (Object object : objects) {
                Object o = prop.get(object);
                System.out.println(object + "," + o);
            }
            // tony,81  ami,34
    
            // 遍历 键值对对象
            Set<Map.Entry<Object, Object>> entries = prop.entrySet();
            for (Map.Entry<Object, Object> entry : entries) {
                System.out.println(entry.getKey() + "," + entry.getValue());
            }
            // tony,81  ami,34
        }
    }
    
    • 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

    ⑵. Properties作为集合的特有方法

    方法名描述
    Object setProperty (String key, String value)设置集合的键和值,都是String类型,底层调用 Hashtable方法 put
    String getProperty (String key)使用此属性列表中指定的键搜索属性
    Set stringPropertyNames ()从该属性列表中返回一个不可修改的键集,其中键及其对应的值是字符串

    示例:

    public class demo13 {
        public static void main(String[] args) {
            Properties prop = new Properties();
            //Object setProperty(String key, String value) --- put
            //设置集合的键和值,都是String类型,底层调用 Hashtable方法 put
            prop.setProperty("America", "Now York");
            prop.setProperty("England", "London");
            prop.setProperty("Australia", "Sydney");
            System.out.println(prop);
            // => {America=Now York, England=London, Australia=Sydney}
    
            //String getProperty(String key)  --- get
            //使用此属性列表中指定的键搜索属性
            String england = prop.getProperty("England");
            System.out.println(england);
            // => London
    
            //Set stringPropertyNames()  --- keySet
            //从该属性列表中返回一个不可修改的键集,其中键及其对应的值是字符串
            Set<String> strings = prop.stringPropertyNames();
            System.out.println(strings);
            // => [America, England, Australia]
    
            // 遍历
            for (String string : strings) {
                String property = prop.getProperty(string);
                System.out.println(string + "," + property);
            }
            // => America,Now York  England,London  Australia,Sydney
        }
    }
    
    • 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

    ⑶. Properties和IO流结合的方法

    方法名描述
    void load (InputStream inStream)从输入字节流读取属性列表(键和元素对)
    void load (Reader reader)从输入字符流读取属性列表(键和元素对)
    void store (OutputStream out, String comments)将此属性列表(键和元素对)写入此 Properties表中,以适合于使用 load(InputStream)方法的格式写入输出字节流
    void store (Writer writer, String comments)将此属性列表(键和元素对)写入此 Properties表中,以适合使用 load(Reader)方法的格式写入输出字符流

    示例:

    public class demo14 {
        public static void main(String[] args) throws IOException {
            Properties prop = new Properties();
            FileReader fr = new FileReader("src/io/file/t.txt");
            //void load(Reader reader)   将本地文件中的键值对数据读取到集合中
            prop.load(fr);
            System.out.println(prop);
            // => {"America",="Now York","England", "London", "Australia", "Sydney"}
            fr.close();
    
    
            FileWriter fw = new FileWriter("src/io/file/r.txt");
            //void store(Writer writer, String comments)   将集合中的数据以键值对形式保存在本地
            // 第二个参数为注释,可传 null
            prop.store(fw, "README");
            fw.close();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18




    九、多线程

    1. 线程相关的概念

    • 并行: 在同一时刻,有多个指令在多个CPU上同时执行。
    • 并发: 在同一时刻,有多个指令在单个CPU上交替执行。

    进程: 是正在运行的软件

    • 独立性: 进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位
    • 动态性: 进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的
    • 并发性: 任何进程都可以同其他进程一起并发执行

    线程: 是进程中的单个顺序控制流,是一条执行路径。

    • 单线程: 一个进程如果只有一条执行路径,则称为单线程程序
    • 多线程: 一个进程如果有多条执行路径,则称为多线程程序


    2. 多线程的实现方式

    ⑴. Thread类

    public class demo1 {
        public static void main(String[] args) {
            MyThead t1 = new MyThead();
            MyThead t2 = new MyThead();
            // t1.start();
            // t2.start();
            // => 线程会 交替 执行
    
            t1.run();
            t2.run();
            // => 线程会 一个个 执行
        }
    }
    class MyThead extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 50; i++) {
                System.out.println("线程" + i);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    ⑵. Runnable接口

    public class demo2 {
        public static void main(String[] args) {
            MyRunnable mr1 = new MyRunnable();
            Thread t1 = new Thread(mr1);
            t1.start();
    
            MyRunnable mr2 = new MyRunnable();
            Thread t2 = new Thread(mr2);
            t2.start();
        }
    }
    
    class MyRunnable implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 50; i++) {
                System.out.println("多线程第二种方式" + i);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    ⑶. Callable和Future接口

    public class demo3 {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            MyCallable mc1 = new MyCallable();
    
            // Thread t1 = new Thread(mc1);
    
            // 可以作为参数传递给 Thread
            FutureTask<String> ft = new FutureTask<>(mc1);
    
            // 创建线程对象
            Thread t1 = new Thread(ft);
    
            // 开启线程
            t1.start();
    
            String s = ft.get();
            System.out.println(s);
            // => 表白
        }
    }
    
    class MyCallable implements Callable<String> {
        @Override
        public String call() throws Exception {
            for (int i = 0; i < 50; i++) {
                System.out.println("表白次数:" + i);
            }
            // 返回的值代表运行完成的结果
            return "答应";
        }
    }
    
    • 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

    ⑷. 三种方式对比

    方法优点缺点
    实现Runnable、Callable接口扩展性强,实现该接口的同时还可以继承其他的类编程相对复杂,不能直接使用Thread类中的方法
    继承Thread类编程比较简单,可以直接使用Thread类中的方法可以扩展性较差,不能再继承其他的类


    3. 线程类的常见方法

    方法描述
    String getName​()返回此线程的名称
    void setName​(String name)将此线程的名称更改为等于参数 name
    public static Thread currentThread()返回对当前正在执行的线程对象的引用
    public static void sleep(long time)让线程休眠指定的时间,单位为毫秒
    public final void setPriority(int newPriority)设置线程的优先级
    public final int getPriority()获取线程的优先级
    public final void setDaemon(boolean on)设置为守护线程
    实现Runnable、Callable接口扩展性强,实现该接口的同时还可以继承其他的类


    4. 示例

    ⑴. 加载器

    public class demo4 {
        public static void main(String[] args) throws IOException {
            // static ClassLoader getSystemClassLoader() 获取系统类加载器
            // InputStream getResourceAsStream(String name) 加载某一个资源文件
    
            // 获取系统类加载器
            ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
    
            // 利用加载器去加载一个指定文件
            // 参数:文件的路径(src 根路径);返回值:字节流
            InputStream is = systemClassLoader.getResourceAsStream("prop.properties");
            Properties prop = new Properties();
            prop.load(is);
    
            System.out.println(prop);
            // => {name=zoe, age=18}
            is.close();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    ⑵. 线程名称

    public class demo6 {
        public static void main(String[] args) {
            thread.MyRunnable2 mr1 = new thread.MyRunnable2();
            Thread t1 = new Thread(mr1);
            t1.start();
    
            thread.MyRunnable2 mr2 = new thread.MyRunnable2();
            Thread t2 = new Thread(mr2);
            t2.start();
        }
    }
    
    class MyRunnable2 implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 50; i++) {
                // 获取线程名称
                System.out.println(Thread.currentThread().getName() + "多线程第二种方式" + i);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    ⑶. 线程休眠

    public class demo7 {
        public static void main(String[] args) throws InterruptedException {
            System.out.println("睡觉了");
            Thread.sleep(1000);
            System.out.println("醒了");
            // => 睡觉了 (间隔1000ms) 醒了
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    ⑷. 优先级

    线程有两种调度模型:

    • 分时调度模型: 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
    • 抢占式调度模型: 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些
    • Java使用的是抢占式调度模型
    public class demo9 {
        public static void main(String[] args) {
    
            MyCallable3 mc1 = new MyCallable3();
            FutureTask<String> ft1 = new FutureTask<>(mc1);
            Thread t1 = new Thread(ft1);
            t1.setName("zoe");
            t1.start();
            System.out.println(t1.getPriority());
            // => 5 优先级是 5
            t1.setPriority(10);
    
            MyCallable3 mc2 = new MyCallable3();
            FutureTask<String> ft2 = new FutureTask<>(mc2);
            Thread t2 = new Thread(ft2);
            t2.setName("tony");
            t2.start();
            // 交替执行...
            System.out.println(t2.getPriority());
            // => 5 优先级是 5
            t2.setPriority(1);
            // t2 抢占 CPU 线程几率更高,是几率
    
            // 小结:线程优先级范围:1 - 10,默认为 5
        }
    }
    
    class MyCallable3 implements Callable<String> {
        @Override
        public String call(){
            for (int i = 0; i < 50; i++) {
                System.out.println(Thread.currentThread().getName() + "---" + i);
            }
            return null;
        }
    }
    
    • 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 demo10 {
        public static void main(String[] args) {
            MyThread1 th1 = new MyThread1();
            MyThread2 th2 = new MyThread2();
            th1.setName("女神");
            th2.setName("备胎");
    
            // 将 th2 设置为守护线程(当普通线程执行完毕,守护线程也就没有执行的必要了)
            th2.setDaemon(true);
    
            th1.start();
            th2.start();
            // 交替打印...
        }
    }
    
    class MyThread1 extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println(getName() + "---" + i);
            }
        }
    }
    
    class MyThread2 extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println(getName() + "---" + i);
            }
        }
    }
    
    • 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

    ⑹. 线程生命周期

    在这里插入图片描述



    5. 线程的安全问题

    ⑴. 同步代码块

    # 锁多条语句操作共享数据,可以使用同步代码块实现
    synchronized(任意对象) { 
           多条语句操作共享数据的代码 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 默认情况是打开的,只要有一个线程进去执行代码了,锁就会关闭
    • 当线程执行完出来了,锁才会自动打开

    同步的好处和弊端:

    • 好处: 解决了多线程的数据安全问题
    • 弊端: 当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率

    ⑵. 同步方法

    # 就是把synchronized关键字加到方法上
    修饰符 synchronized 返回值类型 方法名(方法参数) {    }
    
    • 1
    • 2

    同步代码块和同步方法的区别:

    • 同步代码块可以锁住指定代码,同步方法是锁住方法中所有代码
    • 同步代码块可以指定锁对象,同步方法不能指定锁对象

    ⑵. Lock锁

    为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

    方法描述
    void lock()获得锁
    void unlock()释放锁
    ReentrantLock​()创建一个ReentrantLock的实例

    ⑶. 示例

    需求: 100 张票,分三个窗口售卖,请用编程思维实现

    public class demo3 {
        public static void main(String[] args) {
            Ticket3 ticket = new Ticket3();
            Thread t1 = new Thread(ticket);
            Thread t2 = new Thread(ticket);
            Thread t3 = new Thread(ticket);
            t1.setName("窗口一");
            t2.setName("窗口二");
            t3.setName("窗口三");
            t1.start();
            t2.start();
            t3.start();
        }
    }
    
    class Ticket3 implements Runnable {
        private int ticket = 100;
        private Object obj = new Object();
        private ReentrantLock lock = new ReentrantLock();
    
        @Override
        public void run() {
            while (true) {
                // synchronized (obj) {
                try {
                    lock.lock();
                    if (ticket == 0) {
                        break;
                    } else {
                        Thread.sleep(100);
                        ticket--;
                        System.out.println(Thread.currentThread().getName() + "还剩票" + ticket);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
                // }
            }
        }
    }
    
    • 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


    6. 生产者消费者

    方法描述
    void wait ()导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法
    void notify ()唤醒正在等待对象监视器的单个线程
    void notifyAll ()唤醒正在等待对象监视器的所有线程

    示例(生产者消费者模式是一个十分经典的多线程协作的模式):

    需求: 模拟消费者和生产者关系,生产者生产 -> 产品+1 -> 生产者等待 -> 消费者消费 产品-1 -> 消费者等待 -> 唤醒生产者

    public class demo4 {
        public static void main(String[] args) {
            Cooker c = new Cooker();
            c.start();
            Foodie f = new Foodie();
            f.start();
        }
    }
    
    // 消费者(吃货)
    class Foodie extends Thread {
        @Override
        public void run() {
            while (true) {
                synchronized (Desk.lock) {
                    // 判断汉堡包是否存在
                    if(Desk.count == 0) {
                        break;
                    } else {
                        if(Desk.flag) {
                            // 有汉堡包,吃货执行,汉堡包不存在了,汉堡包总数减 1,唤醒生产者
                            System.out.println("吃货正在吃汉堡包");
                            Desk.flag = false;
                            Desk.count--;
                            Desk.lock.notifyAll();
                        } else {
                            // 没有汉堡包,吃货等待执行
                            try {
                                Desk.lock.wait();
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                            }
                        }
                    }
                }
            }
        }
    }
    
    // 生产者(厨师)
    class Cooker extends Thread {
        @Override
        public void run() {
            while (true) {
                synchronized (Desk.lock) {
                    if(Desk.count == 0) {
                        break;
                    } else {
                        if(Desk.flag) {
                            // 有汉堡包,厨师等待执行
                            try {
                                Desk.lock.wait();
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                            }
                        } else {
                            // 无汉堡包,厨师执行,汉堡包改为存在,总数加 1,唤醒消费者
                            System.out.println("厨师正在做汉堡包");
                            Desk.flag = true;
                            // 不增加数量,提前结束进程
                            // Desk.count++;
                            Desk.lock.notifyAll();
                        }
                    }
                }
            }
        }
    }
    
    // 产品(桌子上的汉堡包)
    class Desk {
        // false 桌上上没有汉堡包,允许厨师执行
        public static boolean flag = false;
        // 汉堡包的总数
        public static int count = 10;
        // 锁对象(唯一)
        public static final Object lock = new Object();
    }
    
    • 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




  • 相关阅读:
    聊聊MySql索引的类型以及失效场景
    软考高项考试历程回顾
    图片怎么压缩到100k以下?
    Dockerfile构建镜像
    30天Python入门(第五天:深入了解Python中的列表)
    【问题处理小知识】jupyter notebook报错:500 internal server error的几种解决办法整理
    thinkphp5 redis使用
    Hive文件存储格式和数据压缩
    vue大型电商项目尚品汇(前台篇)day02
    PHP XML DOM
  • 原文地址:https://blog.csdn.net/weixin_45137565/article/details/125725704