重学 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
    }
}


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


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("恭喜你,秒杀成功了");
        }
    }




二、集合 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]
    }
}

继承体系:

在这里插入图片描述



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

⑶. 集合遍历
  • Iterator:迭代器,集合的专用遍历方式
  • Iterator<E> 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,
    }
}

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

格式定义:

// for(元素数据类型 变量名 : 数组或者Collection集合) {
     // 在此处使用变量即可,该变量就是元素
// }

ArrayList<String> list = new ArrayList<>();
// 添加一些元素
for(String s : list) {
     System.out.println(i);
}

示例:

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

⑸. 三种循环的使用场景
  • 如果需要操作索引,使用普通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());
        }
    }
}

⑹. 实例

需求: 创建一个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
    }
}


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


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


6. 泛型

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

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

泛型类:

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

泛型方法:

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

泛型接口:

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

类型通配符:

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


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

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

示例:

public class demo4 {
    public static void main(String[] args) {
//        TreeSet<Teacher> teachers = new TreeSet<>(new Comparator<Teacher>() {
//            @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 +
                '}';
    }
}

⑷. 两者比较
  • 自然排序: 自定义类实现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
    }
}

⑵. 哈希值

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

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

对象的哈希值特点:

  • 同一个对象多次调用 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;
    }
}




四、集合 Map

1. Map

⑴. 概述
  • Interface Map<K,V> 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}
    }
}

⑵. 常用方法
方法名描述
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
    }
}

⑶. 遍历 map 集合
方法名描述
V get(Object key)根据键获取值
Set keySet()获取所有键的集合
Set<Map.Entry<K,V>> 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号值
        }
    }
}


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


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


4. 可变参数

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

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

特点:

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

示例:

需求: 实现一个两数相加的方法、实现一个三个数相加的方法, 定义一个方法,求 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;
    }
}


5. 创建不可变的集合

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

特点:

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

示例:

public class demo7 {
    public static void main(String[] args) {
        // static <E>  List<E>  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 <E>  Set<E>  of(E…elements)    创建一个具有指定元素的Set集合对象
        // 报错:java.lang.IllegalArgumentException 非法论据异常
        // Set<String> set = Set.of("a", "b", "c", "a");
        Set<String> set = Set.of("a", "b", "c");
        System.out.println(set);
        // => [a, b, c]

        //static <K , V>   Map<K,V>  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}
    }
}




五、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
    }
}


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<String> stream = list.stream();
        // stream.forEach(s -> System.out.println(s));
        list.stream().forEach(s -> System.out.println(s));
        // => zoe,tony,ami


        // 双列集合
        // HashMap<String, Integer> hm = new HashMap<String, Integer>(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
    }
}


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<T> limit(long maxSize):截取指定参数个数的数据
        list.stream()
                .limit(3)
                .forEach(s -> System.out.println(s));
        // => zoe,tony, alen

        // Stream<T> 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<T> distinct():去除流中重复的元素。依赖(hashCode和equals方法)
        list.stream()
                .distinct()
                .forEach(s -> System.out.println(s));
        // => zoe,tony, alen, zoo

        // static <T> Stream<T> 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
    }
}


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




六、 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
    }
}

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

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 (当前文件不存在)
    }
}

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




七、 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();
    }
}

⑵. 写数据的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. 实现数据追加

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

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

⑷. 文件的读取和复制

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

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

需求: 复制文件

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

需求: 复制多个字节

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

⑸. 字节缓冲流
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();
    }
}

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

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


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

⑷. 拓展方法
方法名描述
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);
    }
}

⑸. 读数据方法
方法名描述
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();
    }
}

⑹. 案例

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

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

⑺. 字符缓冲流
方法名构造方法描述
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));
        }
    }
}

- 字符缓冲输入流

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

⑻. 字符缓冲流特有功能
方法名构造方法描述
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();
    }
}

⑼. 案例

需求: 读取文件中的数据,排序后再次写到本地文件

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




八、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);
        }
    }
}


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

- 读对象 — 反序列化

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


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

⑵. 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<String> 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
    }
}

⑶. 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. 线程相关的概念

  • 并行: 在同一时刻,有多个指令在多个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);
        }
    }
}

⑵. 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);
        }
    }
}

⑶. 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 "答应";
    }
}

⑷. 三种方式对比
方法优点缺点
实现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();
    }
}

⑵. 线程名称
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);
        }
    }
}

⑶. 线程休眠
public class demo7 {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("睡觉了");
        Thread.sleep(1000);
        System.out.println("醒了");
        // => 睡觉了 (间隔1000ms) 醒了
    }
}

⑷. 优先级

线程有两种调度模型:

  • 分时调度模型: 所有线程轮流使用 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;
    }
}

⑸. 守护线程
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);
        }
    }
}

⑹. 线程生命周期

在这里插入图片描述



5. 线程的安全问题

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

同步的好处和弊端:

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

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

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

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

⑵. 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();
            }
            // }
        }
    }
}


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




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

后海 0_o

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值