JavaSE第一阶段模块四

第一阶段模块四

异常机制和File类

异常机制(重点)

概念

1.异常在Java语言中主要指程序执行中发生的不正常情况

2.java.lang.Throwable类是Java语言中错误(Error)和异常(Exception)的超类

​ Error类主要用于描述Java虚拟机无法解决的严重错误,通常无法编码解决,如:JVM挂掉了等

​ Exception类主要用于描述因编程错误或偶然外在因素导致的轻微错误,通常可以编码解决,如:0作为除数等

异常分类

1.java.lang.Exception类是所有异常的超类,主要分为以下两种:

a.RuntimeException - 运行时异常,也叫作非检测性异常

b.IOException和其它异常 - 其它异常,也叫作检测性异常,所谓检测性异常就是指在编译阶段都能被编译器检测出来的异常

2.RuntimeException类的主要子类:

a).ArithmeticException类 - 算术异常
b).ArrayIndexOutOfBoundsException类 - 数组下标越界异常
c).NullPointerException - 空指针异常
d).ClassCastException - 类型转换异常
e).NumberFormatException - 数字格式异常

3.注意

​ 当程序执行过程中发生异常但又没有手动处理时,则由Java虚拟机采用默认方式处理异常,而默认处理方式就是:打印异常的名称、异常发生的原因、异常发生的位置以及终止程序。

异常避免

1.在以后的开发中尽量使用if条件判断来避免异常的发生
2.但是过多的if条件判断会导致程序的代码加长、臃肿,可读性

 public class ExceptionPreventTest {

    public static void main(String[] args) {

        // 算术异常
        int ia = 10;
        int ib = 0;
        if(0 != ib){
            System.out.println(ia / ib);
        }

        // 数组下标越界异常
        int[] arr = new int[5];
        int pos = 5;
        if(pos >= 0 && pos < 5){
            System.out.println(arr[pos]);
        }

        // 空指针异常
        String str = null;
        if(null != str){
            System.out.println(str.length());
        }

        // 类型转换异常
        Exception ex = new Exception();
        if(ex instanceof IOException){
            IOException ie = (IOException) ex;
        }

        // 数字格式异常
        String str2 = "123a";
        if (str2.matches("\\d+")){
            System.out.println(Integer.parseInt(str2));
        }

        System.out.println("程序总算正常结束了!");
    }
}
异常捕获

1.语法格式

try {
编写可能发生异常的代码;
}
catch(异常类型 引用变量名) {
编写针对该类异常的处理代码;
}

finally {
编写无论是否发生异常都要执行的代码;
}

2.注意事项

a.当需要编写多个catch分支时,切记小类型应该放在大类型的前面;
b.懒人的写法:
catch(Exception e) {}
c.finally通常用于进行善后处理,如:关闭已经打开的文件等

3.执行流程

try {
a;
b; - 可能发生异常的语句
c;
}catch(Exception ex) {
d;
}finally {
e;
}
当没有发生异常时的执行流程:a b c e;
当发生异常时的执行流程:a b d e;

public class ExceptionCatchTest {
    public static void main(String[] args) {

        // 创建一个FileInputStream类型的对象与e:/a.txt文件关联,打开文件
        FileInputStream fis = null;
        try {
            System.out.println("1");
            // 当程序执行过程中发生了异常后直奔catch分支进行处理
            fis = new FileInputStream("e:/a.txt");
            System.out.println("2");
        } catch (FileNotFoundException e) {
            System.out.println("3");
            e.printStackTrace();
            System.out.println("4");
        }
        // 关闭文件
        try {
            System.out.println("5");
            fis.close();
            System.out.println("6");
        } /*catch (Exception e){
            e.printStackTrace();
        }*/ catch (IOException e) {
            System.out.println("7");
            e.printStackTrace();
            System.out.println("8");
        } catch (NullPointerException e){
            e.printStackTrace();
        }catch (Exception e){
            e.printStackTrace();
        }

        System.out.println("世界上最真情的相依就是你在try我在catch,无论你发什么脾气我都默默承受并静静的处理,到那时再来期待我们的finally!");
        // 当程序没有发生异常时的执行流程:1 2 5 6 世界上...
        // 当程序发生异常又没有手动处理空指针异常时的执行流程:1 3 4 5  空指针异常导致程序终止
        // 当程序发生异常并且手动处理空指针异常时的执行流程:1 3 4 5 世界上...

        // 手动处理异常和没有处理的区别:代码是否可以继续向下执行
    }
}


public class ExceptionFinallyTest {
    public static void main(String[] args) {

        try {
            int ia = 10;
            int ib = 0;
            System.out.println(ia / ib);
        } catch (ArithmeticException e){
            e.printStackTrace();

            String str1 = null;
            //str1.length();  // 会发生空指针异常
        }finally {
            System.out.println("无论是否发生异常都记得来执行我哦!");    // 依然执行
        }

        System.out.println("Over!");    // 不执行

        System.out.println("--------------------------------------");
        int test = test();
        System.out.println("test = " + test);   // 2
    }

    // 笔试考点
    public static int test(){
        try {
            int[] arr = new int[5];
            System.out.println(arr[5]);
            return 0;
        } catch (ArrayIndexOutOfBoundsException e){
            e.printStackTrace();
            return 1;
        } finally {
            return 2;   // 提前结束方法并返回数据
        }
    }
}
异常抛出

1.基本概念

​ 在某些特殊情况下有些异常不能处理或者不便于处理时,就可以将该异常转移给该方法的调用者,这种方法就叫异常的抛出。当方法执行时出现异常,则底层生成一个异常类对象抛出,此时异常代码后续的代码就不再执行

2.语法格式

访问权限 返回值类型 方法名称(形参列表) throws 异常类型1,异常类型2,…{ 方法体; }
如:
public void show() throws IOException{}

3.方法重写的原则

a.要求方法名相同、参数列表相同以及返回值类型相同,从jdk1.5开始支持返回子类类型;
b.要求方法的访问权限不能变小,可以相同或者变大;
c.要求方法不能抛出更大的异常;

4.注意:
子类重写的方法不能抛出更大的异常、不能抛出平级不一样的异常,但可以抛出一样的异常、更小的异常以及不抛出异常

5.经验分享
a.若父类中被重写的方法没有抛出异常时,则子类中重写的方法只能进行异常的捕获处理
b.若一个方法内部又以递进方式分别调用了好几个其它方法,则建议这些方法内可以使用抛出的方法处理到最后一层进行捕获方式处理

public class ExceptionThrowsTest {

    public static void show() throws IOException {
        FileInputStream fis = new FileInputStream("e:/a.txt");
        System.out.println("我想看看你抛出异常后是否继续向下执行???");
        fis.close();
    }

    public static void test1() throws IOException {
        show();
    }
    
    public static void test2() throws IOException {
        test1();
    }

    public static void test3() throws IOException {
        test2();
    }
    
    // 不建议在main方法中抛出异常  JVM负担很重
    public static void main(String[] args)/* throws IOException */{
        try {
            show();
        } catch (IOException e) {
            e.printStackTrace();
        }

        System.out.println("------------------------------");
        try {
            test3();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }   
}

public class ExceptionMethod {

    public void show() throws IOException{

    }
}

public class SubExceptionMethod extends ExceptionMethod {

    @Override
    public void show() throws IOException {}    // 子类重写的方法可以抛出和父类中方法一样的异常
    //public void show() throws FileNotFoundException {}  // 子类重写的方法可以抛出更小的异常
    //public void show(){};   //  子类可以不抛出异常
    //public void show() throws ClassNotLoadedException {}  // 子类重写的方法不可以抛出平级不一样的异常
    //public void show() throws Exception {}  // 子类重写的方法不可以抛出更大的异常

}
自定义异常

1.基本概念

​ 当需要在程序中表达年龄不合理的情况时,而Java官方又没有提供这种针对性的异常,此时就需要程序员自定义异常加以描述

2.实现流程
a.自定义xxxException异常类继承Exception类或者其子类
b.提供两个版本的构造方法,一个是无参构造方法,另外一个是字符串作为参数的构造方法

3.异常的产生
throw new 异常类型(实参);

如:
throw new AgeException(“年龄不合理!!!”);

Java采用的异常处理机制是将异常处理的程序代码集中在一起,与正常的程序代码分开,使得程序简洁、优雅,并易于维护。

public class AgeException extends Exception{

    static final long serialVersionUID = -3387516993124229948L; // 序列化的版本号 与序列化操作有关

    public AgeException(){

    }

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


public class Person {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) /*throws AgeException */{
        setName(name);
        setAge(age);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) /*throws AgeException */{
        if (age > 0 && age < 150){
            this.age = age;
        } else {
            //System.out.println("年龄不合理哦!");
            try {
                throw new AgeException("年龄不合理哦!");
            } catch (AgeException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

public class PersonTest {

    public static void main(String[] args) {

        /*Person p1 = null;
        try {
            p1 = new Person("张飞",-30);
        } catch (AgeException e) {
            e.printStackTrace();
        }
        System.out.println("p1 = " + p1);   // 张飞 0*/
        Person p1 = new Person("张飞",-30);
        System.out.println("p1 = " + p1);   // 张飞 0
    }
}

File类(重点)

概念

​ java.io.File类主要用于描述文件或目录路径的抽象表示信息,可以获取文件或目录的特征信息,如:大小等

常用方法
方法声明 功能介绍
File(String pathname) 根据参数指定的路径名来构造对象
File(String parent, String child) 根据参数指定的父路径和子路径信息构造对象
File(File parent, String child) 根据参数指定的父抽象路径和子路径信息构造对象
boolean exists() 测试此抽象路径名表示的文件或目录是否存在
String getName() 用于获取文件的名称
long length() 返回由此抽象路径名表示的文件的长度
long lastModified() 用于获取文件的最后一次修改时间
String getAbsolutePath() 用于获取绝对路径信息
boolean delete() 用于删除文件,当删除目录时要求是空目录
boolean createNewFile() 用于创建新的空文件
boolean mkdir() 用于创建目录
boolean mkdirs() 用于创建多级目录
File[] listFiles() 获取该目录下的所有内容
boolean isFile() 判断是否为文件
boolean isDirectory() 判断是否为目录
File[] listFiles(FileFilter filter) 获取目录下满足筛选器的所有内容

案例题目
遍历指定目录以及子目录中的所有内容并打印出来

public class FileTest {

    // 自定义成员方法实现指定目录以及子目录中所有内容的打印
    public static void show(File file){
        // 获取目录f3下的所有内容并记录到一位数组中
        File[] filesArray = file.listFiles();
        // 遍历数组
        for (File tf: filesArray) {
            String name = tf.getName();
            // 判断是否为文件,若是则直接打印文件名称
            if (tf.isFile()){
                System.out.println(name);
            }
            // 若是目录,则使用[]将目录名称括起来
            if (tf.isDirectory()){
                System.out.println("[" + name + "]");
                show(tf);
            }
        }
    }


    public static void main(String[] args) throws IOException {

        // 1.构造File类的对象与e:/a.txt文件关联
        File f1 = new File("e:/a.txt");
        // 2、若文件存在则获取文件的相关特征信息并打印
        if (f1.exists()){
            System.out.println("文件的名称是:" + f1.getName());
            System.out.println("文件的大小是:" + f1.length());
            Date d1 = new Date(f1.lastModified());
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            System.out.println("文件最后一次修改时间是:" + sdf.format(d1));
            // 绝对路径:主要指以根目录开始的路径信息,如:c:/  d:/  /...
            // 相对路径:主要指以当前目录所在位置开始的路径信息,如:./  ../   推荐相对路径
            System.out.println("文件的绝对路径是:" + f1.getAbsolutePath());
            System.out.println(f1.delete() ? "文件删除成功" : "文件删除失败");
        } else {
            // 3.若文件不存在则创建新的空文件
            System.out.println(f1.createNewFile() ? "文件创建成功" : "文件创建失败!");
        }

        System.out.println("--------------------------------------");
        // 4.实现目录的删除和创建
        File f2 = new File("e:/捣乱/猜猜我是谁/你猜我猜不猜/死鬼");
        if (f2.exists()){
            System.out.println("目录的名称是:" + f2.getName());
            System.out.println(f2.delete() ? "目录删除成功" : "目录删除失败");
        } else{
            //System.out.println(f2.mkdir() ? "目录创建成功" : "目录创建失败");   // 创建单层目录
            System.out.println(f2.mkdirs() ? "目录创建成功" : "目录创建失败");  // 创建多层目录
        }

        System.out.println("--------------------------------------");
        // 5.实现将指定目录中的所有内容打印出来
        File f3 = new File("e:/捣乱");
        // 获取目录f3下的所有内容并记录到一位数组中
        File[] filesArray = f3.listFiles();
        // 遍历数组
        for (File tf: filesArray) {
            String name = tf.getName();
            // 判断是否为文件,若是则直接打印文件名称
            if (tf.isFile()){
                System.out.println(name);
            }
            // 若是目录,则使用[]将目录名称括起来
            if (tf.isDirectory()){
                System.out.println("[" + name + "]");
            }
        }

        System.out.println("--------------------------------------");
        // 6.实现目录中所有内容获取的同时进行过滤
        // 匿名内部类的语法格式:接口/父类类型 引用变量名 = new 接口/父类类型() { 方法的重写 };
        /*FileFilter ff = new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                // 若文件名是以.avi为结尾,则返回true表示保留    否则返回false就是表示丢弃
                return pathname.getName().endsWith("avi");
            }
        };*/
        // lambda表达式格式:(参数列表) -> {方法体}
        FileFilter ff = (File pathname) -> {return pathname.getName().endsWith("avi");};
        File[] filesArray2 = f3.listFiles(ff);
        for (File tf : filesArray2) {
            System.out.println(tf);
        }

        System.out.println("--------------------------------------");
        // 7.使用递归的思想获取目录以及子目录在的内容
        show(new File("e:/捣乱"));
    }
}

IO流

概念

IO就是Input和Output的简写,也就是输入和输出的含义
IO流就是指读写数据时像流水一样从一端流到另外一端,因此得名为“流"

分类

1.按照读写数据的基本单位不同,分为字节流和字符流

​ 字节流主要指以字节为单位进行数据读,可以读写任意类型的文件
​ 字符流主要指以字符(2个字节)为单位进行数据读写,只能读写文本文件

2.按照读写数据的方向不同,分为输入流和输出流(站在程序的角度)
输入流主要指从文件中读取数据内容输入到程序中,也就是文件
输出流主要指将程序中的数据内容输出到文件中,也就是文件

3.按照流的角色不同分为节点流和处理流
节点流主要指直接和输入输出源对接的流
处理流主要指需要建立在节点流的基础之上的流

体系结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qWzwdN1E-1619445111985)(E:\MarkDown\拉勾笔记\IO流体系结构)]

相关流详解

FileWriter类(重点)
概念

​ java.io.FileWriter类主要用于将文本内容写入到文本文件

常用方法
方法声明 功能介绍
FileWriter(String fileName) 根据参数指定的文件名构造对象
FileWriter(String fileName, boolean append) 以追加的方式根据参数指定的文件名来构造对象
void write(int c) 写入单个字符
void write(char[] cbuf, int off, int len 将指定字符数组中从偏移量off开始的len个字符写入此文件输出流
void write(char[] cbuf) 将cbuf.length个字符从指定字符数组写入此文件输出流中
void flush() 刷新流
void close() 关闭流对象并释放有关的资源
public class FileWriterTest {
    public static void main(String[] args) {
        // 选中代码后可以使用 ctrl+alt+T 来生成异常的捕获代码等
        // 若文件不存在,该流会自动创建新的空的文件
        // 若文件存在,则该流会清空文件中的原有内容
        FileWriter fw = null;

        try {
            // 1.构造FileWriter类型的对象与e:/a.txt文件关联
            //fw = new FileWriter("e:/a.txt");
            // 以追加的方式创建对象去关联文件
            // 若文件不存在,该流会自动创建新的空的文件
            // 若文件存在,则保留文件中的原有数据内容
            fw = new FileWriter("e:/a.txt",true);
            // 2.通过流对象写入数据内容    每当写入一个字符后文件中的读写位置向后移动一位
            fw.write('a');

            // 准备一个字符数组
            char[] cArr = new char[]{'h','e','l','l','o'};
            // 将字符数组的一部分内容写入进去
            fw.write(cArr,1,3);     // ell
            // 将整个字符数组写进去
            fw.write(cArr); // hello

            // 刷新流
            fw.flush();
            System.out.println("写入数据成功!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 3.关闭流对象并释放有关的资源
            if (null != fw) {
                try {
                    fw.close();     // 自带刷新功能
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

FileReader类(重点)
概念

​ java.io.FileReader类主要用于从文本文件读取文本数据内容

常用方法
方法声明 功能介绍
FileReader(String fileName) 根据参数指定的文件名构造对象
int read() 读取单个字符的数据并返回,返回-1表示读取到末尾
int read(char[] cbuf, int offset, int length) 从输入流中将最多len个字符的数据读入一个字符数组中,返回读取到的字符个数,返回-1表示读取到末尾
int read(char[] cbuf) 从此输入流中将最多 cbuf.length 个字符的数据读入字符数组中,返回读取到的字符个数,返回-1表示读取到末尾
void close() 关闭流对象并释放有关的资源
public class FileReaderTest {
    public static void main(String[] args) {
        FileReader fr = null;

        try {
            // 1.构造FileReader类型的对象与e:/a.txt文件关联
            fr = new FileReader("e:/a.txt");
            // 2.读取数据内容并打印
           /* int res = fr.read();
            System.out.println("读取到的单个字符是:" + (char)res);   // 'a'
            */
           /*int res = 0;
           while((res = fr.read()) != -1){
               System.out.println("读取到的单个字符是:" + (char)res);
           }*/

           // 准备一个字符数组来保存读取到的数据内容
            char[] cArr = new char[5];
            // 期望读满字符数组中的一部分空间,也就是读取三个字符串放入数组cArr中夏标从1开始的位置上
            /*int res = fr.read(cArr, 1, 3);
            System.out.println("实际读取到的字符个数是:" + res);   // 3
            for (char cv : cArr) {
                System.out.println("实际读取到的字符个数是:" + cv);   // [ ] a e l [ ]
            }*/
            // 期望读满整个字符数组
            int res = fr.read(cArr);
            System.out.println("实际读取到的字符个数是:" + res);   // 5
            for (char cv : cArr) {
                System.out.println("读取到的字符个数是:" + cv);   // a e l l h
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 3.关闭流并释放有关的资源
            if (null != fr) {
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
文件字符流实现文件的拷贝
public class FileCharCopyTest {

    public static void main(String[] args) {
        FileReader fr = null;
        FileWriter fw = null;

        try {
            // 1.创建FileReader类型的对象与e:/a.txt文件关联
            fr = new FileReader("e:/a.txt");
            // 2.创建FileWriter类型的对象与e:/b.txt文件关联
            fw = new FileWriter("e:/b.txt");
            // 3.不断的从输入输出流中读取数据内容并写入到输出流中
            System.out.println("正在玩命的拷贝...");
            int res = 0;
            while((res = fr.read()) != -1) {
                fw.write(res);
            }
            System.out.println("拷贝文件成功!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4.关闭流对象并释放有关资源
            if (null != fw) {
                try {
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != fr){
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
FileOutputStream类(重点)
概念

​ java.io.FileOutputStream类主要用于将图像数据之类的原始字节流写入到输出流中

常用方法
方法声明 功能介绍
FileOutputStream(String name) 根据参数指定的文件名来构造对象
FileOutputStream(String name, boolean append) 以追加的方式根据参数指定的文件名来构造对象
void write(int b) 将指定字节写入此文件输出流
void write(byte[] b, int off, int len) 将指定字节数组中从偏移量off开始的len个字节写入此文件输出流
void write(byte[] b) 将 b.length 个字节从指定字节数组写入此文件输出流中
void flush() 刷新此输出流并强制写出任何缓冲的输出字节
void close() 关闭流对象并释放有关的资源
FileInputStream类(重点)
概念

​ java.io.FileInputStream类主要用于从输入流中以字节流的方式读取图像数据等

常用方法
方法声明 功能介绍
FileInputStream(String name) 根据参数指定的文件路径名来构造对象
int read() 从输入流中读取单个字节的数据并返回,返回-1表示读取到末尾
int read(byte[] b, int off, int len) 从此输入流中将最多len个字节的数据读入字节数组中,返回读取到的字节个数,返回-1表示读取到末尾
int read(byte[] b) 从此输入流中将最多 b.length 个字节的数据读入字节数组中,返回读取到的字节个数,返回-1表示读取到末尾
void close() 关闭流对象并释放有关的资源
int available() 获取输入流所关联文件的大小
文件字节流实现文件的拷贝
public class FileByteCopyTest {
    public static void main(String[] args) {

        // 获取当前系统时间距离1970年1月1日0时0分0秒的毫秒数
        long g1 = System.currentTimeMillis();

        FileInputStream fis = null;
        FileOutputStream fos = null;

        try {
            // 1.创建FileInputStream类型的对象与e:/03 IO流的框架图.png文件关联
            //fis = new FileInputStream("e:/03 IO流的框架图.png");
            fis = new FileInputStream("e:/统计字符串个数.wmv");

            // 2.创建FileOutputStream类型的对象与e:/IO流的框架图.png文件关联
            //fos = new FileOutputStream("e:/IO流的框架图.png");
            fos = new FileOutputStream("e:/字符串个数.wmv");

            // 3.不断的从输入流中读取数据内容并写入到输出流中
            System.out.println("正在玩命的拷贝...");

            // 方式一:以单个字节为单位进行拷贝,每次读取一个字节在写入一个字节
            // 缺点:文件稍大时,拷贝效率低

            /*int res = 0;
            while((res = fis.read()) != -1){
                fos.write(res);
            }*/

            // 方式二:准备一个和文件大小一样的缓冲区,一次性将文件中的所有内容取出到缓冲区然后一次性写入进去
            // 缺点:若文件过大时,无法申请和文件大小一样的缓冲区,真实物理内存不足
            /*int len = fis.available();
            System.out.println("获取到的文件大小是:" + len);
            byte[] bArr = new byte[len];
            int res = fis.read(bArr);
            System.out.println("实际读取到的文件大小是:" + res);
            fos.write(bArr);*/

            // 方式三:准备一个相对适当的缓冲区,分多次将文件拷贝完成
            byte[] bArr = new byte[1024];
            int res = 0;
            while((res = fis.read(bArr)) != -1){
                fos.write(bArr,0,res);
            }

            System.out.println("文件拷贝成功");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4.关闭流对象并释放有关资源
            if (null != fos) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != fis) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        long g2 = System.currentTimeMillis();
        System.out.println("使用文件流拷贝视频文件的时间为:" + (g2-g1) + "毫秒");

    }
}

BufferedOutputStream类(重点)
概念

​ java.io.BufferedOutputStream类主要用于描述缓冲输出流,此时不用为写入的每个字节调用底层系统

常用方法
方法声明 功能介绍
BufferedOutputStream(OutputStream out) 根据参数指定的引用来构造对象
BufferedOutputStream(OutputStream out, int size) 根据参数指定的引用和缓冲区大小来构造对象
void write(int b) 写入单个字节
void write(byte[] b, int off, int len) 写入字节数组中的一部分数据
void write(byte[] b) 写入参数指定的整个字节数组
void flush() 刷新流
void close() 关闭流对象并释放有关的资源
BufferedInputStream类(重点)
概念

​ java.io.BufferedInputStream类主要用于描述缓冲输入流

常用方法
方法声明 功能介绍
BufferedInputStream(InputStream in) 根据参数指定的引用构造对象
BufferedInputStream(InputStream in, int size) 根据参数指定的引用和缓冲区大小构造对象
int read() 读取单个字节
int read(byte[] b, int off, int len) 读取len个字节
int read(byte[] b) 读取b.length个字节
void close() 关闭流对象并释放有关的资源
缓冲字节流实现文件的拷贝
public class BufferedByteCopyTest {
    public static void main(String[] args) {

        // 获取当前系统时间距离1970年1月1日0时0分0秒的毫秒数
        long g1 = System.currentTimeMillis();

        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;

        try {
            // 1.创建BufferedInputStream类型的对象与e:/统计字符串个数.wmv文件关联
            bis = new BufferedInputStream(new FileInputStream("e:/统计字符串个数.wmv"));

            // 2.创建BufferedOutputStrea类型的对象与e:/字符串个数.wmv文件关联
            bos = new BufferedOutputStream(new FileOutputStream("e:/字符串个数.wmv"));

            // 3.不断的从输入流中读取数据并写入到输出流中
            System.out.println("正在玩命的拷贝...");
            byte[] bArr = new byte[1024];
            int res = 0;
            while((res = bis.read(bArr)) != -1){
                bos.write(bArr,0,res);
            }

            System.out.println("文件拷贝成功");
        } catch (IOException e) {


        } finally {
            // 4.关闭流对象并释放有关资源
            if (null != bos) {
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != bis) {
                try {
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        long g2 = System.currentTimeMillis();
        System.out.println("使用缓冲流拷贝视频文件的时间为:" + (g2-g1) + "毫秒");

    }
}
BufferedWriter类(重点)
概念

​ java.io.BufferedWriter类主要用于写入单个字符、字符数组以及字符串到输出流中

常用方法
方法声明 功能介绍
BufferedWriter(Writer out) 根据参数指定的引用来构造对象
BufferedWriter(Writer out, int sz) 根据参数指定的引用和缓冲区大小来构造对象
void write(int c) 写入单个字符到输出流中
void write(char[] cbuf, int off, int len) 将字符数组cbuf中从下标off开始的len个字符写入输出流中
void write(char[] cbuf) 将字符串数组cbuf中所有内容写入输出流中
void write(String s, int off, int len) 将参数s中下标从off开始的len个字符写入输出流中
void write(String str) 将参数指定的字符串内容写入输出流中
void newLine() 用于写入行分隔符到输出流中
void flush() 刷新流
void close() 关闭流对象并释放有关的资源
BufferedReader类(重点)
概念

​ java.io.BufferedReader类用于从输入流中读取单个字符、字符数组以及字符串

常用方法
方法声明 功能介绍
BufferedReader(Reader in) 根据参数指定的引用来构造对象
BufferedReader(Reader in, int sz) 根据参数指定的引用和缓冲区大小来构造对象
int read() 从输入流读取单个字符,读取到末尾则返回-1,否则返回实际读取到的字符内容
int read(char[] cbuf, int off, int len) 从输入流中读取len个字符放入数组cbuf中下标从off开始的位置上,若读取到末尾则返回-1,否则返回实际读取到的字符个数
int read(char[] cbuf) 从输入流中读满整个数组cbuf
String readLine() 读取一行字符串并返回,返回null表示读取到末尾
void close() 关闭流对象并释放有关的资源
public class BufferedCharCopyTest {
    public static void main(String[] args) {
        BufferedReader br = null;
        BufferedWriter bw = null;

        try {
            // 1.创建BufferedReader类型的对象与e:/a.txt文件关联
            br = new BufferedReader(new FileReader("e:/a.txt"));

            // 2.创建BufferedWriter类型的对象与e:/b.txt文件关联
            bw = new BufferedWriter(new FileWriter("e:/b.txt"));

            // 3.不断从输入流中读取一行字符串并写入到输出流中
            System.out.println("正在玩命的拷贝文件...");
            String str = null;
            while ((str = br.readLine()) != null){
                bw.write(str);
                bw.newLine();   // 当前系统中的行分隔符是:\r\n
            }

            System.out.println("拷贝文件成功!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4.关闭流对象并释放有关资源
            if (null != bw) {
                try {
                    bw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}
PrintStream类
概念

​ java.io.PrintStream类主要用于更加方便地打印各种数据内容

常用方法
方法声明 功能介绍
PrintStream(OutputStream out) 根据参数指定的引用来构造对象
void print(String s) 用于将参数指定的字符串内容打印出来
void println(String x) 用于打印字符串后并终止该行
void flush() 刷新流
void close() 用于关闭输出流并释放有关的资源
PrintWriter类
概念

​ java.io.PrintWriter类主要用于将对象的格式化形式打印到文本输出流

常用方法
方法声明 功能介绍
PrintWriter(Writer out) 根据参数指定的引用来构造对象
void print(String s) 将参数指定的字符串内容打印出来
void println(String x) 打印字符串后并终止该行
void flush() 刷新流
void close() 关闭流对象并释放有关的资源

案例题目
不断地提示用户输入要发送的内容,若发送的内容是"bye"则聊天结束,否则将用户输入的内容写入到文件d:/a.txt中。
要求使用BufferedReader类来读取键盘的输入 System.in代表键盘输入
要求使用PrintStream类负责将数据写入文件

public class PrintStreamChatTest {
    public static void main(String[] args) {
        BufferedReader br = null;
        PrintStream ps = null;

        try {
            // 由手册可知:构造方法需要的是Reader类型的引用,但Reader类是个抽象类,实参只能传递子类的对象  字符流
            // 由手册可知:System.in代表键盘输入,而且是InputStream类型的  字节流
            br = new BufferedReader(new InputStreamReader(System.in));
            ps = new PrintStream(new FileOutputStream("e:/a.txt",true));

            // 声明一个boolean类型的变量作为发送方的代表
            boolean flag = true;

            while(true) {
                // 1.提示用户输入要发送的聊天内容并使用变量记录
                System.out.println("请" + (flag ? "张三:" : "李四:") + "输入要发送的聊天内容:");
                String str = br.readLine();
                // 2.判断用户输入的内容是否为“bye”,若是则聊天结束
                if ("bye".equals(str)) {
                    System.out.println("聊天结束!");
                    break;
                }
                // 3.若不是则将用户输入的内容写入到文件e:/a.txt中
                //else {
                // 获取当前系统时间并调整格式
                Date d1 = new Date();
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                    ps.println(sdf.format(d1) + (flag ? "张三说:" : "李四说:") + str);
                //}
                flag = !flag;
            }
            ps.println();   // 写入空行与之前的聊天记录隔开
            ps.println();
            ps.println();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
        }
        // 4.关闭流对象并释放相关资源
        if (null != ps) {
            ps.close();
        }
        if (null != br) {
            try {
                br.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
OutputStreamWriter类
概念

​ java.io.OutputStreamWriter类主要用于实现从字符流到字节流的转换

常用方法
方法声明 功能介绍
OutputStreamWriter(OutputStream out) 根据参数指定的引用来构造对象
OutputStreamWriter(OutputStream out, String charsetName) 根据参数指定的引用和编码构造对象
void write(String str) 将参数指定的字符串写入
void flush() 刷新流
void close() 用于关闭输出流并释放有关的资源
InputStreamReader类
概念

​ java.io.InputStreamReader类主要用于实现从字节流到字符流的转换

常用方法
方法声明 功能介绍
InputStreamReader(InputStream in) 根据参数指定的引用来构造对象
InputStreamReader(InputStream in, String charsetName) 根据参数指定的引用和编码来构造对象
int read(char[] cbuf) 读取字符数据到参数指定的数组
void close() 用于关闭输出流并释放有关的资源
字符编码
编码表的由来

​ 计算机只能识别二进制数据,早期就是电信号。为了方便计算机可以识别各个国家的文字,就需要将各个国家的文字采用数字编号的方式进行描述并建立对应的关系表,该表就叫做编码表

常见的编码表

1.ASCII:美国标准信息交换码, 使用一个字节的低7位二位进制进行表示
2.ISO8859-1:拉丁码表,欧洲码表,使用一个字节的8位二进制进行表示
3.GB2312:中国的中文编码表,最多使用两个字节16位二进制为进行表示
4.GBK:中国的中文编码表升级,融合了更多的中文文字符号,最多使用两个字节16位二进制位表示
5.Unicode:国际标准码,融合了目前人类使用的所有字符,为每个字符分配唯一的字符码。所有的文字都用两个字节16位二进制位来表示

编码的发展

1.面向传输的众多 UTF(UCS Transfer Format)标准出现了,UTF-8就是每次8个位传输数据,而UTF-16就是每次16个位。这是为传输而设计的编码并使编码无国界,这样就可以显示全世界上所有文化的字符了

2.Unicode只是定义了一个庞大的、全球通用的字符集,并为每个字符规定了唯一确定的编号,具体存储成什么样的字节流,取决于字符编码方案。推荐的Unicode编码是UTF-8和UTF-16

3.UTF-8:变长的编码方式,可用1-4个字节来表示一个字符

DataOutputStream类(了解)
概念

​ java.io.DataOutputStream类主要用于以适当的方式将基本数据类型写入输出流中

常用方法
常用方法 功能介绍
DataOutputStream(OutputStream out) 根据参数指定的引用构造对象 OutputStream类是个抽象类,实参需要传递子类对象
void writeInt(int v) 用于将参数指定的整数一次性写入输出流,优先写入高字节
void close() 用于关闭文件输出流并释放有关的资源
public class DataOutputStreamTest {
    public static void main(String[] args) {
        DataOutputStream dos = null;

        try {
            // 1.创建DateOutputStream类型的读写与e:/a.txt文件关联
            dos = new DataOutputStream(new FileOutputStream("e:/a.txt"));
            // 2.准备一个整数数据66并写入流
            int num = 66;
            // 66: 0000 0000 ... 0100 0010  =>   B
            //dos.writeInt(num);    // 写入四个字节
            dos.write(num);      // 写入一个字节
            System.out.println("写入数据成功!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 3.关闭流对象并释放有关资源
            if (null != dos) {
                try {
                    dos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

DataInputStream类(了解)
概念

​ java.io.DataInputStream类主要用于从输入流中读取基本数据类型的数据

常用方法
方法声明 功能介绍
DataInputStream(InputStream in) 根据参数指定的引用来构造对象 InputStream类是抽象类,实参需要传递子类对象
int readInt() 用于从输入流中一次性读取一个整数数据并返回
void close() 用于关闭文件输出流并释放有关的资源
public class DataInputStreamTest {
    public static void main(String[] args) {
        DataInputStream dis = null;

        try {
            // 1.创建DataInputStream类型的对象与e:/a.txt文件关联
            dis = new DataInputStream(new FileInputStream("e:/a.txt"));
            // 2.从输入流中读取一个整数并打印
            //int res = dis.readInt();  // 读取四个字节
            int res = dis.read();       // 读取一个字节
            System.out.println("读取到的整数数据是:" + res);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 3.关闭流对象并释放有关资源
            if (null != dis) {
                try {
                    dis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
ObjectOutputStream类(重点)
概念

​ java.io.ObjectOutputStream类主要用于将一个对象的所有内容整体写入到输出流中。
​ 只能将支持 java.io.Serializable 接口的对象写入流中。
​ 类通过实现 java.io.Serializable 接口以启用其序列化功能。
​ 所谓序列化主要指将一个对象需要存储的相关信息有效组织成字节序列的转化过程

常用方法
方法声明 功能介绍
ObjectOutputStream(OutputStream out) 根据参数指定的引用来构造对象
void writeObject(Object obj) 用于将参数指定的对象整体写入到输出流中
void close() 用于关闭输出流并释放有关的资源
public class User implements java.io.Serializable{
    private static final long serialVersionUID = 750065370998709768L;
    private String userName;    // 用户名
    private String password;    // 密码
    private transient String phoneNum;    // 手机号    表示该成员变量不参与序列化操作

    public User() {
    }

    public User(String userName, String password, String phoneNum) {
        this.userName = userName;
        this.password = password;
        this.phoneNum = phoneNum;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getPhoneNum() {
        return phoneNum;
    }

    public void setPhoneNum(String phoneNum) {
        this.phoneNum = phoneNum;
    }

    @Override
    public String toString() {
        return "User{" +
                "userName='" + userName + '\'' +
                ", password='" + password + '\'' +
                ", phoneNum='" + phoneNum + '\'' +
                '}';
    }
}

public class ObjectOutpuStreamTest {
    public static void main(String[] args) {
        ObjectOutputStream oos = null;

        try {
            // 1.创建ObjectOutpuStream类型的对象与e:/a.txt文件关联
            oos = new ObjectOutputStream(new FileOutputStream("e:/a.txt"));
            // 2.准备一个User类型的对象并初始化
            User user = new User("weiwei","123456","15952037019");
            // 3.将整个User类型的对象写入输出流
            oos.writeObject(user);
            System.out.println("写入对象成功!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4.关闭流对象并释放有关资源
            if (null != oos) {
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
ObjectInputStream类(重点)
概念

​ java.io.ObjectInputStream类主要用于从输入流中一次性将对象整体读取出来
​ 所谓反序列化主要指将有效组织的字节序列恢复为一个对象及相关信息的转化过程

常用方法
方法声明 功能介绍
ObjectInputStream(InputStream in) 根据参数指定的引用来构造对象
Object readObject() 主要用于从输入流中读取一个对象并返回 无法通过返回值来判断是否读取到文件的末尾
void close() 用于关闭输入流并释放有关的资源
序列化版本号

​ 序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常(InvalidCastException)。

transient关键字

​ transient是Java语言的关键字,用来表示一个域不是该对象串行化的一部分。当一个对象被串行化的时候,transient型变量的值不包括在串行化的表示中,然而非transient型的变量是被包括进去的。

经验分享

​ 当希望将多个对象写入文件时,通常建议将多个对象放入一个集合中,然后将集合这个整体看做一个对象写入输出流中,此时只需要调用一readObject方法就可以将整个集合的数据读取出来,从而避免了通过返回值进行是否达到文件末尾的判断。

public class ObjectInputStreamTest {
    public static void main(String[] args) {
        ObjectInputStream ois = null;

        try {
            // 1.创建ObjectInputStream类型的对象与e:/a.txt文件关联
            ois = new ObjectInputStream(new FileInputStream("e:/a.txt"));
            // 2.从输入流中读取一个对象并打印
            Object obj = ois.readObject();
            System.out.println("读取到的对象是:" + obj);  // weiwei 123456 15952037019 null
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            // 3.关闭流对象并释放有关资源
            if (null != ois) {
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
RandomAccessFile类
概念

​ java.io.RandomAccessFile类主要支持对随机访问文件的读写操作

常用方法
方法声明 功能介绍
RandomAccessFile(String name, String mode) 根据参数指定的名称和模式构造对象 r: 以只读方式打开 rw:打开以便读取和写入 rwd:打开以便读取和写入,同步文件内容的更新 rws:打开以便读取和写入,同步文件内容和元数据的更新
int read() 读取单个字节的数据
void seek(long pos) 用于设置从此文件的开头开始测量的文件指针偏移量
void write(int b) 将参数指定的单个字节写入
void close() 用于关闭流并释放有关的资源
public class RandomAccessFileTest {
    public static void main(String[] args) {
        RandomAccessFile raf = null;

        try {
            // 1.创建RandomAccessFile类型的对象与e:/a.txt文件关联
            raf = new RandomAccessFile("e:/a.txt","rw");
            // 2.对文件内容进行随机读写操作
            // 设置距离文件开头位置的偏移量,从文件开头位置向后偏移3个字节   aellhello
            raf.seek(3);
            int res = raf.read();
            System.out.println("读取到的单个字符是:" + (char)res);   // a l
            res = raf.read();
            System.out.println("读取到的单个字符是:" + (char)res);   // h 指向了e

            raf.write('2'); // 执行该行代码后覆盖了字符'e'
            System.out.println("写入数据成功!");

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 3.关闭流对象并释放有关资源
            if (null != raf) {
                try {
                    raf.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

多线程

概念

程序和进程

1.程序——>数据结构 + 算法,主要指存放在硬盘上的可执行文件

2.进程——主要指运行在内存中的可执行文件

3.主流操作系统基本支持多进程,可以同时执行多个任务,进程是重量级的,会消耗CPU和内存空间等系统资源,因此进程的数量比较局限

线程

1.为了解决多进程资源消耗提出线程的概念,线程就是进程内部的程序流,即操作系统内部支持多进程,而每个进程内部又支持多线程,线程是轻量的,新建线程会共享所在进程的系统资源,因此目前主流的开发都是采用多线程

2.多线程是采用时间片轮转法来保证多个线程的并发执行,所谓并发就是指宏观并行微观串行的机制

线程的创建(重中之重)

Thread类

1.java.lang.Thread类代表线程,任何线程对象都是Thread类(子类)的实例。
2.Thread类是线程的模板,封装了复杂的线程开启等操作,封装了操作系统的差异性

创建方式

1.方式一:自定义类继承Thread类并重写run方法,然后创建该类的对象调用start方法

public class SubThreadRun extends Thread{
    @Override
    public void run() {
        // 打印1~20之间的所以有整数
        for (int i = 0; i <= 20; i++) {
            System.out.println("run方法中:i = " + i);  // 1 2 ... 20
        }
    }
}

public class SubThreadRunTest {
    public static void main(String[] args) {

        // 1.声明Thread类型的引用指向子类类型的对象
        Thread t1 = new SubThreadRun(); // 多态 调用子类重写的方法
        // 2.调用run方法测试,本质上相当于普通成员方法的调用,因此执行流程就是run方法的代码执行完毕后才能向下继续执行
        //t1.run();
        // 用于启动线程,Java虚拟机会自动调用该类线程中的run方法
        // 相当于又启动一个线程,加上执行main方法的线程就是两个线程
        t1.start();

        // 打印1~20之间的所以有整数
        for (int i = 0; i <= 20; i++) {
            System.out.println("------------main方法中:i = " + i);  // 1 2 ... 20
        }
    }
}

2.方式而:自定义类实现Runnable接口并重写run方法,创建该类的对象作为实参来构造Thread类型的对象,然后使用Thread类型的对象调用start方法

public class SubRunnableRun implements Runnable{
    @Override
    public void run() {
        // 打印1~20之间的所以有整数
        for (int i = 0; i <= 20; i++) {
            System.out.println("run方法中:i = " + i);  // 1 2 ... 20
        }
    }
}


public class SubRunnableRunTest {
    public static void main(String[] args) {

        // 1.创建自定义类类型的对象,即实现Runnable接口类的对象
        SubRunnableRun srr = new SubRunnableRun();
        // 2.使用该对象作为实参构造Thread类型的对象
        // 由源码可知:经过构造方法的调用之后,Thread类中的成员变量target数值为srr
        Thread t1 = new Thread(srr);
        // 3.使用Thread类型的对象调用start方法
        // 若使用Runnable引用构造了线程对象,调用该方法时最终调用接口中的版本
        // 由run方法的源码可知:public void run() {
        //                       if (target != null) {
        //                           target.run();
        //                        }
        //                   }
        // 此时target的数值不为空条件成立,执行target.run()的代码,即srr.run();
        t1.start();

        // 打印1~20之间的所以有整数
        for (int i = 0; i <= 20; i++) {
            System.out.println("------------main方法中:i = " + i);  // 1 2 ... 20
        }
    }
}
相关方法
方法声明 功能介绍
Thread() 使用无参的方式构造对象
Thread(String name) 根据参数指定的名称来构造对象
Thread(Runnable target) 根据参数指定的引用来构造对象,其中Runnable是个接口类型
Thread(Runnable target, String name) 根据参数指定引用和名称来构造对象
void run() 1.若使用Runnable引用构造了线程对象,调用该方法时最终调用接口中的版本 2.若没有使用Runnable引用构造线程对象,调用该方法时则啥也不做
void start() 用于启动线程,Java虚拟机会自动调用该线程的run方法
public class ThreadTest {
    public static void main(String[] args) {

        // 1.使用无参方式构造Thread类型的对象
        // 由源码可知:Thread类中的成员变量target的数值为null
        Thread t1 = new Thread();
        // 2.使用run方法进行测试
        // 由源码可知:由于成员变量target的数值为null,因此条件 if (target != null)不成立,跳过{}中的代码不执行
        // 而run方法中除了上述代码再无代码,因此证明run方法确实啥也不干
        t1.run();
        // 3.打印一句话
        System.out.println("我想看看你是否真的啥也不干!");
    }
}
执行流程

1.执行main方法的线程叫做主线程,执行run方法的线程叫做新线程/子线程
2.main方法是程序的入口,对于start方法之前的代码来说,由主线程执行一次,当start方法调用成功后线程的个数由1个变成了2个,新启动的线程去执行run方法的代码,主线程继续向下执行,两个线程各自独立运行互不影响
3.当run方法执行完毕后子线程结束,当main方法执行完毕后主线程结束
4.两个线程执行没有明确的先后执行次序,由操作系统调度算法来决定

方式的比较

1.继承Thread类的方式代码简单,但是若该类继承Thread类后则无法继承其它类

2.实现Runnable接口的方式代码复杂,但不影响该类继承其它类以及实现其它接口,因此以后的开发中推荐使用第二种方式

匿名内部类的方式

使用匿名内部类的方式来创建和启动线程

public class ThreadNoNameTest {
    public static void main(String[] args) {

        // 匿名内部类的语法格式:父类/接口类型 引用变量名 = new 父类/接口类型() { 方法的重写 };
        // 1.使用继承加匿名内部类的方式创建并启动线程
        /*Thread t1 = new Thread(){
            @Override
            public void run() {
                System.out.println("张三说:在吗?");
            }
        };
        t1.start();*/
        new Thread(){
            @Override
            public void run() {
                System.out.println("张三说:在吗?");
            }
        }.start();

        // 2.使用实现接口加匿名内部类的方式创建并启动线程
        /*Runnable ra = new Runnable() {
            @Override
            public void run() {
                System.out.println("李四说:不在。");
            }
        };
        Thread t2 = new Thread(ra);
        t2.start();*/

        /*new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("李四说:不在。");
            }
        }).start();*/

        // Java8开始支持lambda表达式:(形参列表) -> {方法体;}
        /*Runnable ra = () -> System.out.println("李四说:不在。");
        new Thread(ra).start();*/

        new Thread(() -> System.out.println("李四说:不在。")).start();
    }
}

线程的生命周期(熟悉)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XmI4TQj2-1619445111996)(E:\MarkDown\拉勾笔记\线程生命周期)]

1.新建状态——使用new关键字创建之后进入的状态,此时线程并没有开始执行。
2.就绪状态——调用start方法后进入的状态,此时线程还是没有开始执行。
3.运行状态——使用线程调度器调用该线程后进入的状态,此时线程开始执行,当线程的时间片执行完毕后任务没有完成时回到就绪状态。
4.消亡状态——当线程的任务执行完成后进入的状态,此时线程已经终止。
5.阻塞状态——当线程执行的过程中发生了阻塞事件进入的状态,如:sleep方法。
6.阻塞状态解除后进入就绪状态

程的编号和名称(熟悉)

方法声明 功能介绍
long getId() 获取调用对象所表示线程的编号
String getName() 获取调用对象所表示线程的名称
void setName(String name) 设置/修改线程的名称为参数指定的数值
static Thread currentThread() 获取当前正在执行线程的引用

案例题目
自定义类继承Thread类并重写run方法,在run方法中先打印当前线程的编号和名称,然后将线程的名称修改为"zhangfei"后再次打印编号和名称。
要求在main方法中也要打印主线程的编号和名称。

public class ThreadIdNameTest extends Thread{
    public ThreadIdNameTest(String name){
        super(name);    // 表示调用父类的构造方法
    }
    @Override
    public void run() {
        System.out.println("子线程编号为:" + getId() + ",名称为:" + getName());   // 13  guanyu
        // 修改名称为"zhangfei"
        setName("zhangfei");
        System.out.println("子线程编号为:" + getId() + ",名称为:" + getName());   // 13  zhangfei

    }

    public static void main(String[] args) {

        ThreadIdNameTest tint = new ThreadIdNameTest("guanyu");
        tint.start();

        // 获取当前正在执行线程的引用,当前正在执行的线程是主线程,也就是获取主线程的引用
        Thread t1 = Thread.currentThread();
        System.out.println("主线程的编号是:" + t1.getId() + ",名称是:" + t1.getName());
    }
}


public class RunnableIdNameTest implements Runnable{
    @Override
    public void run() {
        // 获取当前正在执行线程的引用,也就是子线程的引用
        Thread t1 = Thread.currentThread();
        System.out.println("子线程编号为:" + t1.getId() + ",名称为:" + t1.getName());  // 13 guanyu
        t1.setName("zhangfei");
        System.out.println("修改后子线程编号为:" + t1.getId() + ",名称为:" + t1.getName());  // 13 zhangfei


    }

    public static void main(String[] args) {

        RunnableIdNameTest rint = new RunnableIdNameTest();
        //Thread t2 = new Thread(rint);
        Thread t2 = new Thread(rint,"guanyu");
        t2.start();

        // 获取当前正在执行线程的引用,当前正在执行的线程是主线程,也就是获取主线程的引用
        Thread t1 = Thread.currentThread();
        System.out.println("主线程的编号是:" + t1.getId() + ",名称是:" + t1.getName());   // 1 main
    }
}

常用的方法(重点)

方法声明 功能介绍
static void yield() 当前线程让出处理器(离开Running状态),使当前线程进入Runnable状态等待
static void sleep(times) 使当前线程从 Running 放弃处理器进入Block状态, 休眠times毫秒, 再返回到Runnable如果其他线程打断当前线程的Block(sleep), 就会发生InterruptedException
int getPriority() 获取线程的优先级
void setPriority(int newPriority) 修改线程的优先级。 优先级越高的线程不一定先执行,但该线程获取到时间片的机会会更多一些
void join() 等待该线程终止
void join(long millis) 等待参数指定的毫秒数
boolean isDaemon() 用于判断是否为守护线程
void setDaemon(boolean on) 用于设置线程为守护线程
/**
 * @auther weiwei
 * @date 2021/4/24 15:15
 * @description     sleep方法使用
 */
public class ThreadSleepTest extends Thread{

    // 声明一个boolean类型的变量作为循环是否执行的条件
    private boolean flag = true;

    //  子类中重写的方法不能抛出更大的异常
    @Override
    public void run() {
        // 每隔一秒获取一次系统时间并打印,模拟时钟的效果
        while(flag){
            // 获取当前系统时间并调整格式打印
//            LocalDateTime.now();
            Date d1 = new Date();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            System.out.println(sdf.format(d1));

            // 睡眠1秒钟
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        ThreadSleepTest tst = new ThreadSleepTest();
        tst.start();

        // 主线程等待3秒后结束子线程
        System.out.println("主线程开始等待...");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 停止子线程 过时 不建议使用
        //tst.stop();
        tst.flag = false;
        System.out.println("主线程等待结束!");
    }
}
/**
 * @auther weiwei
 * @date 2021/4/24 15:28
 * @description     线程优先级管理
 */
public class ThreadPriorityTest extends Thread{

    @Override
    public void run() {
        //System.out.println("子线程优先级是:" + getPriority());  // 5 10  优先级越高的线程不一定先执行
        for (int i = 0; i < 20; i++) {
            System.out.println("子线程中:i = " + i);
        }
    }

    public static void main(String[] args) {

        ThreadPriorityTest tpt = new ThreadPriorityTest();
        // 设置子线程优先级
        tpt.setPriority(Thread.MAX_PRIORITY);
        tpt.start();

        Thread t1 = Thread.currentThread();
        //System.out.println("主线程优先级是:" + t1.getPriority());  // 5 普通优先级
        for (int i = 0; i < 20; i++) {
            System.out.println("-----主线程中:i = " + i);
        }
    }

}
/**
 * @auther weiwei
 * @date 2021/4/24 15:41
 * @description     线程等待
 */
public class ThreadJoinTest extends Thread{
    @Override
    public void run() {
        // 模拟倒数10个数的效果
        System.out.println("倒计时开始!");
        for (int i = 10; i > 0; i--) {
            System.out.println(i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("新年快乐!");
    }

    public static void main(String[] args) {

        ThreadJoinTest tjt = new ThreadJoinTest();
        tjt.start();

        // 主线程开始等待
        System.out.println("主线程开始等待...");
        try {
            // 表示当前正在执行的线程对象等待调用线程对象,也就是主线程等待子线程终止
            //tjt.join();
            tjt.join(5000); // 最多等待5秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //System.out.println("终于等到你,还好没放弃!");
        System.out.println("可惜不是你,陪我到最后!");
    }
}
/**
 * @auther weiwei
 * @date 2021/4/24 15:49
 * @description     守护线程
 */
public class ThreadDaemonTest extends Thread{
    @Override
    public void run() {
        //System.out.println(isDaemon() ? "该线程是守护线程" : "该线程不是守护线程");  // 默认不是守护线程
        // 当子线程不是守护线程时,虽然主线程先结束了,但是子线程依然会继续执行,直到打印完毕所有数据为止
        // 当子线程是守护线程时,当主线程结束后,则子线程随之结束
        for (int i = 0; i < 50; i++) {
            System.out.println("子线程中:i = " + i);
        }
    }

    public static void main(String[] args) {

        ThreadDaemonTest tdt = new ThreadDaemonTest();
        // 必须在线程启动之前把子线程设置为守护线程
        tdt.setDaemon(true);
        tdt.start();

        for (int i = 0; i < 20; i++) {
            System.out.println("---------------主线程中:i = " + i);
        }
    }
}

案例题目
编程创建两个线程,线程一负责打印1 ~ 100之间的所有奇数,其中线程二负责打印1 ~ 100之间的所有偶数。
在main方法启动上述两个线程同时执行,主线程等待两个线程终止。

public class SubThread1 extends Thread{
    @Override
    public void run() {
        // 打印1 ~ 100之间的所有奇数
        for (int i = 1; i <= 100; i += 2) {
            System.out.println("子线程1中:i = " + i);
        }
    }
}

public class SubThread2 extends Thread{
    @Override
    public void run() {
        // 打印1 ~ 100之间的所有偶数
        for (int i = 2; i <= 100; i += 2) {
            System.out.println("----子线程2中:i = " + i);
        }
    }
}

public class SubThreadTest {

    public static void main(String[] args) {

        SubThread1 st1 = new SubThread1();
        SubThread2 st2 = new SubThread2();

        st1.start();
        st2.start();

        System.out.println("主线程开始等待...");
        try {
            st1.join();
            st2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主线程等待结束!");
    }
}


public class SubRunnable1 implements Runnable{
    @Override
    public void run() {
        // 打印1 ~ 100之间的所有奇数
        for (int i = 1; i <= 100; i += 2) {
            System.out.println("子线程1中:i = " + i);
        }
    }
}

public class SubRunnable2 implements Runnable{
    @Override
    public void run() {
        // 打印1 ~ 100之间的所有偶数
        for (int i = 2; i <= 100; i += 2) {
            System.out.println("----子线程2中:i = " + i);
        }
    }
}

public class SubRunnableTest {

    public static void main(String[] args) {

        SubRunnable1 sr1 = new SubRunnable1();
        SubRunnable2 sr2 = new SubRunnable2();

        Thread t1 = new Thread(sr1);
        Thread t2 = new Thread(sr2);

        t1.start();
        t2.start();

        System.out.println("主线程开始等待...");
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主线程等待结束!");
    }
}

线程同步机制(重点)

概念

1.当多个线程同时访问同一种共享资源时,可能会造成数据的覆盖等不一致性问题,此时就需要对线程之间进行通信和协调,该机制就叫做线程的同步机制。
2.多个线程并发读写同一个临界资源时会发生线程并发安全问题。
3.异步操作:多线程并发的操作,各自独立运行。
4.同步操作:多线程串行的操作,先后执行的顺序

解决方案

1.由程序结果可知:当两个线程同时对同一个账户进行取款时,导致最终的账户余额不合理。
2.引发原因:线程一执行取款时还没来得及将取款后的余额写入后台,线程二就已经开始取款。
3.解决方案:让线程一执行完毕取款操作后,再让线程二执行即可,将线程的并发操作改为串行操作。
4.经验分享:在以后的开发尽量减少串行操作的范围,从而提高效率

public class AccountRunnableTest implements Runnable{

    private int balance;    // 用于描述账户余额
    private Demo dm = new Demo();

    public AccountRunnableTest() {
    }

    public AccountRunnableTest(int balance) {
        this.balance = balance;
    }

    public int getBalance() {
        return balance;
    }

    public void setBalance(int balance) {
        this.balance = balance;
    }

    @Override
    public void run() {
        System.out.println("线程" + Thread.currentThread().getName() + "已启动...");
        synchronized (dm) { // ok
        //synchronized (new Demo()) { // 锁不住 要求必须是同一个对象
            // 1.模拟从后台查询账户余额的过程
            int temp = getBalance();
            // 2.模拟取款200元的过程
            if (temp > 200){
                System.out.println("正在出钞,请稍等...");
                temp -= 200;
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("请取走您的钞票!");
            }else {
                System.out.println("余额不足,请核对您的账余额!");
            }
            // 3.模拟将最新的账户余额写入掉后台
            setBalance(temp);
        }
    }

    public static void main(String[] args) {

        AccountRunnableTest account = new AccountRunnableTest(1000);
        Thread t1 = new Thread(account);
        Thread t2 = new Thread(account);
        t1.start();
        t2.start();

        System.out.println("主线程开始等待...");
        try {
            t1.join();
            //t2.start(); // 等待线程1取款操作结束后在启动线程二
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("最终的账户余额为:" + account.getBalance()); // 800  600
    }
}

class Demo{

}
实现方式

​ 在Java语言中使用synchronized关键字来实现同步/对象锁机制从而保证线程执行的原子性,如下:

​ 1.使用同步代码块的方式实现部分代码的锁定,格式如下:
​ synchronized(类类型的引用) {
​ 编写所有需要锁定的代码;
​ }

public class AccountRunnableTest implements Runnable{

    private int balance;    // 用于描述账户余额
    private Demo dm = new Demo();

    public AccountRunnableTest() {
    }

    public AccountRunnableTest(int balance) {
        this.balance = balance;
    }

    public int getBalance() {
        return balance;
    }

    public void setBalance(int balance) {
        this.balance = balance;
    }

    @Override
    public void run() {
        System.out.println("线程" + Thread.currentThread().getName() + "已启动...");
        synchronized (dm) { // ok
        //synchronized (new Demo()) { // 锁不住 要求必须是同一个对象
            // 1.模拟从后台查询账户余额的过程
            int temp = getBalance();
            // 2.模拟取款200元的过程
            if (temp > 200){
                System.out.println("正在出钞,请稍等...");
                temp -= 200;
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("请取走您的钞票!");
            }else {
                System.out.println("余额不足,请核对您的账余额!");
            }
            // 3.模拟将最新的账户余额写入掉后台
            setBalance(temp);
        }
    }

    public static void main(String[] args) {

        AccountRunnableTest account = new AccountRunnableTest(1000);
        Thread t1 = new Thread(account);
        Thread t2 = new Thread(account);
        t1.start();
        t2.start();

        System.out.println("主线程开始等待...");
        try {
            t1.join();
            //t2.start(); // 等待线程1取款操作结束后在启动线程二
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("最终的账户余额为:" + account.getBalance()); // 800  600
    }
}

class Demo{

}


public class AccountThreadTest extends Thread{

    private int balance;    // 用于描述账户余额
    private static Demo dm = new Demo();    // 隶属于类层级,所有对象共享同一个

    public AccountThreadTest() {
    }

    public AccountThreadTest(int balance) {
        this.balance = balance;
    }

    public int getBalance() {
        return balance;
    }

    public void setBalance(int balance) {
        this.balance = balance;
    }

    @Override
    public void run() {
        System.out.println("线程" + Thread.currentThread().getName() + "已启动...");
        synchronized (dm) { // ok
            //synchronized (new Demo()) { // 锁不住 要求必须是同一个对象
            // 1.模拟从后台查询账户余额的过程
            int temp = getBalance();
            // 2.模拟取款200元的过程
            if (temp > 200){
                System.out.println("正在出钞,请稍等...");
                temp -= 200;
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("请取走您的钞票!");
            }else {
                System.out.println("余额不足,请核对您的账余额!");
            }
            // 3.模拟将最新的账户余额写入掉后台
            setBalance(temp);
        }
    }

    public static void main(String[] args) {

        AccountThreadTest att1 = new AccountThreadTest(1000);
        att1.start();

        AccountThreadTest att2 = new AccountThreadTest(1000);
        att2.start();

        System.out.println("主线程开始等待...");
        try {
            att1.join();
            //t2.start(); // 等待线程1取款操作结束后在启动线程二
            att2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("最终的账户余额为:" + att1.getBalance()); // 800
    }
}

​ 2.使用同步方法的方式实现所有代码的锁定
​ 直接使用synchronized关键字来修饰整个方法即可
​ 该方式等价于:
​ synchronized(this) { 整个方法体的代码 }

public class AccountRunnableTest implements Runnable{

    private int balance;    // 用于描述账户余额
    private Demo dm = new Demo();

    public AccountRunnableTest() {
    }

    public AccountRunnableTest(int balance) {
        this.balance = balance;
    }

    public int getBalance() {
        return balance;
    }

    public void setBalance(int balance) {
        this.balance = balance;
    }

    @Override
    public synchronized void run() {
        // 由源码可知:最终是account对象来调用run方法,因此当前正在调用的对象就是account,也就是说this就是account
        //synchronized (this) { // ok
        System.out.println("线程" + Thread.currentThread().getName() + "已启动...");
        //synchronized (dm) { // ok
        //synchronized (new Demo()) { // 锁不住 要求必须是同一个对象
            // 1.模拟从后台查询账户余额的过程
            int temp = getBalance();
            // 2.模拟取款200元的过程
            if (temp > 200){
                System.out.println("正在出钞,请稍等...");
                temp -= 200;
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("请取走您的钞票!");
            }else {
                System.out.println("余额不足,请核对您的账余额!");
            }
            // 3.模拟将最新的账户余额写入掉后台
            setBalance(temp);
        //}
    }

    public static void main(String[] args) {

        AccountRunnableTest account = new AccountRunnableTest(1000);
        //AccountRunnableTest account2 = new AccountRunnableTest(1000);
        Thread t1 = new Thread(account);
        Thread t2 = new Thread(account);
        //Thread t2 = new Thread(account2);
        t1.start();
        t2.start();

        System.out.println("主线程开始等待...");
        try {
            t1.join();
            //t2.start(); // 等待线程1取款操作结束后在启动线程二
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("最终的账户余额为:" + account.getBalance()); // 800  600
    }
}

class Demo{

}


public class AccountThreadTest extends Thread{

    private int balance;    // 用于描述账户余额
    private static Demo dm = new Demo();    // 隶属于类层级,所有对象共享同一个

    public AccountThreadTest() {
    }

    public AccountThreadTest(int balance) {
        this.balance = balance;
    }

    public int getBalance() {
        return balance;
    }

    public void setBalance(int balance) {
        this.balance = balance;
    }

    @Override
    public /*static*/ /*synchronized*/ void run() {
        /*System.out.println("线程" + Thread.currentThread().getName() + "已启动...");
        //synchronized (dm) { // ok
            //synchronized (new Demo()) { // 锁不住 要求必须是同一个对象
            // 1.模拟从后台查询账户余额的过程
            int temp = getBalance();
            // 2.模拟取款200元的过程
            if (temp > 200){
                System.out.println("正在出钞,请稍等...");
                temp -= 200;
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("请取走您的钞票!");
            }else {
                System.out.println("余额不足,请核对您的账余额!");
            }
            // 3.模拟将最新的账户余额写入掉后台
            setBalance(temp);
        //}*/

        test();

    }

    public /*synchronized*/ static void test(){
        synchronized (AccountThreadTest.class) {    // 该类型对应的Class对象,由于类型是固定的,因此Class对象也是唯一的因此可以实现同步
        System.out.println("线程" + Thread.currentThread().getName() + "已启动...");
        //synchronized (dm) { // ok
        //synchronized (new Demo()) { // 锁不住 要求必须是同一个对象
        // 1.模拟从后台查询账户余额的过程
        int temp = 1000;    //getBalance();
        // 2.模拟取款200元的过程
        if (temp > 200){
            System.out.println("正在出钞,请稍等...");
            temp -= 200;
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("请取走您的钞票!");
        }else {
            System.out.println("余额不足,请核对您的账余额!");
        }
        // 3.模拟将最新的账户余额写入掉后台
        //setBalance(temp);
        }
    }

    public static void main(String[] args) {

        AccountThreadTest att1 = new AccountThreadTest(1000);
        att1.start();

        AccountThreadTest att2 = new AccountThreadTest(1000);
        att2.start();

        System.out.println("主线程开始等待...");
        try {
            att1.join();
            //t2.start(); // 等待线程1取款操作结束后在启动线程二
            att2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("最终的账户余额为:" + att1.getBalance()); // 800
    }
静态方法的锁定

1.当我们对一个静态方法加锁,如:
public synchronized static void xxx(){….}
2.该方法锁的对象是类对象。每个类都有唯一的一个类对象。获取类对象的方式:类名.class。
3.静态方法与非静态方法同时使用了synchronized后它们之间是非互斥关系的。
4.原因在于:静态方法锁的是类对象而非静态方法锁的是当前方法所属对象

注意事项

使用synchronized保证线程同步应当注意:
多个需要同步的线程在访问同步块时,看到的应该是同一个锁对象引用。
在使用同步块时应当尽量减少同步范围以提高并发的执行效率。

线程安全类和不安全类

1.StringBuffer类是线程安全的类,但StringBuilder类不是线程安全的类
2.Vector类和 Hashtable类是线程安全的类,但ArrayList类和HashMap类不是线程安全的类
3.Collections.synchronizedList() 和 Collections.synchronizedMap()等方法实现安全

死锁的概念

线程一执行的代码:
public void run(){
synchronized(a){ //持有对象锁a,等待对象锁b
synchronized(b){
编写锁定的代码;
}
}
}

线程二执行的代码:
public void run(){
synchronized(b){ //持有对象锁b,等待对象锁a
synchronized(a){
编写锁定的代码;
}
}
}

注意:
在以后的开发中尽量减少同步的资源,减少同步代码块的嵌套结构的使用!

使用Lock(锁)实现线程同步
概念

1.从Java5开始提供了更强大的线程同步机制—使用显式定义的同步锁对象来实现。
2.java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。
3.该接口的主要实现类是ReentrantLock类,该类拥有与synchronized相同的并发性,在以后的线程安全控制中,经常使用ReentrantLock类显式加锁和释放锁

常用方法
方法声明 功能介绍
ReentrantLock() 使用无参方式构造对象
void lock() 获取锁
void unlock() 释放锁
public class AccountRunnableTest implements Runnable{

    private int balance;    // 用于描述账户余额
    private Demo dm = new Demo();
    private ReentrantLock lock = new ReentrantLock();

    public AccountRunnableTest() {
    }

    public AccountRunnableTest(int balance) {
        this.balance = balance;
    }

    public int getBalance() {
        return balance;
    }

    public void setBalance(int balance) {
        this.balance = balance;
    }

    @Override
    public /*synchronized*/ void run() {
        // 开始加锁
        lock.lock();

        // 由源码可知:最终是account对象来调用run方法,因此当前正在调用的对象就是account,也就是说this就是account
        //synchronized (this) { // ok
        System.out.println("线程" + Thread.currentThread().getName() + "已启动...");
        //synchronized (dm) { // ok
        //synchronized (new Demo()) { // 锁不住 要求必须是同一个对象
            // 1.模拟从后台查询账户余额的过程
            int temp = getBalance();
            // 2.模拟取款200元的过程
            if (temp > 200){
                System.out.println("正在出钞,请稍等...");
                temp -= 200;
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("请取走您的钞票!");
            }else {
                System.out.println("余额不足,请核对您的账余额!");
            }
            // 3.模拟将最新的账户余额写入掉后台
            setBalance(temp);
        //}

        lock.unlock();
    }

    public static void main(String[] args) {

        AccountRunnableTest account = new AccountRunnableTest(1000);
        //AccountRunnableTest account2 = new AccountRunnableTest(1000);
        Thread t1 = new Thread(account);
        Thread t2 = new Thread(account);
        //Thread t2 = new Thread(account2);
        t1.start();
        t2.start();

        System.out.println("主线程开始等待...");
        try {
            t1.join();
            //t2.start(); // 等待线程1取款操作结束后在启动线程二
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("最终的账户余额为:" + account.getBalance()); // 800  600
    }
}

class Demo{

}
与synchronized方式的比较

1.Lock是显式锁,需要手动实现开启和关闭操作,而synchronized是隐式锁,执行锁定代码后自动释放。
2.Lock只有同步代码块方式的锁,而synchronized有同步代码块方式和同步方法两种锁。
3.使用Lock锁方式时,Java虚拟机将花费较少的时间来调度线程,因此性能更好。

Object类常用的方法
方法声明 功能介绍
void wait() 用于使得线程进入等待状态,直到其它线程调用notify()或notifyAll()方法
void wait(long timeout) 用于进入等待状态,直到其它线程调用方法或参数指定的毫秒数已经过去为止
void notify() 用于唤醒等待的单个线程
void notifyAll() 用于唤醒等待的所有线程

线程之间的通信

public class ThreadCommunicateTest implements Runnable{
    private int cnt = 1;

    @Override
    public void run() {
        while(true){
            synchronized (this) {
                // 每当有一个线程进来后,调用notify方法唤醒等待的单个线程
                notify();
                if (cnt <= 100){
                    System.out.println("线程" + Thread.currentThread().getName() + "中:cnt = " + cnt);
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    cnt++;
                    // 当前线程打印完毕一个整数后,为了防止继续打印下一个数据,则调用wait方法
                    try {
                        wait(); // 当前线程进入阻塞状态,自动释放对象锁,必须在锁定的代码中调用
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else {
                    break;
                }
            }
        }
    }

    public static void main(String[] args) {
        ThreadCommunicateTest tct = new ThreadCommunicateTest();
        Thread t1 = new Thread(tct);
        t1.start();

        Thread t2 = new Thread(tct);
        t2.start();
    }
}
生产消费者模型
/**
 * @auther weiwei
 * @date 2021/4/24 19:19
 * @description     编程实现仓库类
 */
public class StoreHouse {
    private int cnt = 0;    // 用于记录产品的数量

    public synchronized void produceProduct() {
        notify();
        if (cnt < 10){
            System.out.println("线程" + Thread.currentThread().getName() + "正在生产第" + (cnt+1) + "个产品...");
            cnt++;
        }else {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public synchronized void comsumerProduct() {
        notify();
        if (cnt > 0){
            System.out.println("线程" + Thread.currentThread().getName() + "消费了" + cnt + "个产品...");
            cnt--;
        } else {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

/**
 * @auther weiwei
 * @date 2021/4/24 19:21
 * @description     编程实现生产者线程,不断生产产品
 */
public class ProduceThread extends Thread{
    // 声明一个仓库类型的引用作为成员变量,是为了能调用仓库类中的生产方法    合成复用原则
    private StoreHouse storeHouse;

    // 为了确保两个线程共用同一个仓库
    public ProduceThread(StoreHouse storeHouse){
        this.storeHouse = storeHouse;
    }

    @Override
    public void run() {
        while (true){
            storeHouse.produceProduct();
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}


public class ComsumerThread extends Thread{
    // 声明一个仓库类型的引用作为成员变量,是为了能调用仓库类中的生产方法    合成复用原则
    private StoreHouse storeHouse;

    // 为了确保两个线程共用同一个仓库
    public ComsumerThread(StoreHouse storeHouse){
        this.storeHouse = storeHouse;
    }

    @Override
    public void run() {
        while (true){
            storeHouse.comsumerProduct();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}


public class StoreHouseTest {

    public static void main(String[] args) {

        // 创建仓库类对象
        StoreHouse storeHouse = new StoreHouse();

        // 创建线程类对象
        ProduceThread t1 = new ProduceThread(storeHouse);
        ComsumerThread t2 = new ComsumerThread(storeHouse);
        t1.start();
        t2.start();
    }
}
线程池(熟悉)
实现Callable接口

从Java5开始新增加创建线程的第三种方式为实现java.util.concurrent.Callable接口

常用的方法如下:

V call() ——计算结果并返回

FutureTask类

​ java.util.concurrent.FutureTask类用于描述可取消的异步计算,该类提供了Future接口的基本实现,包括启动和取消计算、查询计算是否完成以及检索计算结果的方法,也可以用于获取方法调用后的返回结果

常用方法

方法声明 功能介绍
FutureTask(Callable callable) 根据参数指定的引用来创建一个未来任务
V get() 获取call方法计算的结果
public class ThreadCallableTest implements Callable {
    @Override
    public Object call() throws Exception {
        // 计算1~10000之间的累加和并打印
        int sum = 0;
        for (int i = 1; i <= 10000; i++) {
            sum +=i;
        }
        System.out.println("计算的结果是:" + sum);    // 50005000
        return sum;
    }

    public static void main(String[] args) {

        ThreadCallableTest tct = new ThreadCallableTest();
        FutureTask ft = new FutureTask(tct);
        Thread t1 = new Thread(ft);
        t1.start();
        Object obj = null;
        try {
            obj = ft.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println("线程处理方法的返回值是:" + obj);   // 50005000
    }
}
线程池的由来

​ 在服务器编程模型的原理,每一个客户端连接用一个单独的线程为之服务,当与客户端的会话结束时,线程也就结束了,即每来一个客户端连接,服务器端就要创建一个新线程。
​ 如果访问服务器的客户端很多,那么服务器要不断地创建和销毁线程,这将严重影响服务器的性能。

概念和原理

1.线程池的概念:首先创建一些线程,它们的集合称为线程池,当服务器接受到一个客户请求后,就从线程池中取出一个空闲的线程为之服务,服务完后不关闭该线程,而是将该线程还回到线程池中。
2.在线程池的编程模式下,任务是提交给整个线程池,而不是直接交给某个线程,线程池在拿到任务后,它就在内部找有无空闲的线程,再把任务交给内部某个空闲的线程,任务是提交给整个线程池,一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务。

相关类和方法

1.从Java5开始提供了线程池的相关类和接口:java.util.concurrent.Executors类和java.util.concurrent.ExecutorService接口。

2.Executors是个工具类和线程池的工厂类,可以创建并返回不同类型的线程池,常用方法如下:

方法声明 功能介绍
static ExecutorService newCachedThreadPool() 创建一个可根据需要创建新线程的线程池
static ExecutorService newFixedThreadPool(int nThreads) 创建一个可重用固定线程数的线程池
static ExecutorService newSingleThreadExecutor() 创建一个只有一个线程的线程池

3.ExecutorService接口是真正的线程池接口,主要实现类是ThreadPoolExecutor,常用方法如下:

方法声明 功能介绍
void execute(Runnable command) 执行任务和命令,通常用于执行Runnable
Future submit(Callable task) 执行任务和命令,通常用于执行Callable
void shutdown() 启动有序关闭
public class ThreadPoolTest {

    public static void main(String[] args) {

        // 1.创建一个线程池
        ExecutorService es = Executors.newFixedThreadPool(10);
        // 2.向线程池中布置任务
        es.submit(new ThreadCallableTest());
        // 3.关闭线程池
        es.shutdown();
    }
}


// 结果
计算的结果是:50005000

网络编程

网络编程常识

七层网络模型

OSI(Open System Interconnect),即开放式系统互联,是ISO(国际标准化组织)组织在1985年研究的网络互连模型

OSI七层模型和TCP/IP五层模型的划分如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-trZmJ7t6-1619445112004)(E:\MarkDown\拉勾笔记\网络模型.png)]

1.发送数据:对发送的内容按照上述七层模型进行层层加包后发送出去
2.接收数据:对接收的内容按照上述七层模型相反的次序层层拆包并显示出来

相关的协议(笔试题)
协议的概念

​ 计算机在网络中实现通信就必须有一些约定或者规则,这种约定和规则就叫做通信协议,通信协议可以对速率、传输代码、代码结构、传输控制步骤、出错控制等制定统一的标准。

TCP协议

传输控制协议(Transmission Control Protocol),是一种面向连接的协议,类似于打电话。

1.建立连接 => 进行通信 => 断开连接
2.在传输前采用"三次握手"方式。
3.在通信的整个过程中全程保持连接,形成数据传输通道。
4.保证了数据传输的可靠性和有序性。
5.是一种全双工的字节流通信方式,可以进行大数据量的传输。
6.传输完毕后需要释放已建立的连接,发送数据的效率比较低。

UDP协议

用户数据报协议(User Datagram Protocol),是一种非面向连接的协议,类似于写信。

1.在通信的整个过程中不需要保持连接,其实是不需要建立连接。
2.不保证数据传输的可靠性和有序性。
3.是一种全双工的数据报通信方式,每个数据报的大小限制在64K内。
4.发送数据完毕后无需释放资源,开销小,发送数据的效率比较高,速度快

IP地址(重点)

1.192.168.1.1 - 是绝大多数路由器的登录地址,主要配置用户名和密码以及Mac过滤。

2.IP地址是互联网中的唯一地址标识,本质上是由32位二进制组成的整数,叫做IPv4,当然也有128位二进制组成的整数,叫做IPv6,目前主流的还IPv4。

3.日常生活中采用点分十进制表示法来进行IP地址的描述,将每个字节的二进制转化为一个十进制整数,不同的整数之间采用小数点隔开。

4.如:0x01020304 => 1.2.3.4

5.查看IP地址的方式:
Windows系统:在dos窗口中使用ipconfig或ipconfig/all命令即可
Unix/linux系统:在终端窗口中使用ifconfig或/sbin/ifconfig命令即可

6.特殊的地址
本地回环地址(hostAddress):127.0.0.1 主机名(hostName):localhost

端口号(重点)

1.IP地址——可以定位到具体某一台设备。
2.端口号——可以定位到该设备中具体某一个进程。
3.端口号本质上是16位二进制组成的整数,表示范围是:0 ~ 65535,其中0 ~ 1024之间的端口号通常被系统占用,建议编程从1025开始使用。
4.特殊的端口:
HTTP:80 FTP:21 Oracle:1521 MySQL:3306 Tomcat:8080
5.网络编程需要提供:IP地址 + 端口号,组合在一起叫做网络套接字:Socket。

基于tcp协议的编程模型(重点)

C/S架构的简介

1.在C/S模式下客户向服务器发出服务请求,服务器接收请求后提供服务。
2.例如:在一个酒店中,顾客找服务员点菜,服务员把点菜单通知厨师,厨师按点菜单做好菜后让服务员端给客户,这就是一种C/S工作方式。如果把酒店看作一个系统,服务员就是客户端,厨师就是服务器。这种系统分工和协同工作的方式就是C/S的工作方式。
3.客户端部分:为每个用户所专有的,负责执行前台功能。
4.服务器部分:由多个用户共享的信息与功能,招待后台服务。

编程模型

服务器:
(1)创建ServerSocket类型的对象并提供端口号;
(2)等待客户端的连接请求,调用accept()方法;
(3)使用输入输出流进行通信;
(4)关闭Socket;

客户端:
(1)创建Socket类型的对象并提供服务器的IP地址和端口号;
(2)使用输入输出流进行通信;
(3)关闭Socket;

public class ServerStringTest {

    public static void main(String[] args) {
        ServerSocket ss = null;
        Socket s = null;

        try {
            // 1.创建ServerSocket类型的对象并提供端口号
            ss = new ServerSocket(8888);

            // 2.等待客户端的连接请求,调用accept方法
            System.out.println("等待客户端的连接请求...");
            // 当没有客户端连接时,则服务器阻塞在accept方法的调用这里
            s = ss.accept();
            System.out.println("客户端连接成功!");
            // 3.使用输入输出流进行通信
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
        }
        // 4.关闭Socket并释放有关资源
        if (null != s) {
            try {
                s.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (null != ss) {
            try {
                ss.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

public class ClientStringTest {

    public static void main(String[] args) {
        Socket s = null;

        try {
            // 1.创建Socket类的对象并提供服务器主机名或IP地址和端口号
            s = new Socket("127.0.0.1",8888);
            System.out.println("连接服务器成功!");
            // 2.使用输入输出流进行通信
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 3.关闭Socket对象并释放有关资源
            if (null != s) {
                try {
                    s.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
相关类和方法的解析
ServerSocket类

1.java.net.ServerSocket类主要用于描述服务器套接字信息(大插排)。

2.常用方法

方法声明 功能介绍
ServerSocket(int port) 根据参数指定的端口号来构造对象
Socket accept() 侦听并接收到此套接字的连接请求
void close() 用于关闭套接字
Socket类

1.java.net.Socket类主要用于描述客户端套接字,是两台机器间通信的端点(小插排)。

2.常用方法

方法声明 功能介绍
Socket(String host, int port) 根据指定主机名和端口来构造对象
InputStream getInputStream() 用于获取当前套接字的输入流
OutputStream getOutputStream() 用于获取当前套接字的输出流
void close() 用于关闭套接字
注意事项

1.客户端Socket与服务器端Socket对应, 都包含输入和输出流。
2.客户端的socket.getInputStream() 连接于服务socket.getOutputStream()。
3.客户端的socket.getOutputStream()连接于服务器socket.getInputStream()

// 客户端与服务器数据通信的实现
public class ServerStringTest {

    public static void main(String[] args) {
        ServerSocket ss = null;
        Socket s = null;
        BufferedReader br = null;
        PrintStream ps = null;

        try {
            // 1.创建ServerSocket类型的对象并提供端口号
            ss = new ServerSocket(8888);

            // 2.等待客户端的连接请求,调用accept方法
            System.out.println("等待客户端的连接请求...");
            // 当没有客户端连接时,则服务器阻塞在accept方法的调用这里
            s = ss.accept();
            System.out.println("客户端连接成功!");

            // 3.使用输入输出流进行通信
            br = new BufferedReader(new InputStreamReader(s.getInputStream()));
            ps = new PrintStream(s.getOutputStream());

            while (true) {
                // 实现对客户端发来字符串内容的接收并打印
                // 当没有数据发来时,下面的方法会形成阻塞
                String s1 = br.readLine();
                System.out.println("读取到客户端发来的字符串内容是:" + s1);

                // 当客户端发来字符串内容为"bye"时,聊天结束
                if ("bye".equalsIgnoreCase(s1)){
                    System.out.println("客户端已下线!");
                    break;
                }

                // 实现服务器向客户端回发字符串内容"I received!"
                ps.println("I received!");
                System.out.println("数据发送成功!");
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
        }
        // 4.关闭Socket并释放有关资源
        if (null != ps){
            ps.close();
        }
        if (null != br){
            try {
                br.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (null != s) {
            try {
                s.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (null != ss) {
            try {
                ss.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}


public class ClientStringTest {

    public static void main(String[] args) {
        Socket s = null;
        PrintStream ps = null;
        Scanner sc = null;
        BufferedReader br = null;

        try {
            // 1.创建Socket类的对象并提供服务器主机名或IP地址和端口号
            s = new Socket("127.0.0.1",8888);
            System.out.println("连接服务器成功!");

            // 2.使用输入输出流进行通信
            sc = new Scanner(System.in);
            ps = new PrintStream(s.getOutputStream());
            br = new BufferedReader(new InputStreamReader(s.getInputStream()));
            while(true) {
                //Thread.sleep(10000);
                // 实现客户端发送的内容从键盘输入
                System.out.println("请输入要发送的数据内容:");
                String str1 = sc.next();

                // 实现客户端向服务器发送字符串内容"hello"
                //ps.println("hello");
                ps.println(str1);
                System.out.println("客户端发送数据内容成功!");
                // 当发送的数据内容为"bye"时,聊天结束
                if ("bye".equalsIgnoreCase(str1)){
                    System.out.println("聊天结束!");
                    break;
                }

                // 实现接收服务器发来的字符串内容并打印
                String str2 = br.readLine();
                System.out.println("服务器回发的消息是:" + str2);
            }

        } catch (IOException /*| InterruptedException*/ e) {
            e.printStackTrace();
        } finally {
            // 3.关闭Socket对象并释放有关资源
            if (null != br){
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(null != ps){
                ps.close();
            }
            if (null != sc){
                sc.close();
            }
            if (null != s) {
                try {
                    s.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                }
        }
    }
}
// 服务器一对多测试
public class ServerStringTest {

    public static void main(String[] args) {
        ServerSocket ss = null;
        Socket s = null;

        try {
            // 1.创建ServerSocket类型的对象并提供端口号
            ss = new ServerSocket(8888);

            // 2.等待客户端的连接请求,调用accept方法
            while (true) {
                System.out.println("等待客户端的连接请求...");
                // 当没有客户端连接时,则服务器阻塞在accept方法的调用这里
                s = ss.accept();
                System.out.println("客户端" + s.getInetAddress()+ "连接成功!");

                // 没当有一个客户端连接成功,则需要启动一个新的线程为之服务
                new ServerThread(s).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4.关闭Socket并释放有关资源
            if (null != ss) {
                try {
                    ss.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}


public class ClientStringTest {

    public static void main(String[] args) {
        Socket s = null;
        PrintStream ps = null;
        Scanner sc = null;
        BufferedReader br = null;

        try {
            // 1.创建Socket类的对象并提供服务器主机名或IP地址和端口号
            s = new Socket("127.0.0.1",8888);
            System.out.println("连接服务器成功!");

            // 2.使用输入输出流进行通信
            sc = new Scanner(System.in);
            ps = new PrintStream(s.getOutputStream());
            br = new BufferedReader(new InputStreamReader(s.getInputStream()));
            while(true) {
                //Thread.sleep(10000);
                // 实现客户端发送的内容从键盘输入
                System.out.println("请输入要发送的数据内容:");
                String str1 = sc.next();

                // 实现客户端向服务器发送字符串内容"hello"
                //ps.println("hello");
                ps.println(str1);
                System.out.println("客户端发送数据内容成功!");
                // 当发送的数据内容为"bye"时,聊天结束
                if ("bye".equalsIgnoreCase(str1)){
                    System.out.println("聊天结束!");
                    break;
                }

                // 实现接收服务器发来的字符串内容并打印
                String str2 = br.readLine();
                System.out.println("服务器回发的消息是:" + str2);
            }

        } catch (IOException /*| InterruptedException*/ e) {
            e.printStackTrace();
        } finally {
            // 3.关闭Socket对象并释放有关资源
            if (null != br){
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(null != ps){
                ps.close();
            }
            if (null != sc){
                sc.close();
            }
            if (null != s) {
                try {
                    s.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                }
        }
    }
}


public class ServerThread extends Thread{
    private Socket s ;

    public ServerThread(Socket s){
        this.s = s;
    }

    @Override
    public void run() {
        BufferedReader br = null;
        PrintStream ps = null;

        try {
            // 3.使用输入输出流进行通信
            br = new BufferedReader(new InputStreamReader(s.getInputStream()));
            ps = new PrintStream(s.getOutputStream());

            while (true) {
                // 实现对客户端发来字符串内容的接收并打印
                // 当没有数据发来时,下面的方法会形成阻塞
                String s1 = br.readLine();
                InetAddress inetAddress = s.getInetAddress();
                System.out.println("读取到客户端" + inetAddress + "发来的字符串内容是:" + s1);

                // 当客户端发来字符串内容为"bye"时,聊天结束
                if ("bye".equalsIgnoreCase(s1)){
                    System.out.println("客户端" + inetAddress + "已下线!");
                    break;
                }

                // 实现服务器向客户端回发字符串内容"I received!"
                ps.println("I received!");
                System.out.println("数据发送成功!");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != ps){
                ps.close();
            }
            if (null != br){
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != s) {
                try {
                    s.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

基于udp协议的编程模型(熟悉)

编程模型

接收方:
(1)创建DatagramSocket类型的对象并提供端口号;
(2)创建DatagramPacket类型的对象并提供缓冲区;
(3)通过Socket接收数据内容存放到Packet中,调用receive方法;
(4)关闭Socket;

发送方:
(1)创建DatagramSocket类型的对象;
(2)创建DatagramPacket类型的对象并提供接收方的通信地址;
(3)通过Socket将Packet中的数据内容发送出去,调用send方法;
(4)关闭Socket;

相关类和方法的解析
DatagramSocket类

1.java.net.DatagramSocket类主要用于描述发送和接收数据报的套接字(邮局)。换句话说,该类就是包裹投递服务的发送或接收点。

2.常用方法

方法声明 功能介绍
DatagramSocket() 使用无参的方式构造对象
DatagramSocket(int port) 根据参数指定的端口号来构造对象
void receive(DatagramPacket p) 用于接收数据报存放到参数指定的位置
void send(DatagramPacket p) 用于将参数指定的数据报发送出去
void close() 关闭Socket并释放相关资源
DatagramPacket类

1.java.net.DatagramPacket类主要用于描述数据报,数据报用来实现无连接包裹投递服务。

2.常用方法

方法声明 功能介绍
DatagramPacket(byte[] buf, int length) 根据参数指定的数组来构造对象,用于接收长度为length的数据报
DatagramPacket(byte[] buf, int length, InetAddress address, int port) 根据参数指定数组来构造对象,将数据报发送到指定地址和端口
InetAddress getAddress() 用于获取发送方或接收方的通信地址
int getPort() 用于获取发送方或接收方的端口号
int getLength() 用于获取发送数据或接收数据的长度
InetAddress类

1.java.net.InetAddress类主要用于描述互联网通信地址信息。

2.常用方法

方法声明 功能介绍
static InetAddress getLocalHost() 用于获取当前主机的通信地址
static InetAddress getByName(String host) 根据参数指定的主机名获取通信地址
public class ReceiveTest {

    public static void main(String[] args) {
        DatagramSocket ds = null;

        try {
            // 1.创建DatagramSocket类型的对象并提高平端口号
            ds = new DatagramSocket(8888);
            // 2.创建DatagramPacke类型的对象并提供缓冲区
            byte[] barr = new byte[20];
            DatagramPacket dp = new DatagramPacket(barr,barr.length);
            // 3.通过Socket接收数据内容存放到Packet里面,调用receive方法
            System.out.println("等待数据的到来....");
            ds.receive(dp);
            System.out.println("接收到的数据是:" + new String(barr,0,dp.getLength()) + "!");

            // 实现将字符串内容"I received!"回发过去
            byte[] barr2 = "I received!".getBytes();
            DatagramPacket dp2 = new DatagramPacket(barr2,barr2.length,dp.getAddress(),dp.getPort());
            ds.send(dp2);
            System.out.println("回发数据成功!");

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4.关闭Socket并释放有关资源
            if (null != ds) {
                ds.close();
            }
        }
    }
}


public class SendTest {
    public static void main(String[] args) {
        DatagramSocket ds = null;

        try {
            // 1.创建DatagramSocket类型的对象
            ds = new DatagramSocket();
            // 2.创建DatagramPacket类型的对象并提供接收方的通信地址和端口号
            byte[] barr = "hello".getBytes();
            DatagramPacket dp = new DatagramPacket(barr,barr.length, InetAddress.getLocalHost(),8888);
            // 3.通过Socket发送Packet。调用send方法
            ds.send(dp);
            System.out.println("发送数据成功!");

            // 接收回发的数据内容
            byte[] barr2 = new byte[20];
            DatagramPacket dp2 = new DatagramPacket(barr2,barr2.length);
            ds.receive(dp2);
            System.out.println("接收到到的回发数据是:" + new String(barr2,0,dp2.getLength()));

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4.关闭Socket方法并释放有关资源
            if (null != ds) {
                ds.close();
            }
        }
    }
}

URL类(熟悉)

概念

1、java.net.URL(Uniform Resource Identifier)类主要用于表示统一的资源定位器,也就是指向万维网上“资源”的指针。这个资源可以是简单的文件或目录,也可以是对复杂对象的引用,例如对数据库或搜索引擎的查询等。
2、通过URL可以访问万维网上的网络资源,最常见的就是www和ftp站点,浏览器通过解析给定的URL可以在网络上查找相应的资源。
3、URL的基本结构如下:
<传输协议>://<主机名>:<端口号>/<资源地址>

常用方法
方法声明 功能介绍
URL(String spec) 根据参数指定的字符串信息构造对象
String getProtocol() 获取协议名称
String getHost() 获取主机名称
int getPort() 获取端口号
String getPath() 获取路径信息
String getFile() 获取文件名
URLConnection openConnection() 获取URLConnection类的实例
URLConnection类
概念

​ java.net.URLConnection类是个抽象类,该类表示应用程序和URL之间的通信链接的所有类的超类,主要实现类有支持HTTP特有功能的HttpURLConnection类。

HttpURLConnection类的常用方法
方法声明 功能介绍
InputStream getInputStream() 获取输入流
void disconnect() 断开连接
public class URLTest {

    public static void main(String[] args) {

        try {
            // 1.使用参数指定的字符串来构造对象
            URL url = new URL("https://www.lagou.com/");
            // 2.获取相关信息并打印出来
            System.out.println("获取到的协议名称是:" + url.getProtocol());
            System.out.println("获取到的主机名称是:" + url.getHost());
            System.out.println("获取到的端口号是:" + url.getPort());

            // 3.建立连接并读取相关信息
            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
            InputStream inputStream = urlConnection.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
            String str = null;
            while ((str = br.readLine()) != null){
                System.out.println(str);
            }
            br.close();
            // 断开连接
            urlConnection.disconnect();

        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

反射机制

基本概念

​ 反射机制就是用于动态创建对象(通过运行时传递的参数来决定)并且动态调用方法的机制。

​ 目前主流的框架底层都是采用反射机制实现的。

Class类

概念

java.lang.Class类的实例可以用于描述Java应用程序中的类和接口,也就是一种数据类型。

该类没有公共构造方法,该类的实例由Java虚拟机和类加载器自动构造完成,本质上就是加载到内存中的运行时类。

获取Class对象的方式

1、使用数据类型.class的方式可以获取对应类型的Class对象(掌握)。
2、使用引用/对象.getClass()的方式可以获取对应类型的Class对象。
3、使用包装类.TYPE的方式可以获取对应基本数据类型的Class对象。
4、使用Class.forName()的方式来获取参数指定类型的Class对象(掌握)。
5、使用类加载器ClassLoader的方式获取指定类型的Class对象。

public class ClassTest {
    public static void main(String[] args) {

        // 1.使用数据类型.class的方式可以获取对应类型的Class对象
        Class c1 = String.class;
        System.out.println("c1 = " + c1);   // 自动调用toString方法   class java.lang.String
        c1 = int.class;
        System.out.println("c1 = " + c1);   // int
        c1 = void.class;
        System.out.println("c1 = " + c1);   // void

        System.out.println("-------------------------------------------");
        // 2.使用对象.getClass()的方式获取对应的Class对象
        String str1 = new String("hello");
        c1 = str1.getClass();
        System.out.println("c1 = " + c1);   // class java.lang.String

        Integer it1 = 20;
        c1 = it1.getClass();
        System.out.println("c1 = " + c1);   // class java.lang.Integer

        int num = 5;
        //num.getclass(); Error:基本数据类型的变量不能调用方法

        System.out.println("-------------------------------------------");
        // 3.使用包装类.TYPE的方式来获取对应基本数据类型的Class对象
        c1 = Integer.TYPE;
        System.out.println("c1 = " + c1);   // int

        c1 = Integer.class;
        System.out.println("c1 = " + c1);   // class java.lang.Integer

        System.out.println("-------------------------------------------");
        // 4.调用Class类中的forName方法来获取对应的Class对象
        //c1 = Class.forName("String"); // Error    要求写完整的名称:包名.类名
        try {
            c1 = Class.forName("java.lang.String");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        System.out.println("c1 = " + c1);   // class java.lang.String

        try {
            c1 = Class.forName("java.util.Date");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        System.out.println("c1 = " + c1);   // class java.util.Date

        //c1 = Class.forName("int");
        //System.out.println("c1 = " + c1); // 不能获取基本上数据类型的Class对象

        System.out.println("-------------------------------------------");
        // 5.使用类加载器的方式来获取Class对象
        ClassLoader classLoader = ClassTest.class.getClassLoader();
        System.out.println("classloader = " + classLoader); // null
        try {
            c1 = classLoader.loadClass("java.lang.String");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        System.out.println("c1 = " + c1);   // class java.lang.String
    }
}
常用方法(掌握)
方法声明 功能介绍
static Class forName(String className) 用于获取参数指定类型对应的Class对象并返回
T newInstance() 用于创建该Class对象所表示类的新实例
public class Person {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}


public class PerosnConstructorTest {

    public static void main(String[] args) throws Exception {

        // 1.使用原始方式以无参形式构造Person类型的对象
        Person p1 = new Person();
        System.out.println("无参方式创建的对象是:" + p1); // null 0

        System.out.println("-------------------------------------");
        // 2.使用反射机制以无参方式构造Person类型的对象并打印
        // 创建对象的类型可以从键盘输入
        //System.out.println("请输入要创建对象的类型:");
        //Scanner sc = new Scanner(System.in);
        //String str1 = sc.next();
        //Class c1 = Class.forName("com.wei.Part4.Task05.Person");

        // 创建对象的类型可以从配置文件中读取
        BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("e:/a.txt")));
        String str1 = br.readLine();
        Class c1 = Class.forName(str1);
        //System.out.println("无参方式创建的对象是:" + c1.newInstance());   // null 0
        // 获取偶去Class对象对应类中的的无参构造方法,也就是Person类中的无参构造方法
        Constructor constructor = c1.getConstructor();
        // 使用获取到的无参构造方法来构造对应类型的对象,也就是Person类型的对象
        System.out.println("无参方式创建的对象是:" + constructor.newInstance());
        //sc.close();
        br.close();
    }
}

Constructor类

概念

​ java.lang.reflect.Constructor类主要用于描述获取到的构造方法信息

Class类的常用方法
方法声明 功能介绍
Constructor getConstructor(Class…parameterTypes) 用于获取此Class对象所表示类型中参数指定的公共构造方法
Constructor[] getConstructors() 用于获取此Class对象所表示类型中所有的公共构造方法
Constructor类的常用方法
方法声明 功能介绍
T newInstance(Object…initargs) 使用此Constructor对象描述的构造方法来构造Class对象代表类型的新实例
int getModifiers() 获取方法的访问修饰符
String getName() 获取方法的名称
Class[] getParameterTypes() 获取方法所有参数的类型
public class PerosnConstructorTest {

    public static void main(String[] args) throws Exception {

        // 1.使用原始方式以无参形式构造Person类型的对象
        Person p1 = new Person();
        System.out.println("无参方式创建的对象是:" + p1); // null 0

        System.out.println("-------------------------------------");
        // 2.使用反射机制以无参方式构造Person类型的对象并打印
        // 创建对象的类型可以从键盘输入
        //System.out.println("请输入要创建对象的类型:");
        //Scanner sc = new Scanner(System.in);
        //String str1 = sc.next();
        //Class c1 = Class.forName("com.wei.Part4.Task05.Person");

        // 创建对象的类型可以从配置文件中读取
        BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("e:/a.txt")));
        String str1 = br.readLine();
        Class c1 = Class.forName(str1);
        //System.out.println("无参方式创建的对象是:" + c1.newInstance());   // null 0
        // 获取偶去Class对象对应类中的的无参构造方法,也就是Person类中的无参构造方法
        Constructor constructor = c1.getConstructor();
        // 使用获取到的无参构造方法来构造对应类型的对象,也就是Person类型的对象
        System.out.println("无参方式创建的对象是:" + constructor.newInstance());
        //sc.close();
        br.close();

        System.out.println("-------------------------------------");
        // 3.使用有原始方式以有参方式构造Person类型的对象并打印
        Person p2 = new Person("zhangfei",30);
        System.out.println("有参方式构造的对象是:" + p2); // zhangfei 30

        System.out.println("-------------------------------------");
        // 4.使用反射机制以有参方式构造Person类型的对象并打印
        // 获取Class对象对应类中的有参构造方法,也就是Person类中的有参构造方法
        Constructor constructor1 = c1.getConstructor(String.class, int.class);
        // 使用获取到的有参构造方法构造对应类型的对象,也就是Person类型的对象
        // newInstance方法中的实参是用于给有参构造方法的形参进行初始化,也就是给name和age进行初始化的
        System.out.println("有参方式构造的对象是:" + constructor1.newInstance("zhagnfei",30));    // zhangfei 30

        System.out.println("-------------------------------------");
        // 5.使用反射机制获取Person类中的所有的公共构造方法并打印
        Constructor[] constructors = c1.getConstructors();
        for (Constructor ct : constructors) {
            System.out.println("构造方法的访问修饰符是:" + ct.getModifiers());
            System.out.println("构造方法的方法名是:" + ct.getName());
            Class[] parameterTypes = ct.getParameterTypes();
            System.out.print("构造方法的所有参数类型是:");
            for (Class cs : parameterTypes) {
                System.out.print(cs + " ");
            }
            System.out.println();
            System.out.println("------------------------------------");
        }
    }
}

Field类

概念

​ java.lang.reflect.Field类主要用于描述获取到的单个成员变量信息。

Class类的常用方法
方法声明 功能介绍
Field getDeclaredField(String name) 用于获取此Class对象所表示类中参数指定的单个成员变量信息
Field[] getDeclaredFields() 用于获取此Class对象所表示类中所有成员变量信息
Field类的常用方法
方法声明 功能介绍
Object get(Object obj) 获取参数对象obj中此Field对象所表示成员变量的数值
void set(Object obj, Object value) 将参数对象obj中此Field对象表示成员变量的数值修改为参数value的数值
void setAccessible(boolean flag) 当实参传递true时,则反射对象在使用时应该取消 Java 语言访问检查
int getModifiers() 获取成员变量的访问修饰符
Class getType() 获取成员变量的数据类型
String getName() 获取成员变量的名称
public class PersonFieldTest {

    public static void main(String[] args) throws Exception {

        // 1.使用原始方式来构造对象以及获取成员变量的数值并打印
        Person p1 = new Person("zhangfei",30);
        //System.out.println("获取到的成员变量数值为:" + p1.name);   // zhangfei

        System.out.println("--------------------------------------------------");
        // 2.使用反射机制来构造对象以及获取成员变量数值并打印
        // 2.1 获取Class对象
        Class c1 = Class.forName("com.wei.Pert4.Task05.Person");

        // 2.2 根据Class对象获取对应的有参构造方法
        Constructor constructor = c1.getConstructor(String.class, int.class);

        // 2.3 使用有参构造方法来得到Person类型的对象
        Object object = constructor.newInstance("zhangfei", 30);

        // 2.4 根据Class对象获取对应的成员变量信息
        Field field = c1.getDeclaredField("name");
        // 设置Java语言访问检查的取消  暴力反射
        field.setAccessible(true);

        // 2.5 使用Person类型的对象来获取成员变量的数值并打印
        // 获取对象object对象中名字为field的成员变量的数值,也就是成员变量name的数值
        System.out.println("获取到的成员变量数值为:" + field.get(object));

        System.out.println("--------------------------------------------------");
        // 3.使用原始方式修改指定对象中成员变量的数值后再次打印
        //p1.name = "guanyu";
        //System.out.println("修改后成员变量的数值为:" + p1.name);   // guanyu

        System.out.println("--------------------------------------------------");
        // 4.使用反射机制修改指定对象中成员变量的数值后再次打印
        // 修改对象object中名字为field成员变量的数值为guanyu,也就是成员变量name的数值为guanyu
        field.set(object,"guanyu");
        System.out.println("修改后成员变量的数值为:" + field.get(object));   // guanyu

        System.out.println("--------------------------------------------------");
        // 5.获取Class对象对应对应类中的成员变量
        Field[] declaredFields = c1.getDeclaredFields();
        for (Field ft : declaredFields) {
            System.out.println("获取到的访问修饰符为:" + ft.getModifiers());
            System.out.println("获取到的数据类型为:" + ft.getType());
            System.out.println("获取到的成员名称为:" + ft.getName());
            System.out.println("-----------------------------------------");
        }
    }
}

Method类

概念

​ java.lang.reflect.Method类主要用于描述获取到的单个成员方法信息

Class类的常用方法
方法声明 功能介绍
Method getMethod(String name, Class… parameterTypes) 用于获取该Class对象表示类中名字为name参数为parameterTypes的指定公共成员方法
Method[] getMethods() 用于获取该Class对象表示类中所有公共成员方法
Method类的常用方法
方法声明 功能介绍
Object invoke(Object obj, Object… args) 使用对象obj来调用此Method对象所表示的成员方法,实参传递args
int getModifiers() 获取方法的访问修饰符
Class getReturnType() 获取方法的返回值类型
String getName() 获取方法的名称
Class[] getParameterTypes() 获取方法所有参数的类型
Class[] getExceptionTypes() 获取方法的异常信息
public class PersonMethodTest {

    public static void main(String[] args) throws Exception{

        // 1.使用原始方式构造对象并调用方法打印结果
        Person p1 = new Person("zhangfei",30);
        System.out.println("调用方法的返回值是:" + p1.getName());    // zhangfei

        System.out.println("--------------------------------------------------");
        // 2.使用反射机制构造对象并调用方法打印结果
        // 2.1 获取Class对象
        Class c1 = Class.forName("com.wei.Pert4.Task05.Person");
        // 2.2 根据Class对象获取对应的有参构造方法
        Constructor constructor = c1.getConstructor(String.class,int.class);
        // 2.3 使用有参构造方法构造对象并记录
        Object object = constructor.newInstance("zhangfei", 30);
        // 2.4 根据Class对象获取对应的成员方法
        Method method = c1.getMethod("getName");
        // 2.5 使用对象调用成员方法进行打印
        // 表示使用object对象调用method表示的方法,也就是调用getName方法来获取姓名
        System.out.println("调用方法的返回值是:" + method.invoke(object));   // zhangfei

        System.out.println("--------------------------------------------------");
        // 3.使用反射机制来获取类中的所有成员方法并打印
        Method[] methods = c1.getMethods();
        for (Method mt : methods) {
            System.out.println("成员方法的修饰符是:" + mt.getModifiers());
            System.out.println("成员方法的返回值类型是:" + mt.getReturnType());
            System.out.println("成员方法的名称是:" + mt.getName());
            System.out.println("成员方法形参列表的类型是:");
            Class<?>[] parameterTypes = mt.getParameterTypes();
            for (Class ct : parameterTypes) {
                System.out.print(ct + " ");
            }
            System.out.println();

            System.out.println("成员方法的异常类型列表是:");
            Class<?>[] exceptionTypes = mt.getExceptionTypes();
            for (Class ct : exceptionTypes) {
                System.out.print(ct + " ");
            }
            System.out.println();
            System.out.println("--------------------------------------------------");
        }
    }
}

获取其它结构信息

方法声明 功能介绍
Package getPackage() 获取所在的包信息
Class getSuperclass() 获取继承的父类信息
Class[] getInterfaces() 获取实现的所有接口
Annotation[] getAnnotations() 获取注解信息
Type[] getGenericInterfaces() 获取泛型信息
@MyAnnotation
public class Student<T, E> extends Person implements Comparable<String>, Serializable {

    @Override
    public int compareTo(String o) {
        return 0;
    }
}

@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
}

public class StudentTest {

    public static void main(String[] args) throws Exception {

        // 获取Student类型的Class对象
        Class c1 = Class.forName("com.wei.Part4.Task05.Student");
        System.out.println("获取到的包信息是:" + c1.getPackage());
        System.out.println("获取到的父类信息是:" + c1.getSuperclass());
        System.out.println("获取到的接口信息是:");
        Class[] interfaces = c1.getInterfaces();
        for (Class ct : interfaces) {
            System.out.print(ct + " ");
        }
        System.out.println();
        System.out.println("获取到的注解信息是:");
        Annotation[] annotations = c1.getAnnotations();
        for (Annotation at : annotations) {
            System.out.print(at + " ");
        }
        System.out.println();
        System.out.println("获取到的泛型信息是:");
        Type[] genericInterfaces = c1.getGenericInterfaces();
        for (Type tt : genericInterfaces) {
            System.out.print(tt + " ");
        }
        System.out.println();
    }
}

你可能感兴趣的:(学习,java)