JavaSE进阶笔记

1、Scanner方法中next和nextLine的区别

next():遇到空格就不接收数据了,容易丢失数据。
结束标记:空格,Tab键
nextLine():可以完整的接收数据
结束标记:回车
细节问题:当数字和字符串一起接收(先接收一个数字紧接着接收一个字符串)的时候,nextLine()就不起作用了,因为在输入完一个数字时,需要摁一下回车键,此时nextInt()已接收完数字,完成工作,但是回车键还留存在内存当中,当nextLine()将要开始接收字符串时,却遇到了回车键,所以nextLine()直接结束了,不接受数据了,此时应该采用next()方法才可以结接收数据。大多数情况下,推荐使用next()方法。

2、使用双引号创建字符串和使用构造方法创建字符串的区别

双引号:使用双引号进行创建字符串时,会去查询字符串常量池,如果创建的字符串不在字符串常量池中,则创建,如果在字符串常量池存在相同的字符串,则直接引用,不会开辟新的内存地址。
构造方法:使用构造方法创建字符串时,是直接在堆内存中开辟一个地址。并且实际上创建了两个字符串对象,一个String括号里面的存储在字符常量池中,另一个是new出来存储在堆内存中。
注:new 出来的东西一般存储在堆内存中。

String s1 = "abc";
String s2 = "abc";
System.out.println(s1 == s2);//使用引号创建字符串,在创建字符串s2时发现“abc”已在字符串常量池中,所以直接引用,与s1地址相同,结果为true
//-----------------------------------------
char [] c = {'a','b','c'};
String s3 = new String(c);
String s4 = new Stirng(c);
System.out.println(s3 == s4);//结果为false

3、字符串比较

==:使用双等号来进行比较时
基本类型:比较他们的内容是否相同
引用类型:比较他们的地址是否相同
equals:直接比较字符串的内容是否相同,不比较地址。
equalsIgnoreCase:比较字符串的内容是否相同,忽略大小写,常用于验证码

4、String与StringBuilder

区别:String是不可修改的字符串,而StringBuilder是可修改的字符串。可以将StringBuilder视为一种容器。

两者的相互转化:

String->StringBuilder:使用StringBuilder的有参构造方法(StringBuilder sb = new StringBuilder(s))
StringBuilder->String:使用ToString()方法。

StringBuilder提高效率的原理

String s1 = "a";
String s2 = s1 + "b";
String s3 = s2 + "c";
System.out.println(s3);

在使用String用“+”拼接字符串时,暗含以下操作,首先“a”会在字符串常量池中创建,然后在s1与“b”进行拼接时,编辑器在堆内存会自动创建StringBuilder,开辟一个地址其内容为“ab”,同时“b”也会在字符串常量池中被创建,然后由于此时“ab”为StringBuilder类型,还不能赋值给为String类型的s2,所有StringBuilder使用ToString()方法将StringBuilder类型转为String类型,将“ab”赋给s2,同理,s3也是如此,就是先创建StringBuilder类型的“abc”,然后用ToString()方法转为String类型赋给s3。注意,这种会额外产生StringBuilder的“ab”和“abc”,不仅会占用内存,而且还要转换类型,降低速度。

StringBuilder sb = new StringBuilder();
sb.append("a");
sb.append("b");
sb.append("c");
System.out.println(sb);

而使用StringBuilder使用append方法会在一个StringBuilder中拼接,只会有一个StringBuilder,而且不需要类型转化。
在这里插入图片描述 使用String拼接字符串
在这里插入图片描述使用StringBuilder拼接字符串

5、继承构造方法的特点

所有子类的构造方法执行前都要先完成父类的无参构造方法,每个构造方法都会有一个隐含的super(),每次执行构造方法时,都会先执 行这个super()方法,最顶层的父类是object。
原因:因为子类继承了父类,所以子类可能会用到父类的数据,所以需要在子类初始化之前先完成父类的初始化,完成父类的初始化就需要执行父类的无参构造方法。

6、接口

成员变量:凡是在接口定义的成员变量,前面默认的关键字为public static final。
构造方法:接口没有构造方法。
成员方法:接口的成员方法不允许有body,并且前面默认的关键字为public abstract
default关键字:带有default关键字的成员方法,实现类可以不进行重写,允许该方法有body,并且可以直接拿来使用,类似于继承,此时该成员方法也就变成非抽象的了。如果不带有default成员方法,实现类必须重写接口中的成员方法。

7、多态访问特点

成员变量:编译看左边(父类),执行看左边(父类)。
成员方法:编译看左边(父类),执行看右边(子类)。

8、匿名内部类

实现匿名内部类的前提是要有接口/类,其作用是,将继承/实现、方法重写、创建对象一步完成。以下是个例子:

package test;

public class Test03 {
    public static void main(String[] args) {
        Inter inter = new interImpl();
        inter.show();
        new Inter(){
            @Override
            public void show() {
                System.out.println("匿名内部类中的show方法、、、、、、、、、、、、、");
            }
        }.show();
    }
}
interface Inter{
    void show();
}
class interImpl implements Inter{

    @Override
    public void show() {
        System.out.println("实现类interImpl中的show方法");
    }
}

输出结果为:
在这里插入图片描述匿名内部类代码解释:
匿名内部类的作用是将继承/实现、重写、创建对象一步完成
一点点看,个人看来,实现接口可以看作是一种抽象继承,然后结构体里面也实现方法重写,而创建对象就是:new Inter();这个便就是创建对象,而对象又可以调用其方法,所以在这个案例中大括号可以.shou()调用方法的原因,那么这个对象的方法是谁呢?其实它就是他自己结构体中重写的成员方法。
匿名内部类的应用场景:

package test;

public class Test03 {
    public static void main(String[] args) {
        getShow(new Inter(){
            @Override
            public void show() {
                System.out.println("匿名内部类的应用");
            }
        });
    }
    public static void getShow(Inter inter){
        inter.show();
    }
}
interface Inter{
    void show();
}


9、Lambda表达式

Lambda表达式使用前提:

  • 有一个接口
  • 该接口中有且仅有一个成员方法
    练习1:无参
    在这里插入图片描述代码:
package test;

public class Test03 {
    public static void main(String[] args) {
        //匿名内部类的实现
        userShowHandler(new ShowHandler() {
            @Override
            public void show() {
                System.out.println("匿名内部类中的show方法");
            }
        });
        //Lambda实现
        userShowHandler(()->{
            System.out.println("Lambda中的show方法");
        });
    }
    public static void userShowHandler(ShowHandler showHandler){
        showHandler.show();
    }
}
interface ShowHandler{
    void show();
}


练习2:有参
在这里插入图片描述
代码:

package test;

public class StringHandlerDemo {
    public static void main(String[] args) {
        //匿名内部类实现
        useStringHandler(new StringHandler() {
            @Override
            public void printMessage(String msg) {
                System.out.println("我是匿名内部类"+msg);
            }
        });
        //Lambda实现
        useStringHandler((String msg) -> {
            System.out.println("我是Lambda表达式"+msg);
        });
    }
    public static void useStringHandler(StringHandler stringHandler){
        stringHandler.printMessage("test");
    }
}
interface StringHandler{
    void printMessage(String msg);
}

练习3:带返回值
在这里插入图片描述
代码:

package test;

import javax.swing.*;
import java.util.Random;


public class RandomNumHandlerDemo {
    public static void main(String[] args) {
        //匿名内部类
        useRandomNumHandler(new RandomNumHandler() {
            @Override
            public int getNumber() {
                Random r  = new Random();
                int num = r.nextInt(10)+1;
                return num;
            }
        });
        useRandomNumHandler(()->{
            Random r = new Random();
            int num = r.nextInt(10)+1;
            return num;
        });
    }
    public static void useRandomNumHandler(RandomNumHandler randomNumHandler){
        int num = randomNumHandler.getNumber();
        System.out.println(num);
    }
}
interface RandomNumHandler{
    int getNumber();
}

练习4:有参且带有返回值
在这里插入图片描述代码:

package test;

public class CalculatorDemo {
    public static void main(String[] args) {
        //匿名内部类实现
        useCalculator(new Calculator() {
            @Override
            public int calc(int a, int b) {
                return a+b;
            }
        });
        useCalculator((int a ,int b)->{
            return a+b;
        });
    }

    public static void useCalculator(Calculator calculator){
        int result = calculator.calc(1,2);
        System.out.println(result);
    }
}
interface Calculator{
    int calc(int a, int b);
}

10、自定义异常示例

package test;

public class AgeOutOfBoundsException extends RuntimeException{

    public AgeOutOfBoundsException() {
    }

    public AgeOutOfBoundsException(String message) {
        super(message);
    }
}

11、时间日期类

常识:中国标准时间(北京时间)= 世界标准时间+8小时
1s = 100
计算的时间原点为:1970年1月1日00:00:00

  • Date
    构造方法1:
    Date date = new Date();
    这种方法表示计算机当前时间。
    构造方法2:
    Date date = new Date(Long date);
    这种方法表示从计算机原点时间加上构造方法里面的时间,括号里指定的时间单位为毫秒。
    成员方法:
    date.getTime():获取当前的date对象的毫秒值。
    date.setTime():将此 Date对象设置为表示格林尼治标准时间1970年1月1日00:00:00之后的 time毫秒的时间点。

  • SimpleDateFormat
    构造方法1:
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
    时间默认格式
    构造方法2:
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”);
    这种方法可转为括号中指定的时间格式。

  • 时间对象转为指定格式的字符串

package test;

import entity.Student;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Objects;
import java.util.Scanner;

public class Test01 {
    public static void main(String[] args) {
        Date testDate = new Date();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String Date = simpleDateFormat.format(testDate);
        System.out.println(Date);
    }
}

  • 字符串转为时间对象
package test;

import entity.Student;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Objects;
import java.util.Scanner;

public class Test01 {
    public static void main(String[] args) {
        String string = "2020-9-20 10:10:10";
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        try {
            Date date = simpleDateFormat.parse(string);
            System.out.println(date);
        } catch (ParseException e) {
            e.printStackTrace();
        }

    }
}

练习:
在这里插入图片描述
代码:

package test;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class Test01 {
    public static void main(String[] args) {
        String start = "2020-11-11 00:00:00";
        String end = "2020-11-11 00:10:00";
        String jia = "2020-11-11 00:03:47";
        String pi = "2020-11-11 00:10:11";
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        try {
            Date startDate = simpleDateFormat.parse(start);
            Date endDate = simpleDateFormat.parse(end);
            Date jiaDate = simpleDateFormat.parse(jia);
            Date piDate = simpleDateFormat.parse(pi);
            long processDate = endDate.getTime()-startDate.getTime();
            if((jiaDate.getTime()-startDate.getTime())<processDate){
                System.out.println("小贾在秒杀活动范围内");
            }else{
                System.out.println("小贾不在在秒杀活动范围内");
            }
            if((piDate.getTime()-startDate.getTime())<processDate){
                System.out.println("小皮在秒杀活动范围内");
            }else{
                System.out.println("小皮不在在秒杀活动范围内");
            }
        } catch (ParseException e) {
            e.printStackTrace();
        }

    }
}

12、集合

集合框架结构:
在这里插入图片描述

  • 集合的迭代器遍历
    Iterator: 迭代器,集合的专门遍历方式。
    构造方法:Iterator<集合泛型> iterator = 集合对象.iterator();
    注:每次构造完成迭代器,迭代器自动指向集合索引为0的位置,也就是第一个位置。
    Iterator的成员方法:
    Iterator.hasNext():表示当前是否有元素可以取出,若可以取出则返回true,否则返回false。
    Iteratro.next():做两个事情,一是取出当前元素,并返回,二是将迭代器指向下一个索引位置。
    有这两个方法便可以实现集合的遍历:
package test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class Test01 {
    public static void main(String[] args) {
        Collection<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        Iterator<String> iterator = list.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }

    }
}

  • 迭代器的删除
package test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class Test01 {
    public static void main(String[] args) {
        Collection<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("b");
        list.add("c");
        list.add("d");
        Iterator<String> iterator = list.iterator();
        while(iterator.hasNext()){
            if("b".equals(iterator.next())){
                //指向谁,那么就删除谁
                iterator.remove();
            }
        }
        System.out.println(list);
    }
}

12、Set集合

Set集合的特点:
1、元素不允许重复
2、存取数据不一致
3、Set集合中没有索引,所以不能够通过普通for循环来遍历Set集合。

  • TreeSet
    TreeSet集合特点:
    1、底层数据结构是红黑树。
    2、无索引,不可用普通for循环来遍历集合。
    3、不允许元素重复。
    特有特点:可以按照一定规则进行排序。
    自然排序Comparable接口
    如果要将自定义实体类进行排序,那么就需要重写Comparable接口里面的Comparato()方法,自己定义比较规则,然后才可以在TreeSet中顺序存储。
    自然排序Comparable接口的使用:
    1、使用空参构造TreeSet集合
    2、 用自定义实体类来实现Comparable
    3、重写compareTo()方法
    在这里插入图片描述
    在这里插入图片描述在这里插入图片描述
    comparaTo方法的重写:
    @Override
    public int compareTo(Student o) {
        int result = this.age-o.age;
        return result;
    }

代码说明:this.age 指的是将要插进来Student的年龄,而o.age指的TreeSet集合中原有的Student的年龄,将两者进行比较。返回值为正的Student放在后面,返回值为负的放在前面,返回值为0则不存。
案例练习:
在这里插入图片描述`

    @Override
    public int compareTo(Student o) {
        int result = this.age - o.age;
        result = result == 0 ? this.name.compareTo(o.name) : result;
        return result;
    }

代码里面 的compareTo()方法调用的是String的,首先比较第一个字母,如果第一个字母相同,则比较下一个字母,如果不同,则用该字母的ASCII值减去compareTo()括号里面字母的ASCII值,返回值为Int型数值。
比较器排序Comparator
Comparator比较器用于有参的TreeSet的排序。

package test;
import entity.Student;
import entity.Teacher;

import java.util.*;

public class Test01 {
    public static void main(String[] args) {
        TreeSet<Teacher> treeSet = new TreeSet<Teacher>(new Comparator<Teacher>() {
            @Override
            public int compare(Teacher o1, Teacher o2) {
                //o1是现在将要存进去的元素
                //02是treeSet中已有的元素
                int result = o1.getAge()-o2.getAge();
                result = result == 0 ? o1.getName().compareTo(o2.getName()):result;
                return result;
            }
        });
        Teacher t1 = new Teacher("zhangsan",23);
        Teacher t2 = new Teacher("lisi",21);
        Teacher t3 = new Teacher("wangwu",34);
        treeSet.add(t1);
        treeSet.add(t2);
        treeSet.add(t3);
        System.out.println(treeSet);
    }
}

注:o1是现在将要存进去的元素,02是treeSet中已有的元素
练习:
在这里插入图片描述代码:

package test;
import entity.Student;
import entity.Teacher;

import java.util.*;

public class Test01 {
    public static void main(String[] args) {
        TreeSet<String> treeSet = new TreeSet<>(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                int result = o1.length()-o2.length();
                result = result == 0 ? o1.compareTo(o2):result;
                return result;
            }
        });
        //Lambda表达式改写
//        TreeSet<String> treeSet = new TreeSet<>((String o1,String o2)->{
//            int result = o1.length()-o2.length();
//            result = result == 0 ?o1.compareTo(o2):result;
//            return result;
//        });
        String s1 = "c";
        String s2 = "ab";
        String s3 = "df";
        String s4 = "qwer";
        treeSet.add(s1);
        treeSet.add(s2);
        treeSet.add(s3);
        treeSet.add(s4);
        System.out.println(treeSet);
    }
}

  • HashSet
    HashSet特点:
    1、不能保证存储和取出的顺序一致
    2、元素不能重复
    3、底层的数据结构是哈希表
    4、不能够通过索引来遍历集合
    成员方法:
    hashCode():通过对象的地址值计算出哈希值。
    hashCode()可以在自定义的实体类中重写,而重写后的hashCode()方法是通过对象的属性值来计算哈希值的。
    代码:
    hashCode()计算出哈希值
package test;
import entity.Student;
import entity.Teacher;

import java.util.*;

public class Test01 {
    public static void main(String[] args) {
        Student stu1 = new Student("zhangsan",23);
        Student stu2 = new Student("lisi",22);
        System.out.println(stu1.hashCode());//1915910607
        System.out.println(stu2.hashCode());//284720968
    }
}

14、红黑树

红黑规则:
1、每个节点只能是红或黑节点。
2、根节点必须是黑节点。
3、红节点的两个子节点必须是黑节点。
4、叶节点也就是值为null的节点,必须是黑节点
5、对于每一个节点,从该节点到其后代的节点的简单路径上,经过相同数目的黑节点,如果起点是黑节点,不包括自身。

15、Map

Map的基本使用:

package test;


import java.util.*;

public class Test01 {
    public static void main(String[] args) {
        Map<String,String> map = new HashMap<>();
        map.put("1","小明");
        map.put("2","小红");
        map.put("3","小刚");
        System.out.println(map);
    }
}

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

package test;


import java.util.*;

public class Test01 {
    public static void main(String[] args) {
        Map<String,String> map = new HashMap<>();
        //V put(K key,V value) 添加元素
        //使用put方法,如果所添加的键不存在则添加,如果添加的健已存在,
        //则该键新增的值覆盖原来的值
        map.put("1","小明");
        map.put("2","小红");
        map.put("3","小刚");
        String s1 = map.put("1","小智");
        System.out.println(s1);//小明
        System.out.println(map);//{1=小智, 2=小红, 3=小刚}
        System.out.println("-----------------------------------");
        //V remove(Object key) 根据键删除键值对元素
        String s2 = map.remove("1");
        System.out.println(s2);//小智
        System.out.println(map);//{2=小红, 3=小刚}
//        void clear() 移除所有的键值对元素
        System.out.println("---------------------------------------");
        map.clear();
        System.out.println(map);//{}
        System.out.println("---------------------------------------");
//        boolean containsKey(Object key) 判断集合是否包含指定的键
        map.put("1","小明");
        map.put("2","小红");
        map.put("3","小刚");
        if(map.containsKey("1")){
            System.out.println(true);//true
        }
//        boolean containsValue(Object value) 判断集合是否包含指定的值
        System.out.println("---------------------------------------");
        if(map.containsValue("小明")){
            System.out.println(true);
        }
        System.out.println("---------------------------------------");
//        boolean isEmpty() 判断集合是否为空
        System.out.println(map.containsValue("小明"));
//        int size() 集合的长度,也就是集合中键值对的个数
        System.out.println("---------------------------------------");
        System.out.println(map.size());
    }
}

map的遍历:

  • 第一种遍历

V get(Object key) 根据键获取值
Set keySet() 获取所有键的集合

package test;
import java.util.*;
public class Test01 {
    public static void main(String[] args) {
        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号妻子");
        //获得所有key,并将他们存在集合当中
        Set<String> keys = map.keySet();
        for (String key : keys) {
            //通过key获得相应的value
            String value = map.get(key);
            System.out.println(key+"->"+value);
        }
    }
}

  • 第二种遍历
    Set<Map.Entry<K,V>> entrySet() 获取所有键值对对象的集合
    K getKey() 获得键
    V getValue() 获得值
package test;
import java.util.*;
public class Test01 {
    public static void main(String[] args) {
        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号妻子");
        //Set集合存储所有的键值对对象
        Set<Map.Entry<String, String>> entrySet = map.entrySet();
        for (Map.Entry<String, String> entry : entrySet) {
            //获得键值对的键和值
            String key = entry.getKey();
            String value = entry.getValue();
            System.out.println(key+"->"+value);
        }
    }
}

练习:
在这里插入图片描述

package test;
import entity.Student;

import java.util.*;
public class Test01 {
    public static void main(String[] args) {
        Map<Student,String> map  = new HashMap<>();
        map.put(new Student("小明",23),"山东济南");
        map.put(new Student("小红",24),"湖南长沙");
        map.put(new Student("小刚",25),"广东广州");
        //第一种遍历
        //获得所有的键,并存在Set集合中
        Set<Student> keySet = map.keySet();
        for (Student student : keySet) {
            //通过键获得相应的值
            String value = map.get(student);
            System.out.println(student.getName()+"的籍贯为:"+value);
        }
        System.out.println("---------------------------------------");
        //第二种遍历
        //获得所有的键值对对象并存在set集合中
        Set<Map.Entry<Student, String>> entries = map.entrySet();
        for (Map.Entry<Student, String> entry : entries) {
            //获取键值对的键和值
            Student student = entry.getKey();
            String value = entry.getValue();
            System.out.println(student.getName()+"的籍贯为:"+value);
        }
        System.out.println("---------------------------------------");
        //第三种遍历
        map.forEach((Student student,String value)->{
            System.out.println(student.getName()+"的籍贯为:"+value);
        });
    }
}

TreeMap的使用练习:
在这里插入图片描述
代码:

package test;
import entity.Student;

import java.util.*;
public class Test01 {
    public static void main(String[] args) {
        Map<Student,String> map  = new TreeMap<>(new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                int result = o1.getAge()-o2.getAge();
                result = result == 0 ? o1.getName().compareTo(o2.getName()):result;
                return result;
            }
        });
        Student s1 = new Student("zhangsan",23);
        Student s2 = new Student("lisi",35);
        Student s3 = new Student("wangwu",22);
        Student s4 = new Student("zhaoliu",13);
        map.put(s1,"山东济宁");
        map.put(s2,"湖南长沙");
        map.put(s3,"广东广州");
        map.put(s4,"江苏宿迁");
        map.forEach((Student student,String value)->{
            System.out.println(student.getName()+"的籍贯为:"+value);
        });

    }
}

注:这个和TreeSet很像,但是要注意compare(Student o1, Student o2)这个里面是两个Key的类型。

16、可变参数

练习:
在这里插入图片描述

package test;
import entity.Student;

import java.util.*;
public class Test01 {
    public static void main(String[] args) {
        int sum = getSum(1,2,3,4,5,6);
        System.out.println(sum);
    }
    private static int getSum(int... arr) {
        int sum = 0;
        for (int i = 0; i < arr.length; i++) {
            sum+=arr[i];
        }
        return sum;
    }

}

注意:getSum(int number,int… arr)这样写是对的,但是getSum(int… arr,int number)这样写是错的,因为可变参数要放在最后面。

16、不可变集合

1、通过Map、Set、List中的of方法可以创建不可变集合。
2、创建的不可变集合不能添加、不能删除、不能修改。
3、Map中有个ofEntries方法,可以提高代码的阅读性,其实质和of方法一致。
4、不可变集合可通过带参来实现集合的批量添加
代码:

package test;
import entity.Student;

import java.util.*;
public class Test01 {
    public static void main(String[] args) {
        //List创建不可变集合
        List<Integer> list = List.of(1, 2, 3, 4);
        //加入到参数里面实现批量添加
        ArrayList<Integer> list1 = new ArrayList<>(list);
        //Set创建不可变集合
        Set<String> stringSet = Set.of("1", "2", "3");
        //Map创建不可变集合
        Map<String, String> map = Map.of("1", "小明", "2", "小红");
        //Map.ofEntries()创建不可变集合,可提高代码的阅读性,其实质与of方法一致
        Map<String, String> map1 = Map.ofEntries(Map.entry("1", "小明"), Map.entry("2", "小刚"));
    }


}

17、Stream流

  • 获取Stream流
    可以理解为创建一条流水线,然后将数据放在这个流水线上。
    单列集合:直接使用Collection接口中的Stream()方法。
    双列集合:间接生成流,先通过keySet或者entrySet()生成Set集合,然后在通过Stream()方法生成。
    数组:使用Arrays中的Stream方法,方法参数就是数组。
    同种类型的多个数据:使用Stream.of(T…value)。
package test;
import entity.Student;

import java.util.*;
import java.util.stream.Stream;

public class Test01 {
    public static void main(String[] args) {
        //单列集合
        ArrayList<String> arrayList = new ArrayList<>(List.of("zhangsan","lisi","wangwu ","zhaoliu"));
        arrayList.stream().forEach(s-> System.out.println(s));
        System.out.println("--------------------------------------------");
        //双列集合,间接获取流
        //通过keySet()获取流
        Map<String,Integer> map = new HashMap<>(Map.of("xiaoming",1,"xiaohong",2,"xiaogang",3,"xiaoying",4));
        Set<String> keySet = map.keySet();
        keySet.stream().forEach(s-> System.out.println(s));
        System.out.println("--------------------------------------------");
        //通过entrySet()获取流
        Set<Map.Entry<String, Integer>> entries = map.entrySet();
        entries.stream().forEach(s->{
            System.out.println("键:"+s.getKey()+"值:"+s.getValue());
        });
        System.out.println("--------------------------------------------");
        //数组
        int [] arr = {1,2,3,4,5};
        Arrays.stream(arr).forEach(num-> System.out.println(num));
        System.out.println("--------------------------------------------");
        //同类型的多个数据
        Stream.of("1","3","xiaohong","hello").forEach(s-> System.out.println(s));
    }


}

  • 中间方法
    对流水线上的数据进行操作。
    Stream filter(Predicate predicate) :用于对流中的数据进行过滤
    Predicate 接口中的方法
    boolean test(T t) :对给定的参数进行判断,返回一个布尔值
package test;
import entity.Student;

import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Stream;

public class Test01 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>(List.of("张无忌","张三丰","四季花开","张谦","小明"));
        //filter方法时获取流中的所有数据
        //test方法则是依次拿取其中的每一个数据
        //只需要判断s就可以了
        //如果判断结果为true,则留下,否则,就过滤掉
        list.stream().filter(new Predicate<String>() {
            @Override
            public boolean test(String s) {
                boolean result = s.startsWith("张");
                return result;
            }
        }).forEach(s-> System.out.println(s));
        System.out.println("--------------------------------------------");
        list.stream().filter((String s)->{
            boolean result = s.startsWith("张");
            return result;
        }).forEach(s-> System.out.println(s));
        System.out.println("--------------------------------------------");
        list.stream().filter(s->s.startsWith("张")).forEach(s -> System.out.println(s));
    }


}

注:filter方法时获取流中的所有数据,test方法则是依次拿取其中的每一个数据,所以只需要判断s就可以了,如果判断结果为true,则留下,否则,就过滤掉。
Stream limit(long maxSize) :截取指定参数个数的数据,从前往后
Stream skip(long n) :跳过指定参数个数的数据,跳过前面的指定的个数,然后截取后面的
static Stream concat(Stream a, Stream b) :合并 a 和 b 两个流为一个流
Stream distinct() :去除流中重复的元素。依赖 (hashCode 和 equals 方法 )

package test;
import entity.Student;

import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Stream;

public class Test01 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>(List.of("张无忌","张三丰","四季花开","张谦","小明","张三丰"));
//        Stream<T> limit(long maxSize) :截取指定参数个数的数据
        list.stream().limit(2).forEach(s-> System.out.println(s));//张无忌  张三丰
        System.out.println("--------------------------------------------");
// Stream<T> skip(long n) :跳过指定参数个数的数据
        list.stream().skip(2).forEach(s-> System.out.println(s));//四季花开 张谦 小明 张三丰
        System.out.println("--------------------------------------------");
// static <T> Stream<T> concat(Stream a, Stream b) :合并 a 和 b 两个流为一个流
        Map<String,Integer> map = new HashMap<>(Map.of("你好",1,"还好啊",2,"试一试",3));
        Set<String> keySet = map.keySet();
        Stream.concat(keySet.stream(),list.stream()).forEach(s-> System.out.println(s));//你好 试一试 还好啊 张无忌 张三丰 四季花开 张谦 小明 张三丰
        System.out.println("--------------------------------------------");
// Stream<T> distinct() :去除流中重复的元素。依赖 (hashCode 和 equals 方法 )
        list.stream().distinct().forEach(s-> System.out.println(s));//张无忌 张三丰 四季花开 张谦 小明
    }
}

  • 终结方法
    每个Stream流只有一个终结方法。
    void forEach(Consumer action) :对此流的每个元素执行操作
    Consumer 接口中的方法 void accept(T t) :对给定的参数执行此操作
    long count() :返回此流中的元素数
package test;
import entity.Student;

import java.util.*;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;

public class Test01 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>(List.of("张无忌","张三丰","四季花开","张谦","小明"));
        //forEach方法循环调用accept方法,并将每一个数据传给accept
        //因此形参s也就是获取到的每一个数据
        list.stream().forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        });
        System.out.println("----------------------------------------");
        list.stream().forEach((String s)->{
            System.out.println(s);
        });
        System.out.println("----------------------------------------");
        list.stream().forEach(s-> System.out.println(s));
        System.out.println("----------------------------------------");
        long count = list.stream().count();
        System.out.println(count);
    }
}

练习:
在这里插入图片描述代码:

package test;
import java.util.*;
public class Test01 {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>(List.of(1,2,3,4,5,6,7,8,9,10));
        list.stream().filter(integer -> integer%2 == 0).forEach(s-> System.out.println(s));
    }
}

  • Stream流的收集操作

Stream 流的收集方法
R collect(Collector collector)
工具类 Collectors 提供了具体的收集方式
public static Collector toList() :把元素收集到 List 集合中
public static Collector toSet() :把元素收集到 Set 集合中
public static Collector toMap(Function keyMapper,Function valueMapper) :把元素收集到 Map 集合中

package test;
import java.util.*;
import java.util.stream.Collectors;

public class Test01 {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>(List.of(1,2,3,4,5,6,7,8,9,10,10,10,10));
        //filter是用来过滤的
        //collect是用来收集过滤后的数据的,但是他不负责创建容器,也不负责将数据添加到容器当中去。
        //Collectors.toList()创建一个List容器,并将collect收集到的数据添加到该容器中
        List<Integer> integerList = list.stream()
                .filter(number -> number % 2 == 0)
                .collect(Collectors.toList());
        Set<Integer> set = list.stream()
                .filter(number -> number % 2 == 0)
                .collect(Collectors.toSet());
        System.out.println(integerList);//[2, 4, 6, 8, 10, 10, 10, 10]
        System.out.println("-------------------------------");
        System.out.println(set);//[2, 4, 6, 8, 10]
    }
}

练习:
在这里插入图片描述

package test;
import entity.Actor;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

//现在有两个 ArrayList 集合,分别存储 6 名男演员和 6 名女演员,要求完成如下的操作
//        男演员只要名字为 3 个字的前两人
//        女演员只要姓杨的,并且不要第一个
//        把过滤后的男演员姓名和女演员姓名合并到一起
//        把上一步操作后的元素作为构造方法的参数创建演员对象 , 遍历数据
//        演员类 Actor ,里面有一个成员变量,一个带参构造方法,以及成员变量对应的 get/set 方法
public class Test01 {
    public static void main(String[] args) {
        //有两个 ArrayList 集合,分别存储 6 名男演员和 6 名女演员
        ArrayList<String> listM = new ArrayList<>(List.of("张国立","张晋","刘烨","郑伊健","徐峥","王宝强"));
        ArrayList<String> listF = new ArrayList<>(List.of("郑爽","杨紫","关晓彤","张天爱","杨幂","赵丽颖"));
        //男演员只要名字为 3 个字的前两人
        Stream<String> streamM = listM.stream().filter(s -> s.length() == 3).limit(2);
        //女演员只要姓杨的,并且不要第一个
        Stream<String> streamF = listF.stream().filter(s -> s.startsWith("杨")).skip(1);
        //把过滤后的男演员姓名和女演员姓名合并到一起
        //第一种方式
//        Stream<String> stream = Stream.concat(streamM, streamF);
//        ArrayList<String> actors = new ArrayList<>(stream.toList());
//        for (String actor : actors) {
//            System.out.println(actor);
//        }
        System.out.println("------------------------------------");
        //第二种方式
        Stream.concat(streamM,streamF).forEach(s-> {
            Actor actor = new Actor(s);
            System.out.println(actor);
        });
    }
}

18、File类

FIle类是什么?
1 ,在读写数据时告诉虚拟机要操作的(文件 / 文件夹)在哪
2 ,对(文件 / 文件夹)本身进行操作。包括创建,删除等。

  • File类
    File类的构造方法:
    File(String pathname) 通过将给定的路径名字符串转换为抽象路径名来创建新的 File
    实例
    File(String parent, String child) 从父路径名字符串和子路径名字符串创建新的 File 实例
    File(File parent, String child) 从父抽象路径名和子路径名字符串创建新的 File 实例
    绝对路径与相对路径:
    在这里插入图片描述
    File的创建功能:
    public boolean createNewFile() 创建一个新的空的文件
    public boolean mkdir() 创建一个单级文件夹
    public boolean mkdirs() 创建一个多级文件夹
package test;
import java.io.File;
import java.io.IOException;
public class Test01 {
    public static void main(String[] args) throws IOException {
//        public boolean createNewFile() 创建一个新的空的文件
        //如果文件已存在则创建失败,返回false
        //如果文件不存在则创建,返回true
//        File file = new File("D:\\test.txt");
//        boolean newFile = file.createNewFile();
//        public boolean mkdir() 创建一个单级文件夹
//        File file = new File("D:\\test");
//        System.out.println(file.mkdir());
//        public boolean mkdirs() 创建一个多级文件夹
        File file = new File("D:\\test\\aaa\\bbb\\ccc");
        System.out.println(file.mkdirs());
    }
}

FIle的删除功能
public boolean delete() 删除由此抽象路径名表示的文件或目录
删除目录时的注意事项:
delete 方法直接删除不走回收站。
如果删除的是一个文件,直接删除。
如果删除的是一个文件夹,需要先删除文件夹中的内容,最后才能删除文件夹。

package test;
import java.io.File;
import java.io.IOException;
public class Test01 {
    public static void main(String[] args) throws IOException {
        //注意:
        //1、不走回收站
        //2、如果删除的是文件,那么直接删除,如果删除的是空的文件夹,那么也是直接删,有内容的文件夹不能直接删
        //3、如果要删除有内容的文件夹,那么需要先删除文件夹里面的内容,然后再删除文件夹。
        //简单来说,这个删除功能只能删除文件或者是空的文件夹。
        File file = new File("D:\\test");
        System.out.println(file.delete());
    }
}

FIle的判断和获取功能
public boolean isDirectory() 测试此抽象路径名表示的 File 是否为目录
public boolean isFile() 测试此抽象路径名表示的 File 是否为文件
public boolean exists() 测试此抽象路径名表示的 File 是否存在
public String getAbsolutePath() 返回此抽象路径名的绝对路径名字符串
public String getPath() 将此抽象路径名转换为路径名字符串
public String getName() 返回由此抽象路径名表示的文件或目录的名称
File的高级获取功能
public File[] listFiles() 返回此抽象路径名表示的目录中的文件和目录的 File 对象数组

package test;
import java.io.File;
import java.io.IOException;
public class Test01 {
    public static void main(String[] args) throws IOException {
        File file = new File("D:\\");
        File[] files = file.listFiles();
        for (File file1 : files) {
            System.out.println(file1);
        }
    }
}

listFiles 方法注意事项:
当调用者不存在时,返回 null
当调用者是一个文件时,返回 null
当调用者是一个空文件夹时,返回一个长度为 0 的数组
当调用者是一个有内容的文件夹时,将里面所有文件和文件夹的路径放在 File 数组中返回
当调用者是一个有隐藏文件的文件夹时,将里面所有文件和文件夹的路径放在 File 数组中返回,包含隐藏内容
当调用者是一个需要权限才能进入的文件夹时,返回 null
练习1:
在这里插入图片描述

package test;
import java.io.File;
import java.io.IOException;
public class Test01 {
    public static void main(String[] args) throws IOException {
        File file = new File("Test\\aaa");
        if(!file.exists()){
            file.mkdirs();
            File file1 = new File("Test\\aaa\\a.txt");
            file1.createNewFile();
        }
    }
}

练习2:

在这里插入图片描述
代码:

package test;
import java.io.File;
import java.io.IOException;
public class Test01 {
    public static void main(String[] args) throws IOException {
        File file = new File("E:\\ProjectTwo");
        deleteDir(file);
    }
    private static void deleteDir(File file) {
        //由于delete()方法不能直接删除有内容的文件夹,所以要先将里面的内容删除
        //又因为一个文件夹中可能存在其他的文件夹,所以要用递归去解决
        //1、获取
        File[] files = file.listFiles();//获取该文件中所有的文件或文件夹对象。
        //2、遍历
        for (File file1 : files) {
            //3、判断
            //如果是文件,直接删除
            if(file1.isFile()){
                file1.delete();
                //4、判断
            }else if (file1.isDirectory()){//如果是文件夹,则递归删除其内容
                deleteDir(file1);
            }
        }
        //最后会存在一个空的文件夹,直接删除即可。
        file.delete();

    }
}

练习3:
在这里插入图片描述
代码:

package test;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
public class Test01 {
    public static void main(String[] args) throws IOException {
        //如果定义一个变量去依次的去统计,那么一次只能判断一个
        //采用map来统计,可一次统计出来,键:文件后缀名,值:个数
        File file = new File("Test");
        HashMap<String,Integer> map = new HashMap<>();
        getCount(map,file);
        System.out.println(map);
    }

    private static void getCount(HashMap<String, Integer> map, File file) {
        //获取file内容的文件或文件夹对象
        File[] files = file.listFiles();
        for (File file1 : files) {
            //判断是否是文件
            if(file1.isFile()){
                //获取该文件的文件名,格式:文件名.后缀名
                String fileName = file1.getName();
                //通过"."来分开,并存在一个长度为2数组中,第一个位置存的是文件名,第二个位置存的是后缀名
                String[] split = fileName.split("\\.");
                if(split.length == 2){
                    //获取后缀名
                    String fileEndName = split[1];
                    if(map.containsKey(fileEndName)){
                        //如果已经存在了,那么获取原来的次数
                        Integer count = map.get(fileEndName);
                        //加一
                        count++;
                        //覆盖掉原来的次数
                        map.put(fileEndName,count);
                    }else{
                        //如果不存在,那么给他赋值为1
                        map.put(fileEndName,1);
                    }
                }

            }
            //若不是文件,则递归获取
            getCount(map,file1);
        }
    }

}

19、IO流

  • 字节输出流FileOutputStream

用Windows记事本打开文件,如果可以看得懂那么用字符流,看不懂就用字节流。
使用字节输出流FileOutputStream写入数据

package test;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
public class Test02 {
    public static void main(String[] args) throws IOException {
        //1、创建字节输出流对象
        //注意:如果文件不存在,会自动创建文件,
        //如果文件已存在,那么会清空文件。
        FileOutputStream fileOutputStream = new FileOutputStream("D:\\test.txt");
        //2、写入数据
        //注意:如果输入的是整数,那么是实际写入的是该整数在ASCII表中所对应的符号
        fileOutputStream.write(97);
        //3、关闭链接
        //注意:如果不关闭链接,那么该文件会一直处于打开状态。
        fileOutputStream.close();
    }
}

字节流写数据的三种方式
void write(int b) 一次写一个字节数据
void write(byte[] b) 一次写一个字节数组数据
void write(byte[] b, int off, int len) 一次写一个字节数组的部分数据

package test;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
//void write(int b) 一次写一个字节数据
//        void write(byte[] b) 一次写一个字节数组数据
//        void write(byte[] b, int off, int len) 一次写一个字节数组的部分数据
public class Test02 {
    public static void main(String[] args) throws IOException {

        FileOutputStream fileOutputStream = new FileOutputStream("D:\\test.txt");
        byte [] bytes = {87,97,6,54,64,54};
        //fileOutputStream.write(bytes);
        //从索引1开始,写两个
        fileOutputStream.write(bytes,1,2);
        fileOutputStream.close();
    }
}

getBytes()方法表示将一个字符串转为字节数组。
字节输出流FileOutputStream的第二个参数是续写的开关,默认为false,置为true的话,就可以在文件原有内容的基础上继续写,不再会清除原有内容。
写完数据后,加换行符
windows:\r\n
linux:\n
mac:\r

package test;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class Test02 {
    public static void main(String[] args) throws IOException {
        //第二个参数为续写开关,默认为false,置为true后表示在文件原有内容的基础上继续写
        FileOutputStream fileOutputStream = new FileOutputStream("D:\\test.txt",true);
        fileOutputStream.write(97);
        //换行
        //getBytes()方法表示将一个字符串转为byte数组
        fileOutputStream.write("\r\n".getBytes());
        fileOutputStream.write(98);
        fileOutputStream.write("\r\n".getBytes());
        fileOutputStream.write(99);
        fileOutputStream.write("\r\n".getBytes());
        fileOutputStream.write(100);
        fileOutputStream.write("\r\n".getBytes());
        fileOutputStream.write(101);
        fileOutputStream.close();
    }
}

  • 字节输入流FileInputStream
    读取文件数据
package test;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class Test02 {
    public static void main(String[] args) throws IOException {
        //如果文件存在,则不会报错
        //如果文件不存在,则会报错
        FileInputStream fileInputStream = new FileInputStream("D:\\test.txt");
        //读取数据
        //一次只读取一个数据,也就是返回的那个数据
        //而且返回的是该数据所对应的ASCII的整数值,如果需要字符,那么需要强转
        int read = fileInputStream.read();
        System.out.println(read);
        System.out.println((char)read);
        fileInputStream.close();
    }
}

读取多个文件
当FileInputStream读取到空时,会返回-1,利用这个来判断读取循环是否结束。

package test;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class Test02 {
    public static void main(String[] args) throws IOException {
        FileInputStream fileInputStream = new FileInputStream("D:\\test.txt");
        int number;
        //如果读到-1说明文件中所有的的内容都被读出来了,此时退出循环。
        while((number = fileInputStream.read())!=-1){
            System.out.println((char) number);
        }
    }
}

练习:
在这里插入图片描述
代码:

package test;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class Test02 {
    public static void main(String[] args) throws IOException {
        //创建字节输入流,准备读数据
        FileInputStream fileInputStream = new FileInputStream("D:\\课程文件\\test.docx");
        //创建字节输出流,准备写数据
        FileOutputStream fileOutputStream = new FileOutputStream("D:\\test.docx");
        int number;
        //如果读到-1说明文件中所有的的内容都被读出来了,此时退出循环。
        while((number = fileInputStream.read())!=-1){
            fileOutputStream.write(number);
        }
        fileInputStream.close();
        fileOutputStream.close();
    }
}

提升拷贝速度
一次读取多个字节,通过创建字节数组实现。

package test;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class Test02 {
    public static void main(String[] args) throws IOException {
        //创建字节输入流,准备读数据
        FileInputStream fileInputStream = new FileInputStream("D:\\课程文件\\test.docx");
        //创建字节输出流,准备写数据
        FileOutputStream fileOutputStream = new FileOutputStream("D:\\test.docx");
        byte [] bytes = new byte[1024];
        int len;//一次读取了多少字节
        //如果读到-1说明文件中所有的的内容都被读出来了,此时退出循环。
        while((len = fileInputStream.read(bytes))!=-1){
            fileOutputStream.write(bytes,0,len);
        }
        fileInputStream.close();
        fileOutputStream.close();
    }
}

缓冲流BuffInputStream与BuffOutputStream
其底层实现是创造了一个8192的字节数组,缓冲流只是起字节读取写入的辅佐功能,具体的实现还是字节输入流和字节输出流,所以参数是字节输入流和字节输出流
一次读写一个字节实现:

package test;
import java.io.*;

public class Test02 {
    public static void main(String[] args) throws IOException {
        BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("D:\\课程文件\\test.docx"));
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("D:\\test.txt"));
        int number  ;
        while((number = bufferedInputStream.read())!=-1){
            bufferedOutputStream.write(number);
        }
        bufferedInputStream.close();
        bufferedOutputStream.close();
    }
}

缓冲流结合数组完成文件的复制

package test;
import java.io.*;

public class Test02 {
    public static void main(String[] args) throws IOException {
       BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("D:\\课程文件\\test.docx"));
       BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("D:\\测试.docx"));
       byte [] bytes = new byte[1024];
       int len;
       while((len = bufferedInputStream.read(bytes)) !=-1){
           bufferedOutputStream.write(bytes,0,len);
       }
       bufferedInputStream.close();
       bufferedOutputStream.close();
    }
}

  • 字符输出流FileWriter
    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) 写一个字符串的一部分
package test;
import java.io.*;
public class Test02 {
    public static void main(String[] args) throws IOException {
       FileWriter fileWriter = new FileWriter("D:\\test.txt");
       fileWriter.write(97);
       fileWriter.write(98);
       fileWriter.write(99);
       fileWriter.close();
    }
}

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

  • 字符输入流FileReader
    一次读取一个字符
package test;
import java.io.*;
public class Test02 {
    public static void main(String[] args) throws IOException {
       FileReader fileReader = new FileReader("D:\\test.txt");
       int num;
       while((num = fileReader.read())!=-1){
           System.out.println((char) num);
       }
       fileReader.close();
    }
}

一次读取多个字符
read()方法还是一次读取多个字符,并将这些字符存入chars字符数组中,并返回这一次存了多少个字符

package test;
import java.io.*;
public class Test02 {
    public static void main(String[] args) throws IOException {
       FileReader fileReader = new FileReader("D:\\test.txt");
       char [] chars = new char[1024];
       int len;
       while((len = fileReader.read(chars))!=-1){
           System.out.println(new String(chars,0,len));
       }
       fileReader.close();
    }
}

练习:
在这里插入图片描述

package test;

import java.io.*;
import java.util.Scanner;

public class Test02 {
    public static void main(String[] args) throws IOException {
        FileWriter fileWriter = new FileWriter("D:\\test.txt");
        Scanner input = new Scanner(System.in);
        System.out.println("请输入用户名:");
        String username = input.next();
        System.out.println("请输入密码:");
        String password = input.next();
        fileWriter.write(username);
        fileWriter.write("\r\n".toCharArray());
        fileWriter.write(password);
        fileWriter.close();
    }
}

字符缓冲流BufferedReader与BufferedWriter
字符缓冲输入流与数组的结合

package test;

import java.io.*;
import java.util.Scanner;

public class Test02 {
    public static void main(String[] args) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new FileReader("D:\\test.txt"));
        char [] chars = new char[1024];
        int len;
        while((len = bufferedReader.read(chars))!=-1){
            System.out.println(new String(chars,0,len));
        }
        bufferedReader.close();
    }
}

特有功能:
BufferedWriter.newLine():可以实现跨平台换行
BufferedReader.readLine():一次读取一行,如果下一行没有数据则返回null,不在返回-1,写for循环时-1要换成null。
练习:
在这里插入图片描述在这里插入图片描述

package test;

import java.io.*;
import java.util.Arrays;
import java.util.Scanner;
public class Test02 {
    public static void main(String[] args) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new FileReader("D:\\test.txt"));
        BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("D:\\sort.txt"));
        //读取文件的第一行
        String s = bufferedReader.readLine();
        //通过空格分开并存在字符串数组当中
        String[] s1 = s.split(" ");
        //需要借助Arrays.sort()方法排序,所以需要一个int数组
        int [] arr = new int[s1.length];
        for (int i = 0; i < s1.length; i++) {
            arr[i] = Integer.valueOf(s1[i]);
        }
        //排序
        Arrays.sort(arr);
        //循环写入
        for (int i : arr) {
            bufferedWriter.write(String.valueOf(arr[i]));
        }
        bufferedReader.close();
        bufferedWriter.close();

    }
}

20、特殊操作流

转换流InputStreamReader原OutputStreamWriter
在这里插入图片描述
对象操作流ObjectInputStream与ObjectOutputStream
ObjectOutputStream对象序列化(将JavaBean类对象写入文件)
想要实现对象序列化,定义的JavBean类必须实现serializable这个接口,该接口无任何方法,其意义为标志性意义;表示该类可以被序列化了。
在这里插入图片描述

package test;

import entity.Actor;
import entity.Student;

import java.io.*;
import java.util.Arrays;
import java.util.Scanner;
public class Test02 {
    public static void main(String[] args) throws IOException {
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:\\test1.txt"));
        Student student = new Student("小明",12);
        objectOutputStream.writeObject(student);
        objectOutputStream.close();

    }
}

ObjectInputSteam反序列化(从文件中读出JavaBean类对象)

package test;

import entity.Actor;
import entity.Student;

import java.io.*;
import java.util.Arrays;
import java.util.Scanner;
public class Test02 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:\\test1.txt"));
        Student stu = (Student) objectInputStream.readObject();
        System.out.println(stu);
        objectInputStream.close();

    }
}

用对象序列化流序列化了一个对象后,假如我们修改了对象所属的 Javabean 类,读取数据会不会出问题呢?
答:会出问题,会抛出 InvalidClassException 异常
如果出问题了,如何解决呢?
答:给对象所属的类加一个 serialVersionUID
例如:private static final long serialVersionUID = 42L;
如果一个对象中的某个成员变量的值不想被序列化,又该如何实现呢?
答:给该成员变量加 transient 关键字修饰,该关键字标记的成员变量不参与序列化过程
练习:
在这里插入图片描述

package test;

import entity.Actor;
import entity.Student;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
public class Test02 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:\\test.txt"));
        ArrayList<Student> arrayList = new ArrayList<>(List.of(new Student("张三",23),new Student("李四",22),new Student("王五",35),new Student("赵六",54)));
        for (Student student : arrayList) {
            objectOutputStream.writeObject(student);
        }
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:\\test.txt"));
        for (int i = 0; i < arrayList.size(); i++) {
            System.out.println(objectInputStream.readObject());
        }
        objectInputStream.close();
        objectOutputStream.close();
    }
}

  • Properties
    Properties 概述:
    是一个 Map 体系的集合类
    Properties 中有跟 IO 相关的方法
    只存字符串
    Properties的基本使用:
package test;
import java.io.*;
import java.util.*;
public class Test02 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Properties properties = new Properties();
        //增
        properties.put("1","小明");
        properties.put("2","小刚");
        properties.put("3","小红");
        properties.put("4","小智");
        System.out.println(properties);
        //删
        properties.remove("2");
        System.out.println(properties);
        //改
        //如果该键值对不存在则存入,存在则覆盖
        properties.put("1","小胖");
        System.out.println(properties);
        //查
        Object o2 = properties.get("1");
        System.out.println(o2);
        //遍历
        //第一种方式
        Set<Object> keySet = properties.keySet();
        for (Object o : keySet) {
            Object o1 = properties.get(o);
            System.out.println("键:"+o+"值:"+o1);
        }
        System.out.println("--------------------------------");
        //第二种方式
        Set<Map.Entry<Object, Object>> entries = properties.entrySet();
        for (Map.Entry<Object, Object> entry : entries) {
            Object key = entry.getKey();
            Object value = entry.getValue();
            System.out.println("键:"+key+"值:"+value);
        }

    }
}

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

package test;

import java.io.*;
import java.util.*;

public class Test02 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Properties properties = new Properties();
//        Object setProperty(String key, String value) 设置集合的键和值,都是 String 类型,底层调用 Hashtable
//        方法 put
        properties.setProperty("1", "小明");
        properties.setProperty("2", "小刚");
        properties.setProperty("3", "小红");
        System.out.println(properties);
//        String getProperty(String key) 使用此属性列表中指定的键搜索属性
        String s = properties.getProperty("1");
        System.out.println(s);
//        Set<String> stringPropertyNames() 从该属性列表中返回一个不可修改的键集,其中键及其对应的
//                值是字符串
        Set<String> stringSet = properties.stringPropertyNames();
        for (String s1 : stringSet) {
            Object o = properties.get(s1);
            System.out.println("键:"+s1+"值:"+o);
        }

    }
}

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) 方法的格式写入输出字符流

package test;

import java.io.*;
import java.util.*;

public class Test02 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Properties properties = new Properties();
        FileReader fileReader = new FileReader("D:\\test1.properties");
        //执行完这条语句,那么文件中的键值对就存在了properties中
        properties.load(fileReader);
        fileReader.close();
        System.out.println(properties);
        FileWriter fileWriter = new FileWriter("D:\\test2.properties");
        properties.setProperty("1","小明");
        properties.setProperty("2","小红");
        properties.setProperty("3","小刚");
        properties.store(fileWriter,"注释");
        fileWriter.close();

    }
}

21、多线程

多线程的实现方案:
第一种方式:
在这里插入图片描述
Mythread:

package test;

public class MyThread extends Thread{
    @Override
    public void run() {
        //这里的代码就是线程开启后执行的代码
        for (int i = 0; i < 10; i++) {
            System.out.println("线程开启了"+i);
        }
    }
}

测试:

package test;

import java.io.*;
import java.util.*;

public class Test02 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //创建第一条线程
        MyThread myThread1 = new MyThread();
        //创建第一条线程
        MyThread myThread2 = new MyThread();
        //开启第一条线程
        myThread1.start();
        //开启第二条线程
        myThread2.start();

    }
}

第二种方式:
在这里插入图片描述
MyRunnable

package test;

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("第二种线程创建方式"+i);
        }
    }
}

测试:

package test;

import java.io.*;
import java.util.*;

public class Test02 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //创建一个参数对象
        MyRunnable myRunnable = new MyRunnable();
        //创建一个线程,并把参数传给这个线程,在线程启动之后,执行的是参数里面的run方法
        Thread thread = new Thread(myRunnable);
        //启动线程
        thread.start();

    }
}

两个小问题:
为什么要重写 run() 方法?
答:因为 run() 是用来封装被线程执行的代码
run() 方法和 start() 方法的区别?
答:run() :封装线程执行的代码,直接调用,相当于普通方法的调用,并没有开启线程。
start() :启动线程;然后由 JVM 调用此线程的 run() 方法
第三种方式:
在这里插入图片描述MyCallable:

package test;

import java.util.concurrent.Callable;
                        //返回值的类型是什么,这里的泛型就是什么类型
public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        for (int i = 0; i < 10; i++) {
            System.out.println("向女孩表白"+i);
        }
        return "答应";
    }
}

测试:

package test;

import java.io.*;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Test02 {
    public static void main(String[] args) throws IOException, ClassNotFoundException, ExecutionException, InterruptedException {
        MyCallable myCallable = new MyCallable();
        //线程的返回值类型是什么,这里的泛型就是什么类型
        FutureTask<String> futureTask = new FutureTask<>(myCallable);
        Thread thread = new Thread(futureTask);
        thread.start();
        //通过get()方法获取返回值
        String s = futureTask.get();
        System.out.println(s);
    }
}

  • 类加载器

在这里插入图片描述

package test;

import java.io.*;
import java.util.*;

public class Test02 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
        //利用类加载器加载一个指定的文件
        //参数:文件路径
        //返回值:字节流
        InputStream resourceAsStream = classLoader.getResourceAsStream("test1.properties");
        Properties properties = new Properties();
        properties.load(resourceAsStream);
        System.out.println(properties);
        resourceAsStream.close();
    }
}

线程类常见的方法:
获取和设置线程名称
获取线程的名字
String getName() :返回此线程的名称
Thread 类中设置线程的名字
void setName(String name) :将此线程的名称更改为等于参数 name
通过构造方法也可以设置线程名称
Mythread:

package test;

public class MyThread extends Thread{
    //设置构造方法可以设置线程名称
    public MyThread() {
    }
    public MyThread(String name) {
        super(name);
    }
    @Override
    public void run() {
        //这里的代码就是线程开启后执行的代码
        for (int i = 0; i < 10; i++) {
            //getName()获取线程名称
            System.out.println(getName()+"线程开启了"+i);
        }
    }
}

测试:

package test;

import java.io.*;
import java.util.*;

public class Test02 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        MyThread myThread1 = new MyThread("小明");
        MyThread myThread2 = new MyThread("小刚");
        //设置线程名称
//        myThread1.setName("小明");
//        myThread2.setName("小刚");
        myThread1.start();
        myThread2.start();
    }
}

获得当前线程的对象
public static Thread currentThread() :返回对当前正在执行的线程对象的引用
线程休眠
public static void sleep(long time) :让线程休眠指定的时间,单位为毫秒。
MyRunnable:

package test;

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"第二种线程创建方式"+i);
        }
    }
}

测试:

package test;

import java.io.*;
import java.util.*;

public class Test02 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        thread.start();
    }
}

线程的优先级
public final void setPriority(int newPriority) 设置线程的优先级
public final int getPriority() 获取线程的优先级
MyCallable:

package test;

import java.util.concurrent.Callable;
                        //返回值的类型是什么,这里的泛型就是什么类型
public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+i);
        }
        return "线程执行完毕了";
    }
}

测试

package test;

import java.io.*;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Test02 {
    public static void main(String[] args) throws IOException, ClassNotFoundException, ExecutionException, InterruptedException {
        MyCallable myCallable = new MyCallable();
        //线程的返回值类型是什么,这里的泛型就是什么类型
        FutureTask<String> futureTask1 = new FutureTask<>(myCallable);
        //第一个线程
        Thread thread1 = new Thread(futureTask1);
        thread1.setName("飞机");
        //设置优先级,优先级范围为1-10,默认为5
        thread1.setPriority(10);
        thread1.start();
        //第二个线程
        FutureTask<String> futureTask2 = new FutureTask<>(myCallable);
        Thread thread2 = new Thread(futureTask2);
        thread2.setName("坦克");
        thread2.setPriority(1);
        thread2.start();
    }
}

后台线程 / 守护线程
public final void setDaemon(boolean on) :设置为守护线程
MyThread1:

package test;

public class MyThread1 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+i);
        }
    }
}

MyThread2:

package test;

public class MyThread2 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+i);
        }
    }
}

测试,将线程二设置为守护线程,如果主要线程结束了,那么守护线程也就没有执行的必要了。

package test;

import java.io.*;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Test02 {
    public static void main(String[] args) throws IOException, ClassNotFoundException, ExecutionException, InterruptedException {
        MyThread1 thread1 = new MyThread1();
        MyThread2 thread2 = new MyThread2();
        thread1.setName("主要");
        thread2.setName("次要");
        //设置守护线程,如果主要线程执行完毕了,那么守护线程也就没有继续执行下去的必要了。
        thread2.setDaemon(true);
        thread1.start();
        thread2.start();
    }
}

线程安全问题:
练习1:
在这里插入图片描述代码:
Ticket类

package entity;

public class Ticket implements Runnable{
    private int ticketCount = 100;
    @Override
    public void run() {
        while(true){
            if(ticketCount == 0){
                System.out.println(Thread.currentThread().getName()+"卖完了");
                break;
            }else{
                ticketCount--;
                System.out.println(Thread.currentThread().getName()+"在卖票,还剩"+ticketCount+"张票");
            }
        }
    }
}

测试类:

package test;

import entity.Ticket;

import java.io.*;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Test02 {
    public static void main(String[] args) throws IOException, ClassNotFoundException, ExecutionException, InterruptedException {
        Ticket ticket = new Ticket();
        Thread thread1 = new Thread(ticket);
        Thread thread2 = new Thread(ticket);
        Thread thread3 = new Thread(ticket);
        thread1.setName("窗口一");
        thread2.setName("窗口二");
        thread3.setName("窗口三");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

同步代码块
主要用于解决线程安全问题;
锁多条语句操作共享数据,可以使用同步代码块实现
格式:
synchronized( 任意对象 ) {
多条语句操作共享数据的代码
}
默认情况是打开的,只要有一个线程进去执行代码了,锁就会关闭
当线程执行完出来了,锁才会自动打开
同步的好处和弊端
好处:解决了多线程的数据安全问题
弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
Ticket:

package entity;

public class Ticket implements Runnable{
    private int ticketCount = 100;
    private Object object = new Object();
    @Override
    public void run() {
        while(true){
            synchronized (object){//多个线程要用同一把锁
                if(ticketCount <= 0){
                    System.out.println(Thread.currentThread().getName()+"卖完了");
                    break;
                }else{
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticketCount--;
                    System.out.println(Thread.currentThread().getName()+"在卖票,还剩"+ticketCount+"张票");
                }
            }
        }
    }
}

测试:

package test;

import entity.Ticket;
public class Test02 {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        Thread thread1 = new Thread(ticket);
        Thread thread2 = new Thread(ticket);
        Thread thread3 = new Thread(ticket);
        thread1.setName("窗口一");
        thread2.setName("窗口二");
        thread3.setName("窗口三");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

synchronized ()里面的对象要求是唯一的
MyThread:

package test;

public class MyThread extends Thread {
    private static int ticketCount = 100;
    private static Object object = new Object();
    @Override
    public void run() {
        while (true) {
            synchronized (object) {
                if (ticketCount <= 0) {
                    System.out.println(Thread.currentThread().getName() + "卖完了");
                    break;
                } else {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticketCount--;
                    System.out.println(Thread.currentThread().getName() + "在卖票,还剩" + ticketCount + "张票");
                }
            }
        }

    }
}

测试:

package test;

import entity.Ticket;
public class Test02 {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        t1.setName("窗口一");
        t2.setName("窗口二");
        t1.start();
        t2.start();
    }
}

同步方法
同步方法:就是把 synchronized 关键字加到方法上
格式:
修饰符 synchronized 返回值类型 方法名 ( 方法参数 ) { }
同步代码块和同步方法的区别 :
同步代码块可以锁住指定代码 , 同步方法是锁住方法中所有代码
同步代码块可以指定锁对象 , 同步方法不能指定锁对象
同步方法的锁对象是什么呢 ?
this
MyRunnable:

package test;

public class MyRunnable implements Runnable{
    private int ticketCount = 100;
    @Override
    public void run() {
        while(true){
            if("窗口一".equals(Thread.currentThread().getName())){
                //同步方法
                boolean b = synchronizedMethod();
                if(b){
                    break;
                }
            }
            if("窗口二".equals(Thread.currentThread().getName())){
                //同步代码块
                synchronized (this){
                    if(ticketCount == 0){
                        break;
                    }else{
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        ticketCount--;
                        System.out.println(Thread.currentThread().getName() + "在卖票,还剩" + ticketCount + "张票");
                    }
                }
            }
        }

    }

    private synchronized boolean synchronizedMethod() {
        if(ticketCount == 0){
            return true;
        }else{
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ticketCount--;
            System.out.println(Thread.currentThread().getName() + "在卖票,还剩" + ticketCount + "张票");
            return false;
        }
    }
}

测试:

package test;

public class Test01 {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread t1 = new Thread(myRunnable);
        Thread t2 = new Thread(myRunnable);
        t1.setName("窗口一");
        t2.setName("窗口二");
        t1.start();
        t2.start();
    }
}

同步方法
同步静态方法:就是把 synchronized 关键字加到静态方法上
格式:
修饰符 static synchronized 返回值类型 方法名 ( 方法参数 ) { }
同步静态方法的锁对象是什么呢 ?
类名 .class
MyRunnable:

package test;

public class MyRunnable implements Runnable{
    private static int ticketCount = 100;
    @Override
    public void run() {
        while(true){
            if("窗口一".equals(Thread.currentThread().getName())){
                //同步方法
                boolean b = synchronizedMethod();
                if(b){
                    break;
                }
            }
            if("窗口二".equals(Thread.currentThread().getName())){
                //同步代码块
                synchronized (MyRunnable.class){
                    if(ticketCount == 0){
                        break;
                    }else{
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        ticketCount--;
                        System.out.println(Thread.currentThread().getName() + "在卖票,还剩" + ticketCount + "张票");
                    }
                }
            }
        }

    }

    private static synchronized boolean synchronizedMethod() {
        if(ticketCount == 0){
            return true;
        }else{
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ticketCount--;
            System.out.println(Thread.currentThread().getName() + "在卖票,还剩" + ticketCount + "张票");
            return false;
        }
    }
}

测试:

package test;

public class Test01 {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread t1 = new Thread(myRunnable);
        Thread t2 = new Thread(myRunnable);
        t1.setName("窗口一");
        t2.setName("窗口二");
        t1.start();
        t2.start();
    }
}

Lock 锁
虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,
为了更清晰的表达如何加锁和释放锁, JDK5 以后提供了一个新的锁对象 Lock
Lock 实现提供比使用 synchronized 方法和语句可以获得更广泛的锁定操作
Lock 中提供了获得锁和释放锁的方法
void lock() :获得锁
void unlock() :释放锁
Lock 是接口不能直接实例化,这里采用它的实现类 ReentrantLock 来实例化
ReentrantLock 的构造方法
ReentrantLock() :创建一个 ReentrantLock 的实例
Ticket:

package entity;

import java.util.concurrent.locks.ReentrantLock;

public class Ticket implements Runnable{
    private static int ticketCount = 100;
    private static Object object = new Object();
    private ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        while(true){
            //synchronized (object){//多个线程要用同一把锁
            try {
                lock.lock();
                if(ticketCount <= 0){
                    System.out.println(Thread.currentThread().getName()+"卖完了");
                    break;
                }else{
                    Thread.sleep(100);
                    ticketCount--;
                    System.out.println(Thread.currentThread().getName()+"在卖票,还剩"+ticketCount+"张票");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
            //}
        }
    }
}

测试:

package test;

import entity.Ticket;

public class Test01 {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        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();
    }
}

死锁 ( 需要构思回程关闭机关 )
线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。
例子:

package test;

import entity.Ticket;

public class Test01 {
    public static void main(String[] args) {
        Object object1 = new Object();
        Object object2 = new Object();
        new Thread(() -> {
            while(true){
                synchronized (object1){
                    //进程一
                    synchronized (object2){
                        System.out.println("小明");
                    }
                }
            }
        }).start();
        new Thread(()->{
            while(true){
                synchronized (object2){
                    //进程二
                    synchronized (object1){
                        System.out.println("小智");
                    }
                }
            }
        }).start();
    }
}

生产者与消费者:
等待和唤醒的方法
为了体现生产和消费过程中的等待和唤醒, Java 就提供了几个方法供我们使用,这几个方法在 Object 类中
Object 类的等待和唤醒方法:
void wait() 导致当前线程等待,直到另一个线程调用该对象的 notify() 方法或 notifyAll() 方法
void notify() 唤醒正在等待对象监视器的单个线程
void notifyAll() 唤醒正在等待对象监视器的所有线程
例子:
Desk:

package test;

public class Desk {
    //true 代表有汉堡包,false代表没有汉堡包
    public static boolean flag = false;
    //代表汉堡包的数量,生产者做10,消费者吃10后就停止
    public static int count = 10;
    //锁对象
    public static final Object lock = new Object();
}

Foodie:

package test;

//消费者步骤:
//        1 ,判断桌子上是否有汉堡包。
//        2 ,如果没有就等待。
//        3 ,如果有就开吃
//        4 ,吃完之后,桌子上的汉堡包就没有了
//        叫醒等待的生产者继续生产
//        汉堡包的总数量减一
public class Foodie extends Thread {
    @Override
    public void run() {
        //因为消费者可能会吃多次,所以用while循环
        while (true) {
            synchronized (Desk.lock) {
                //把十个汉堡包吃完后就退出
                if (Desk.count == 0) {
                    break;
                } else {
                    //判断桌子上是否有汉堡包
                    if (Desk.flag) {
                        //有,开吃
                        System.out.println("消费者开始吃汉堡包");
                        //把汉堡包吃完了
                        Desk.flag = false;
                        //汉堡包数量减一
                        Desk.count--;
                        //唤醒生产者去做汉堡包
                        //用锁对象去调用唤醒和等待方法
                        Desk.lock.notifyAll();
                    } else {
                        //桌子上没有汉堡包,等待
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
}

Cooker:

package test;
//生产者步骤:
//        1 ,判断桌子上是否有汉堡包
//        如果有就等待,如果没有才生产。
//        2 ,把汉堡包放在桌子上。
//        3 ,叫醒等待的消费者开吃。
public 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) {
                            e.printStackTrace();
                        }
                    }else{
                        //没有,去做汉堡包
                        System.out.println("生产者在做汉堡包");
                        //把汉堡包放在桌子上
                        Desk.flag = true;
                        //唤醒消费者来吃
                        Desk.lock.notifyAll();
                    }
                }
            }
        }
    }
}

22、网络编程

网络编程三要素
IP 地址
设备在网络中的地址,是唯一的标识。
端口
应用程序在设备中唯一的标识。
协议
数据在网络中传输的规则,常见的协议有 UDP 协议和 TCP 协议。
常用命令:
ipconfig :查看本机 IP 地址
ping IP 地址:检查网络是否连通
特殊 IP 地址:
127.0.0.1 :是回送地址也称本地回环地址,可以代表本机的 IP 地址,一般用来测试使用

InetAddress 的使用
为了方便我们对 IP 地址的获取和操作, Java 提供了一个类 InetAddress 供我们使用
InetAddress :此类表示 Internet 协议( IP )地址
static InetAddress getByName(String host) 确定主机名称的 IP 地址。主机名称可以是机器名称,也可以是 IP 地

String getHostName() 获取此 IP 地址的主机名
String getHostAddress() 返回文本显示中的 IP 地址字符串

package test;

import entity.Ticket;

import java.net.InetAddress;
import java.net.UnknownHostException;

public class Test01 {
    public static void main(String[] args) throws UnknownHostException {
        //通过设备名(主机名、电脑名)来获取主机IP地址
        InetAddress address = InetAddress.getByName("DESKTOP-3JG2GQ1");
        //获取主机名
        String hostName = address.getHostName();
        System.out.println(hostName);
        //获取IP地址
        String ip = address.getHostAddress();
        System.out.println(ip);
    }
}

  • UDP通信程序
    UDP发送数据
    在这里插入图片描述
package test;

import entity.Ticket;

import java.io.IOException;
import java.net.*;

public class Test01 {
    public static void main(String[] args) throws IOException {
        //1、找码头
        DatagramSocket datagramSocket = new DatagramSocket();
        //2、打包礼物
        //DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)
        String s = "给村长的礼物";
        int length = s.length();
        InetAddress address = InetAddress.getByName("127.0.0.1");
        int port = 10000;
        DatagramPacket datagramPacket = new DatagramPacket(s.getBytes(),length,address,port);
        //3、发送礼物
        datagramSocket.send(datagramPacket);
        //4、付钱走羊
        datagramSocket.close();
    }
}

UDP接收数据
在这里插入图片描述

package test;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class Test02 {
    //注意点
        //1、先运行接受端,在运行发送端
        //2、如果接收端没有接收到数据,那么接收端就是死等,停留在receive方法这里
        //3、在接受数据的时候,需要调用一下DatagramSocket.getLength()方法,表示获取到了多少字节,这样控制台就不需要打印过多的空格
    public static void main(String[] args) throws IOException {
        //1、找码头        ---参数10000表示从端口号10000接收数据,如果不写参数则从随机端口号接收数据
        DatagramSocket datagramSocket = new DatagramSocket(10000);
        //2、创建箱子准备接收
        //DatagramPacket(byte[] buf, int length)
        byte[] bytes = new byte[1024];
        DatagramPacket datagramPacket = new DatagramPacket(bytes,bytes.length);
        //3、接收礼物
        datagramSocket.receive(datagramPacket);
        //4、从箱子中拿出礼物
        byte[] data = datagramPacket.getData();
        //表示接收到了多少字节
        int length = datagramPacket.getLength();
        System.out.println(new String(data,0,length));
        //5、拿完走羊
        datagramSocket.close();
    }
}

练习:
在这里插入图片描述
UDPSend:

package test;

import java.io.IOException;
import java.net.*;
import java.util.Scanner;

public class UDPSend {
    public static void main(String[] args) throws IOException {
        DatagramSocket ds = new DatagramSocket();
        Scanner input = new Scanner(System.in);
        while(true){
            String s = input.next();
            byte [] bytes = s.getBytes();
            InetAddress address = InetAddress.getByName("127.0.0.1");
            int port = 10000;
            DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
            ds.send(dp);
            if("886".equals(s)){
                ds.close();
                break;
            }
        }
    }
}

UDPReceive:

package test;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class UDPReceive {
    public static void main(String[] args) throws IOException {
        DatagramSocket ds = new DatagramSocket(10000);
        while(true){
            byte [] bytes = new byte[1024];
            DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
            ds.receive(dp);
            String s = new String(bytes, 0, dp.getLength());
            if("886".equals(s)){
                ds.close();
                break;
            }else if(!s.equals(" ")){
                System.out.println(s);
            }
        }
    }
}

组播:
在这里插入图片描述Server:

package test;

import java.io.IOException;
import java.net.*;

public class Server {
    public static void main(String[] args) throws IOException {
        DatagramSocket ds = new DatagramSocket();
        String s = "hello 组播";
        byte [] bytes = s.getBytes();
        InetAddress address = InetAddress.getByName("224.0.1.0");
        int port = 10000;
        DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
        ds.send(dp);
        ds.close();
    }
}

Client:

package test;

import java.io.IOException;
import java.net.*;

public class Client {
    public static void main(String[] args) throws IOException {
        MulticastSocket ms = new MulticastSocket(10000);
        byte [] bytes = new byte[1024];
        DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
        //把当前计算机绑定一个组播地址
        ms.joinGroup(InetAddress.getByName("224.0.1.0"));
        ms.receive(dp);
        byte[] data = dp.getData();
        int length = dp.getLength();
        System.out.println(new String(data,0,length));
        ms.close();
    }
}

  • TCP协议
    TCP发送数据:
    在这里插入图片描述
    Client:
package test;

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

public class TCPSend {
    public static void main(String[] args) throws IOException {
        //1、创建一个socket对象
        Socket socket = new Socket("127.0.0.1",10000);
        //2、获取OutputStream
        OutputStream outputStream = socket.getOutputStream();
        //3、写数据
        outputStream.write("hello".getBytes());
        //4、关闭连接
        socket.close();
        outputStream.close();
    }
}

TCP接收数据:
在这里插入图片描述Server:

package test;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class TCPServer {
    public static void main(String[] args) throws IOException {
        ServerSocket ss  = new ServerSocket(10001);
        Socket accept = ss.accept();
        InputStream inputStream = accept.getInputStream();
        int b;
        while((b = inputStream.read())!=-1){
            System.out.println((char)b);
        }
        ss.close();
        inputStream.close();
    }
}

练习1:
在这里插入图片描述
Client:

package test;

import java.io.*;
import java.net.Socket;

public class Client {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1",10002);
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write("hello".getBytes());
        //outputStream.close();如果在这里关流,会导致整个socket关闭
        socket.shutdownOutput();//这个只会关闭输出流,并写一个结束标记,对socket没有影响
        //由于读取的是汉字,所以用字节流去读取的话会乱码
        //而socket中又没有字符流的方法,所以需要使用转换流,将字节流转为字符流
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String line;
        while((line = bufferedReader.readLine())!=null){
            System.out.println(line);
        }
//        InputStream inputStream = socket.getInputStream();
//        int b;
//        while((b = inputStream.read())!=-1){
//            System.out.println((char) b);
//        }
        outputStream.close();
        bufferedReader.close();
        //inputStream.close();
        socket.close();
    }
}

Server:

package test;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(10002);
        Socket accept = ss.accept();
        InputStream inputStream = accept.getInputStream();
        int b;
        while((b = inputStream.read())!=-1){
            System.out.println((char) b);
        }

//        OutputStream outputStream = accept.getOutputStream();
//        outputStream.write("你是谁啊?".getBytes());
        BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
        bufferedWriter.write("你是谁啊?");
        bufferedWriter.newLine();
        bufferedWriter.flush();
        bufferedWriter.close();
        inputStream.close();
        //outputStream.close();
        accept.close();
        ss.close();
    }
}

练习2:
在这里插入图片描述Client:

package test;

import java.io.*;
import java.net.Socket;

public class Client {
    public static void main(String[] args) throws IOException {
        //创建本地连接,端口号为10003
        Socket socket = new Socket("127.0.0.1",10003);
        //利用字节输入流,读取文件内容,本地流
        BufferedInputStream bi = new BufferedInputStream(new FileInputStream("D:\\test.txt"));
        //获取服务器的输出流,将文件内容进行输出,网络流
        OutputStream os = socket.getOutputStream();
        int b;
        while((b = bi.read())!=-1){
            os.write(b);//边读取本地文件,边通过网络流向服务器端去写数据
        }
        //防止服务器在read()方法阻塞,关闭输出流,不影响socket,并且给服务器一个结束标记,表示数据已传输完毕。
        socket.shutdownOutput();
        //由于去读汉字,所以用字符缓冲输入流,速度更快些,网络流
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String line;
        while((line = br.readLine())!=null){
            System.out.println(line);
        }
        //关闭
        br.close();
        os.close();
        bi.close();
        socket.close();
    }
}

Server:

package test;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    public static void main(String[] args) throws IOException {
        //创建连接,并准备从端口号10003获取数据
        ServerSocket ss = new ServerSocket(10003);
        Socket accept = ss.accept();
        //将接收到的数据写入该文件
        //这里使用的是字节缓冲输出流,效率更高,本地流
        BufferedOutputStream bo = new BufferedOutputStream(new FileOutputStream("D:\\testCopy.txt"));
        //获取客户端的输入流,网络流
        InputStream inputStream = accept.getInputStream();
        int b;
        while((b = inputStream.read())!=-1){
            //写数据
            bo.write(b);
        }
        //由于写的是汉字,所以采用字符流,要向客户端去写,所以获取的是accept的输出流,网络流
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
        String s = "上传成功!";
        bw.write(s);
        bw.flush();
        //关闭
        bw.close();
        inputStream.close();
        bo.close();
        accept.close();
        ss.close();


    }
}

服务端优化
第一个弊端:
服务器只能处理一个客户端请求,接收完一个图片之后,服务器就关闭了。
改进方式:
循环

package test;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    public static void main(String[] args) throws IOException {
        //创建连接,并准备从端口号10003获取数据
        ServerSocket ss = new ServerSocket(10003);
        while (true) {
            Socket accept = ss.accept();
            //将接收到的数据写入该文件
            //这里使用的是字节缓冲输出流,效率更高,本地流
            BufferedOutputStream bo = new BufferedOutputStream(new FileOutputStream("D:\\testCopy.txt"));
            //获取客户端的输入流,网络流
            InputStream inputStream = accept.getInputStream();
            int b;
            while((b = inputStream.read())!=-1){
                //写数据
                bo.write(b);
            }
            //由于写的是汉字,所以采用字符流,要向客户端去写,所以获取的是accept的输出流,网络流
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
            String s = "上传成功!";
            bw.write(s);
            bw.flush();
            //关闭
            bw.close();
            inputStream.close();
            bo.close();
            accept.close();
        }
        //ss.close();
    }
}

第二个弊端:
第二次上传文件的时候,会把第一次的文件给覆盖。
改进方式:
UUID. randomUUID() 方法生成随机的文件名

package test;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.UUID;

public class Server {
    public static void main(String[] args) throws IOException {
        //创建连接,并准备从端口号10003获取数据
        ServerSocket ss = new ServerSocket(10003);
        while (true) {
            Socket accept = ss.accept();
            //将接收到的数据写入该文件
            //这里使用的是字节缓冲输出流,效率更高,本地流
            BufferedOutputStream bo = new BufferedOutputStream(new FileOutputStream("D:\\"+ UUID.randomUUID().toString()+".txt"));
            //获取客户端的输入流,网络流
            InputStream inputStream = accept.getInputStream();
            int b;
            while((b = inputStream.read())!=-1){
                //写数据
                bo.write(b);
            }
            //由于写的是汉字,所以采用字符流,要向客户端去写,所以获取的是accept的输出流,网络流
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
            String s = "上传成功!";
            bw.write(s);
            bw.flush();
            //关闭
            bw.close();
            inputStream.close();
            bo.close();
            accept.close();
        }
        //ss.close();
    }
}

加入循环以后又引发了一个问题:
使用循环虽然可以让服务器处理多个客户端请求。但是还是无法同时跟多个客户端进行通信。
改进方式:
开启多线程处理
Client:

package test;

import java.io.*;
import java.net.Socket;

public class Client {
    public static void main(String[] args) throws IOException {
        //创建本地连接,端口号为10003
        Socket socket = new Socket("127.0.0.1",10003);
        //利用字节输入流,读取文件内容,本地流
        BufferedInputStream bi = new BufferedInputStream(new FileInputStream("D:\\test.txt"));
        //获取服务器的输出流,将文件内容进行输出,网络流
        OutputStream os = socket.getOutputStream();
        int b;
        while((b = bi.read())!=-1){
            os.write(b);//边读取本地文件,边通过网络流向服务器端去写数据
        }
        //防止服务器在read()方法阻塞,关闭输出流,不影响socket,并且给服务器一个结束标记,表示数据已传输完毕。
        socket.shutdownOutput();
        //由于去读汉字,所以用字符缓冲输入流,速度更快些,网络流
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String line;
        while((line = br.readLine())!=null){
            System.out.println(line);
        }
        //关闭
        br.close();
        os.close();
        bi.close();
        socket.close();
    }
}

ThreadSocket:

package test;

import java.io.*;
import java.net.Socket;
import java.util.UUID;
public class ThreadSocket implements Runnable{
    private Socket acceptSocket;
    public ThreadSocket(Socket accept){
        this.acceptSocket = accept;
    }
    @Override
    public void run() {
        try {
            //将接收到的数据写入该文件
            //这里使用的是字节缓冲输出流,效率更高,本地流
            BufferedOutputStream bo = new BufferedOutputStream(new FileOutputStream("D:\\"+ UUID.randomUUID().toString()+".txt"));
            //获取客户端的输入流,网络流
            InputStream inputStream = acceptSocket.getInputStream();
            int b;
            while((b = inputStream.read())!=-1){
                //写数据
                bo.write(b);
            }
            //由于写的是汉字,所以采用字符流,要向客户端去写,所以获取的是accept的输出流,网络流
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(acceptSocket.getOutputStream()));
            String s = "上传成功!";
            bw.write(s);
            bw.flush();
            //关闭
            bw.close();
            inputStream.close();
            bo.close();

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(acceptSocket!=null){
                try {
                    acceptSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
    }
}

Server:

package test;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.UUID;

public class Server {
    public static void main(String[] args) throws IOException {
        //创建连接,并准备从端口号10003获取数据
        ServerSocket ss = new ServerSocket(10003);
        while (true) {
            Socket accept = ss.accept();
            ThreadSocket ts = new ThreadSocket(accept);
            new Thread(ts).start();
        }
        //ss.close();
    }
}

加入多线程以后又引发了一个问题:
使用多线程虽然可以让服务器同时处理多个客户端请求。但是资源消耗太大。
改进方式:
加入线程池
Server:

package test;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.UUID;
import java.util.concurrent.*;

public class Server {
    public static void main(String[] args) throws IOException {
        //创建连接,并准备从端口号10003获取数据
        ServerSocket ss = new ServerSocket(10003);
        //第一个参数表示核心线程的数量
        //第二个参数表示线程池的总数量
        //第三个参数和第四个参数表示,经过多长时间临时线程没有用就销毁,本次是60S
        //第五个参数表示阻塞队列,表示线程池总数量用完之后,允许多少线程在排队
        //第六个参数表示创建线程的方式,这里采用默认的创建方式
        //第七个参数表示拒绝策略,意思是当线程池总数量用完了,而且阻塞队列也排满了,所采用的策略,这里是默认的策略。
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                3,//核心线程数量
                10,//线程池的总数量
                60,//空闲时间
                TimeUnit.SECONDS,//空闲时间的单位
                new ArrayBlockingQueue<>(5),//阻塞队列
                Executors.defaultThreadFactory(),//创建线程的方式
                new ThreadPoolExecutor.AbortPolicy()//任务拒绝策略
        );
        while (true) {
            Socket accept = ss.accept();
            ThreadSocket ts = new ThreadSocket(accept);
            //new Thread(ts).start();
            pool.submit(ts);
        }
        //ss.close();
    }
}

Client:

package test;

import java.io.*;
import java.net.Socket;

public class Client {
    public static void main(String[] args) throws IOException {
        //创建本地连接,端口号为10003
        Socket socket = new Socket("127.0.0.1",10003);
        //利用字节输入流,读取文件内容,本地流
        BufferedInputStream bi = new BufferedInputStream(new FileInputStream("D:\\test.txt"));
        //获取服务器的输出流,将文件内容进行输出,网络流
        OutputStream os = socket.getOutputStream();
        int b;
        while((b = bi.read())!=-1){
            os.write(b);//边读取本地文件,边通过网络流向服务器端去写数据
        }
        //防止服务器在read()方法阻塞,关闭输出流,不影响socket,并且给服务器一个结束标记,表示数据已传输完毕。
        socket.shutdownOutput();
        //由于去读汉字,所以用字符缓冲输入流,速度更快些,网络流
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String line;
        while((line = br.readLine())!=null){
            System.out.println(line);
        }
        //关闭
        br.close();
        os.close();
        bi.close();
        socket.close();
    }
}

ThreadSocket:

package test;

import java.io.*;
import java.net.Socket;
import java.util.UUID;
public class ThreadSocket implements Runnable{
    private Socket acceptSocket;
    public ThreadSocket(Socket accept){
        this.acceptSocket = accept;
    }
    @Override
    public void run() {
        try {
            //将接收到的数据写入该文件
            //这里使用的是字节缓冲输出流,效率更高,本地流
            BufferedOutputStream bo = new BufferedOutputStream(new FileOutputStream("D:\\"+ UUID.randomUUID().toString()+".txt"));
            //获取客户端的输入流,网络流
            InputStream inputStream = acceptSocket.getInputStream();
            int b;
            while((b = inputStream.read())!=-1){
                //写数据
                bo.write(b);
            }
            //由于写的是汉字,所以采用字符流,要向客户端去写,所以获取的是accept的输出流,网络流
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(acceptSocket.getOutputStream()));
            String s = "上传成功!";
            bw.write(s);
            bw.flush();
            //关闭
            bw.close();
            inputStream.close();
            bo.close();

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(acceptSocket!=null){
                try {
                    acceptSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
    }
}

23、反射

获取 Class 类的对象
Class.forName(“ 全类名” );
类名 .class
对象 .getClass();

package test;

import entity.Teacher;

public class Test02 {
    public static void main(String[] args) throws ClassNotFoundException {
        //第一种方式
        // 调用Class.forName()方法
        //全类名:包名+类名
        Class clazz1 = Class.forName("entity.Teacher");
        System.out.println(clazz1);
        System.out.println("--------------------------------");
        //第二种方式
        //类.class
        Class<Teacher> teacherClass = Teacher.class;
        System.out.println(teacherClass);
        System.out.println("--------------------------------");
        //第三种方式
        //利用对象的getClass()方法
        Teacher teacher = new Teacher();
        Class<? extends Teacher> aClass = teacher.getClass();
        System.out.println(aClass);
    }
}

反射获取构造方法并使用
Class 类中用于获取构造方法的方法
Constructor<?>[] getConstructors() :返回所有公共构造方法对象的数组 Constructor<?>[] getDeclaredConstructors() :返回所有构造方法对象的数组
Constructor getConstructor(Class<?>... parameterTypes) :返回单个公共构造方法对象 Constructor getDeclaredConstructor(Class<?>… parameterTypes) :返回单个构造方法对象

package test;
import java.lang.reflect.Constructor;
public class Test02 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
        //1、获取类对象所有公共构造方法
        Class<?> aClass = Class.forName("entity.Teacher");
        Constructor<?>[] constructors = aClass.getConstructors();
        for (Constructor<?> constructor : constructors) {
            System.out.println(constructor);
        }
        System.out.println("--------------------------------------");
        //2、获取类对象的所有构造方法
        Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors();
        for (Constructor<?> declaredConstructor : declaredConstructors) {
            System.out.println(declaredConstructor);
        }
        System.out.println("--------------------------------------");
        //3、获取类对象单个公共构造方法
        Constructor<?> constructor = aClass.getConstructor(String.class, int.class);
        System.out.println(constructor);
        System.out.println("--------------------------------------");
        //4、获取类对象单个构造方法
        Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(String.class);
        System.out.println(declaredConstructor);
    }
}

Constructor 类中用于创建对象的方法
T newInstance(Object… initargs) :根据指定的构造方法创建对象
setAccessible(boolean flag) :设置为 true, 表示取消访问检查

package test;

import entity.Teacher;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class Test01 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        //method1();无参构造方法
        //method2();有参构造方法
        //1、获取类对象
        Class<?> aClass = Class.forName("entity.Teacher");
        //2、获取构造方法
        Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(String.class);
        //3、被private修饰的构造方法不能直接使用,需要临时取消访问检查
        declaredConstructor.setAccessible(true);
        //4、通过newInstance()方法构造对象
        Teacher o = (Teacher) declaredConstructor.newInstance("李四");
        System.out.println(o);

    }

    private static void method2() throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        //1、获取类对象
        Class<?> aClass = Class.forName("entity.Teacher");
        //2、获取构造方法
        Constructor<?> constructor = aClass.getConstructor(String.class, int.class);
        Teacher o = (Teacher) constructor.newInstance("张三", 1);
        System.out.println(o);
    }

    private static void method1() throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        //1、获取类对象
        Class<?> aClass = Class.forName("entity.Teacher");
        //2、获取构造方法对象
        Constructor<?> constructor = aClass.getConstructor();
        //3、利用newInstance()方法来构造对象
        Teacher teacher = (Teacher) constructor.newInstance();
        System.out.println("无参方法构造对象"+teacher);
    }
}

反射获取成员变量并使用
Field[] getFields() :返回所有公共成员变量对象的数组
Field[] getDeclaredFields() :返回所有成员变量对象的数组
Field getField(String name) :返回单个公共成员变量对象
Field getDeclaredField(String name) :返回单个成员变量对象

package test;

import java.lang.reflect.Field;
import java.math.BigDecimal;

public class Test03 {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        //1、获取所有的公共成员变量
        //method1();
        //2、获取所有的成员变量
        //method2();
        //3、获取单个公共成员变量
        //method3();
        //4、获取单个成员变量
        //method4();
    }

    private static void method4() throws ClassNotFoundException, NoSuchFieldException {
        Class<?> aClass = Class.forName("entity.Teacher");
        Field name = aClass.getDeclaredField("name");
        System.out.println(name);
    }

    private static void method3() throws ClassNotFoundException, NoSuchFieldException {
        Class<?> aClass = Class.forName("entity.Teacher");
        Field field = aClass.getField("id");
        System.out.println(field);
    }

    private static void method2() throws ClassNotFoundException {
        Class<?> aClass = Class.forName("entity.Teacher");
        Field[] declaredFields = aClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println(declaredField);
        }
    }

    private static void method1() throws ClassNotFoundException {
        Class<?> aClass = Class.forName("entity.Teacher");
        Field[] fields = aClass.getFields();
        for (Field field : fields) {
            System.out.println(field);
        }
        System.out.println("---------------------------------");
    }
}

反射获取成员变量并使用
void set(Object obj, Object value) :给指定对象的成员变量赋值
Object get(Object obj) 返回指定对象的 Field 的值

package test;

import entity.Teacher;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

public class Test04 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        //1、获取类对象
        Class<?> aClass = Class.forName("entity.Teacher");
        //2、获取成员变量
        Field id = aClass.getField("id");
        //3、创建一个对象
        Constructor<?> constructor = aClass.getConstructor();
        Teacher teacher = (Teacher) constructor.newInstance();
        //4、用set方法赋值
        id.set(teacher,"1");
        System.out.println(teacher);
        //5、get方法返回值
        Object o = id.get(teacher);
        System.out.println(o);
    }
}

反射获取成员方法
Method[] getMethods() :返回所有公共成员方法对象的数组,包括继承的
Method[] getDeclaredMethods() :
返回所有成员方法对象的数组,不包括继承的
Method getMethod(String name, Class<?>... parameterTypes) : 返回单个公共成员方法对象 Method getDeclaredMethod(String name, Class<?>… parameterTypes) :
返回单个成员方法对象

package test;

import java.lang.reflect.Method;
public class Test05 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
        //1、获取单个公共的成员方法
        //method1();
        //2、获取单个成员方法
        //method2();
        //3、获取所有公共的成员方法
        //method3();
        //4、获取所有的成员方法
        //method4();
    }

    private static void method4() throws ClassNotFoundException {
        //1、获得类对象
        Class<?> aClass = Class.forName("entity.Teacher");
        //2、获取所有的成员方法
        Method[] declaredMethods = aClass.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            System.out.println(declaredMethod);
        }
    }

    private static void method3() throws ClassNotFoundException {
        //1、获得类对象
        Class<?> aClass = Class.forName("entity.Teacher");
        //2、获取所有的公共的成员方法
        Method[] methods = aClass.getMethods();
        for (Method method : methods) {
            System.out.println(method);
        }
    }

    private static void method2() throws ClassNotFoundException, NoSuchMethodException {
        //1、获得类对象
        Class<?> aClass = Class.forName("entity.Teacher");
        //2、获取单个成员方法
        Method method = aClass.getDeclaredMethod("run");
        //3、取消临时检查
        method.setAccessible(true);
        System.out.println(method);
    }

    private static void method1() throws ClassNotFoundException, NoSuchMethodException {
        //1、获得类对象
        Class<?> aClass = Class.forName("entity.Teacher");
        //2、获取单个公共的成员方法
        Method method = aClass.getMethod("test");
        System.out.println(method);
    }
}

反射获取成员方法并运行
反射
Object invoke(Object obj, Object… args) :运行方法
参数一:用 obj 对象调用该方法
参数二:调用方法的传递的参数(如果没有就不写)
返回值:方法的返回值(如果没有就不写)

package test;

import entity.Teacher;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Test06 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        //1、获取类方法
        Class<?> aClass = Class.forName("entity.Teacher");
        //2、获取方法
        Method show = aClass.getDeclaredMethod("show", String.class);
        //3、取消临时检查
        show.setAccessible(true);
        //4、创建一个对象调用这个方法
        Constructor<?> constructor = aClass.getConstructor();
        Teacher teacher = (Teacher) constructor.newInstance();
        //5、运行方法
        Object invoke = show.invoke(teacher, "123");
        System.out.println(invoke);
    }
}

24、XML

DOM4J解析XML文件
student.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!--用于存储多个学生信息-->
<students>
    <student id = "1">
        <name>张三</name>
        <age>23</age>
    </student>

    <student id = "2">
        <name>李四</name>
        <age>45</age>
    </student>
</students>

XmlParse:

package test;

import entity.Student;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.dom4j.tree.BackedList;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class XmlParse {
    public static void main(String[] args) throws DocumentException {
        //获取解析器对象
        SAXReader saxReader = new SAXReader();
        //获取xml文件对象
        Document document = saxReader.read(new File("E:\\MyJava\\Test\\src\\xml\\student.xml"));
        //获取根目录(students)
        Element rootElement = document.getRootElement();
        //创建List学生集合
        List<Student> list = new ArrayList<>();
        //elements()将调用者的所有子标签放在一个集合当中
        //element("标签名")将调用者指定的子标签返回到一个集合当中
        List<Element> student = rootElement.elements("student");
        for (Element element : student) {
            //element依次表示<student>标签
            //获取id属性
            Attribute idAttribute = element.attribute("id");
            //获取id的属性值
            String id = idAttribute.getValue();

            //获取name标签
            Element nameElement = element.element("name");
            //获取name标签的值
            String name = nameElement.getText();

            //获取age标签
            Element ageElement = element.element("age");
            //获取age标签的值
            String age = ageElement.getText();
            //创建student对象
            Student stu = new Student(id,age,Integer.parseInt(age));
            //添加到集合当中去
            list.add(stu);
        }
        //遍历集合
        for (Student stu : list) {
            System.out.println(stu);
        }
    }
}

DTD约束
在这里插入图片描述
persondtd.dtd

<!ELEMENT persons (person)>
<!ELEMENT person (name,age)>
<!ELEMENT name (#PCDATA)>
<!ELEMENT age (#PCDATA)>

person.xml

<?xml version="1.0" encoding="UTF-8" ?>
<persons>
    <person>
        <name>张三</name>
        <age>24</age>
    </person>
</persons>

引入 DTD 约束的三种方法
引入本地 dtd

在 xml 文件内部引入

引入网络 dtd

本地引入

<?xml version="1.0" encoding="UTF-8" ?>
<!--引入DTD约束文件
SYSTEM表示引用本地的dtd文件
本地引入
-->
<!DOCTYPE persons SYSTEM "persondtd.dtd">
<persons>
    <person>
        <name>张三</name>
        <age>24</age>
    </person>
</persons>

内部引入

<?xml version="1.0" encoding="UTF-8" ?>
<!--引入DTD约束文件
内部引入
-->
<!DOCTYPE persons[
        <!ELEMENT persons (person)>
        <!ELEMENT person (name,age)>
        <!ELEMENT name (#PCDATA)>
        <!ELEMENT age (#PCDATA)>
        ]>
<persons>
    <person>
        <name>张三</name>
        <age>24</age>
    </person>
</persons>

网络引入

<?xml version="1.0" encoding="UTF-8" ?>
<!--引入DTD约束文件
网络引入
-->
<!DOCTYPE persons PUBLIC "DTD文件的名称" "DTD文件的URL">
<persons>
    <person>
        <name>张三</name>
        <age>24</age>
    </person>
</persons>

DTD语法规则:
在这里插入图片描述定义属性
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值