java之学习记录 1 - 4 (java核心类库下)

记录一:

异常机制(重点)

1、基本概念:

  • 异常就是"不正常"的含义,在Java语言中主要指程序执行中发生的不正常情况。

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

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

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

2、异常的分类:

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

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

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

  • 其中RuntimeException类的主要子类:

    ArithmeticException类 - 算术异常

    ArrayIndexOutOfBoundsException类 - 数组下标越界异常

    NullPointerException - 空指针异常

    ClassCastException - 类型转换异常

    NumberFormatException - 数字格式异常

  • 注意:

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

3、异常的避免:

  • 在以后的开发中尽量使用if条件判断来避免异常的发生。

  • 但是过多的if条件判断会导致程序的代码加长、臃肿,可读性差。

import java.io.IOException;
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(0 <= 5 && 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("程序正常结束了!");
    }
}

4、异常的捕获:

  • 语法格式

    try {

    编写可能发生异常的代码;

    }

    catch(异常类型 引用变量名) {

    编写针对该类异常的处理代码;

    }

    ...

    fifinally {

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

    }

  • 注意事项

    当需要编写多个catch分支时,切记小类型应该放在大类型的前面;

    懒人的写法:

    catch(Exception e) {}

    c.finally通常用于进行善后处理,如:关闭已经打开的文件等。

  • 执行流程

    try {

    a;

    b; //可能发生异常的语句

    c;

    }catch(Exception ex) {

    d;

    }finally {

    e;

    }

    当没有发生异常时的执行流程:a b c e;

    当发生异常时的执行流程:a b d e;

    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.IOException;
    ​
    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 (IOException e) {
                System.out.println("7");
                e.printStackTrace();
                System.out.println("8");
            } catch (NullPointerException e){
                e.printStackTrace();
            } finally {
                // 会提前结束方法并打印/返回
                System.out.println("无论是否发生异常,都执行!");
            }
            System.out.println("测试打印");
            // 当程序执行过程中没有发生异常时的执行流程:1 2 5 6 测试打印
            // 当程序执行过程中发生异常又没有手动处理异常时的执行流程:1 3 4 5 空指针异常导致程序终止
            // 当程序执行过程中发生异常并且手动处理空指针异常时的执行流程: 1 3 4 5 测试打印
    ​
            // 手动处理异常和没有处理的区别:代码是否可以继续向下执行
        }
    }

     

5、异常的抛出:

  • 基本概念

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

  • 语法格式

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

  • 方法重写的原则

    要求方法名相同、参数列表相同以及返回值类型相同,从jdk1.5开始支持返回子类类型;

    要求方法的访问权限不能变小,可以相同或者变大;

    要求方法不能抛出更大的异常;

  • 注意:

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

  • 经验分享

    若父类中被重写的方法没有抛出异常时,则子类中重写的方法只能进行异常的捕获处理。

    若一个方法内部又以递进方式分别调用了好几个其它方法,则建议这些方法内可以使用抛出的方法处理到最后一层进行捕获方式处理。

import com.sun.jdi.ClassNotLoadedException;
​
import java.io.FileNotFoundException;
import java.io.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{} // 不可以抛出更大的异常
​
}

6、自定义异常:

  • 基本概念

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

  • 实现流程

    自定义xxxException异常类继承Exception类或者其子类。

    提供两个版本的构造方法,一个是无参构造方法,另外一个是字符串作为参数的构造方法。

  • 异常的产生

    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 {
        setAge(age);
        setName(name);
    }
​
    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("年龄不合理!");
            throw  new AgeException("年龄不合理!");
        }
    }
​
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
​
/**
    测试
*/
public class PersonTest {
    public static void main(String[] args) {
        Person p = null;
        try {
            p = new Person("赵云",-18);
        } catch (AgeException e) {
            e.printStackTrace();
        }
        System.out.println("p = "+ p);
    }
}

File类(重点):

1、基本概念:

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

2、常用的方法:

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.Date;
​
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 {
        // 构造File类型的对象与E"a.txt关联
        File f1 = new File("E:/a.txt");
        // 若文件存在则获取文件的相关特征信息并打印后删除文件
        if(f1.exists()){
            System.out.println("文件的名称是:"+ f1.getName());
            System.out.println("文件的大小是:"+ f1.length());
            Date d = new Date(f1.lastModified());
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            System.out.println("文件的最后一次修改时间:"+ sdf.format(d));
            System.out.println("文件的绝对路径信息是:"+ f1.getAbsolutePath());
            System.out.println(f1.delete()?"删除成功":"删除失败");
        } else {
            // 若文件不存在 则创建新的空文件
            System.out.println(f1.createNewFile()?"创建成功":"创建失败");
        }
​
        System.out.println("------------------");
        // 实现目录的删除和创建
        File f2 = new File("E:/mmm/cccc/eeee");
        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("------------------");
        // 实现将制定目录中的所有内容打印出来
        File f3 = new File("E:/mmm/cccc");
        // 获取目录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("------------------");
        // 实现目录中所有内容获取的同时进行过滤
        // 匿名内部类的语法格式:接口/父类类型 引用变量名 = new 接口/父类类型(){方法的重写};
        /*FileFilter fileFilter = new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                // 若文件名是以.avi为结尾,则返回true表示保留 否则false 就是表示丢弃
                return pathname.getName().endsWith(".avi");
            }
        };*/
        // Lambda表达式的格式:(参数列表)-> {方法体}
        FileFilter fileFilter = (pathname) -> {return pathname.getName().endsWith(".avi");};
        File[] filesArray2 = f3.listFiles(fileFilter);
        for (File tf: filesArray2){
            System.out.println(tf);
        }
​
        System.out.println("------------------");
        // 使用递归的思想获取目录以及子目录中的内容
        show(new File("E:/mmm"));
    }
}

记录二:**

1、IO流的概念:

  • IO就是Input和Output的简写,也就是输入和输出的含义。

  • IO流就是指读写数据时像流水一样从一端流到另外一端,因此得名为“流"。

2、基本分类:

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

    其中字节流主要指以字节为单位进行数据读写的流,可以读写任意类型的文件。

    其中字符流主要指以字符(2个字节)为单位进行数据读写的流,只能读写文本文件。

  • 按照读写数据的方向不同,分为 输入流 和 输出流(站在程序的角度)。

    其中输入流主要指从文件中读取数据内容输入到程序中,也就是读文件。

    其中输出流主要指将程序中的数据内容输出到文件中,也就是写文件。

  • 按照流的角色不同分为节点流和处理流。

    其中节点流主要指直接和输入输出源对接的流。

    其中处理流主要指需要建立在节点流的基础之上的流。

3、体系结构:

 

4、相关流的详解:

4.1 FileWriter类(重点):

4.1.1 基本概念:

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

4.1.2 常用的方法:

import java.io.FileWriter;
import java.io.IOException;
​
public class FileWriterTest {
    public static void main(String[] args) {
        // 选中代码后可以使用ctrl+alt+t 来生成异常的捕获代码等。
        FileWriter fw = null;
        try {
            // 构造FileWrite类型的对象与E:/a.txt文件关联
            // 若文件不存,则该流回自动创建新的空文件
            // 若文件存在,该流会清空文件中的原有内容
            //fw = new FileWriter("E:/a.txt");
            // 以追加的方式创建对象去关联文件
            // 若文件不存在则自动创建新的空文件,若文件存在则保留原有数据内容
            fw = new FileWriter("E:/a.txt",true);
            // 通过流对象写入数据内容  每当写入一个字符后则文件中的读写位置向后移动一位
            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 {
            // 关闭流对象并释放有关的资源
            if (null != fw){
                try {
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

4.2 FileReader类(重点):

4.2.1 基本概念:

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

4.2.2 常用的方法:

import java.io.FileReader;
import java.io.IOException;
​
public class FileReaderTest {
    public static void main(String[] args) {
        FileReader fr = null;
        try {
            // 构造FileReader类型的对象与E:/a.txt文件关联
            fr = new FileReader("E:/a.txt");
            // 读取数据内容并打印
            /*
            int res = fr.read();
            System.out.println("读取到的单个字符是:"+ (char)res);
            */
            /*int res = 0;
            while ((res = fr.read()) != -1){
                System.out.println("读取到的单个字符是:"+ (char)res);
            }*/
            // 准备一个字符数组来保存读取到的数据内容
            char[] cArr = new char[5];
            // 期望读满字符数组中的一部分空间,也就是读取3个字符放入数组cArr中下标从1开始的位置
            /*int res = fr.read(cArr, 1, 3);
            System.out.println("实际读取到的字符个数是:"+ res);
            for (char cv: cArr){
                System.out.println("实际读取到的字符个数是:"+ (char)cv);
            }*/
            // 期望读满整个字符数组
            int res = fr.read(cArr);
            System.out.println("实际读取到的字符个数是:"+ res);
            for (char cv: cArr){
                System.out.println("实际读取到的字符个数是:"+ (char)cv);
            }
            // 期望杜曼整个字符数组
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(null != fr){
                // 关闭流对象并释放有关的资源
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
​
/*
* 文件字符流实现文件拷贝
* */
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
​
public class FileCharCopyTest {
    public static void main(String[] args) {
        FileReader fr = null;
        FileWriter fw = null;
        try {
            // 创建FileReader类型的对象与E:/a.txt文件关联
            fr = new FileReader("E:/a.txt");
            //fr = new FileReader("E:/03 IO流的框架图.png");
            // 创建FileWriter类型的对象与E:/b.txt文件关联
            fw = new FileWriter("E:/b.txt");
            //fw = new FileWriter("E:/IO流的框架图.png"); // 拷贝图片文件失败
            // 不断地从输入流中读取数据内容并写入到输出流中
            System.out.println("正在拷贝");
            int res = 0;
            while ((res = fr.read()) != -1){
                fw.write(res);
            }
            System.out.println("拷贝完成");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭流对象并释放有关的资源
            if(null != fw){
                try {
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
​
            if(null != fw){
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
​
    }
}

4.3 FileOutputStream类(重点):

4.3.1 基本概念:

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

4.3.2 常用的方法:

4.4 FileInputStream类(重点):

4.4.1 基本概念:

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

4.4.2 常用的方法:

/*
 * 缓存字节流实现文件的拷贝 三种方式
 * */
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
​
public class FileByteCopyTest {
    public static void main(String[] args) {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            // 创建FileInputStream类型的对象与E:/03 IO流的框架图.png文件关联
            //fis = new FileInputStream("E:/03 IO流的框架图.png");
            fis = new FileInputStream("E:/02_IO流的框架结构.mp4");
            // 创建FileOutputStream类型的对象与E:/IO流的框架图.png文件关联
            //fos = new FileOutputStream("E:/IO流的框架图.png");
            fos = new FileOutputStream("E:/IO流的框架结构.mp4");
            // 不断地从输入流中读取数据内容并写入到输出流中
            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 {
            // 关闭流对象并释放有关的资源
            if(null != fos){
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(null != fis){
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
​
        }
    }
}

4.5 BufferedOutputStream类(重点):

4.5.1 基本概念:

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

4.5.2 常用的方法:

4.6 BufferedInputStream类(重点):

4.6.1 基本概念:

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

4.6.2 常用的方法:

import java.io.*;
​
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 {
            // 创建BufferedInputStream类型的对象与E:/02_IO流的框架结构.mp4文件关联
            bis = new BufferedInputStream(new FileInputStream("E:/02_IO流的框架结构.mp4"));
            // 创建BufferedOuputStream类型的对象与E:/IO流的框架结构.mp4文件关联
            bos = new BufferedOutputStream(new FileOutputStream("E:/IO流的框架结构.mp4"));
            // 不断地从输入流中读取数据并写入到输出流中
            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) {
            e.printStackTrace();
        } finally {
            // 关闭流对象并释放有关的资源
            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));
    }
}

4.7 BufferedWriter类(重点):

4.7.1 基本概念:

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

4.7.2 常用的方法:

4.8 BufferedReader类(重点):

4.8.1 基本概念:

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

4.8.2 常用的方法:

import java.io.*;
​
public class BufferedCharCopyTest {
    public static void main(String[] args) {
        BufferedReader br = null;
        BufferedWriter bw = null;
        try {
            // 创建BufferedReader类型的对象与E:/a.txt文件关联
            br = new BufferedReader(new FileReader("E:/a.txt"));
            // 创建BufferedWriter类型的对象与E:/b.txt文件关联
            bw = new BufferedWriter(new FileWriter("E:/b.txt"));
            // 不断地从输入流中读取一行字符串并写入到输出流中
            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 {
            // 关闭流对象并释放有关的资源
            if(null != bw){
                try {
                    bw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(null != br){
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
​
        }
​
    }
}

4.9 PrintStream类:

4.9.1 基本概念:

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

4.9.2 常用的方法:

4.10 PrintWriter类:

4.10.1 基本概念:

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

4.10.2 常用的方法:

import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
​
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){
                // 提示用户输入要发送的聊天内容并使用变量记录
                System.out.println("请"+(flag?"赵云":"孔明")+"输入要发送的聊天内容:");
​
                String str = br.readLine();
                // 判断用户输入的内容是否为"bye",若是则聊天结束
                if("bye".equals(str)){
                    System.out.println("聊天结束");
                    break;
                }
                // 若不是则将用户输入的内容写入到文件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 {
            // 关闭流对象并释放有关的资源
            if (null != ps){
                ps.close();
            }
            if (null != br){
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
​
        }
    }
}

4.11 OutputStreamWriter类:

4.11.1 基本概念:

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

4.11.2 常用的方法:

4.12 InputStreamReader类:

4.12.1 基本概念:

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

4.12.2 常用的方法:

4.13 字符编码

4.13.1 编码表的由来

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

4.13.2 常见的编码表:

ASCII:美国标准信息交换码, 使用一个字节的低7位二位进制进行表示。

ISO8859-1:拉丁码表,欧洲码表,使用一个字节的8位二进制进行表示。

GB2312:中国的中文编码表,最多使用两个字节16位二进制为进行表示。

GBK:中国的中文编码表升级,融合了更多的中文文字符号,最多使用两个字节16位二进制位表示。

Unicode:国际标准码,融合了目前人类使用的所有字符,为每个字符分配唯一的字符码。所有的文字都用两个字节16位二进制位来表示。

4.13.3 编码的发展:

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

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

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

4.14 DataOutputStream类(了解):

4.14.1 基本概念:

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

4.14.2 常用的方法:

import java.io.DataOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
​
public class DataOutputStreamTest {
    public static void main(String[] args) {
        DataOutputStream dos = null;
        try {
            // 创建DataOutputStream类型的对象与E:/a.txt文件关联
            dos = new DataOutputStream(new FileOutputStream("E:/a.txt"));
            // 准备一个证书数据66并写入输出流
            // 66: 0000 0000 .... 0100 0010 =>   B
            int num = 66;
            dos.writeInt(num);
            System.out.println("写入数据成功");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭流对象并释放有关的资源
            if (null != dos){
                try {
                    dos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
​
        }
​
    }
}

4.15 DataInputStream类(了解):

4.15.1 基本概念:

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

4.15.2 常用的方法:

public class DataInputStreamTest {
    public static void main(String[] args) {
        DataInputStream dis = null;
        try {
            // 创建DataInputStream类型的对象与E:/a.txt文件关联
            dis = new DataInputStream(new FileInputStream("E:/a.txt"));
            // 从输入流中读取一个整数并打印
            int res = dis.readInt();
            System.out.println("读取到的整数数据是:"+ res);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭流对象并释放有关的资源
            if (null != dis){
                try {
                    dis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

4.16 ObjectOutputStream类(重点):

4.16.1 基本概念:

java.io.ObjectOutputStream类主要用于将一个对象的所有内容整体写入到输出流中。

只能将支持 java.io.Serializable 接口的对象写入流中。

类通过实现 java.io.Serializable 接口以启用其序列化功能。

所谓序列化主要指将一个对象需要存储的相关信息有效组织成字节序列的转化过程。

4.16.2 常用的方法:

public class User implements java.io.Serializable{
    private static final long serialVersionUID = -5814716593800822421L;
    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 + '\'' +
                '}';
    }
}
​
​
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
​
public class ObjectOutputStreamTest {
    public static void main(String[] args) {
        ObjectOutputStream oos = null;
        try {
            // 创建ObjectOutputStream类型的对象与E:/a.txt文件关联
            oos = new ObjectOutputStream(new FileOutputStream("E:/a.txt"));
            // 准备一个User类型的对象并初始化
            User u = new User("mzj","123456","17600000000");
            // 将整个User类型的对象写入输出流
            oos.writeObject(u);
            System.out.println("写入对象成功");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭流对象并释放有关的资源
            if(null != oos){
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
​
        }
​
    }
}

4.17 ObjectInputStream类(重点):

4.17.1 基本概念:

java.io.ObjectInputStream类主要用于从输入流中一次性将对象整体读取出来。

所谓反序列化主要指将有效组织的字节序列恢复为一个对象及相关信息的转化过程。

4.17.2 常用的方法:

4.17.3 序列化版本号:

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

4.17.4 transient关键字:

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

4.17.5 经验的分享:

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

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
​
public class ObjectInputStreamTest {
    public static void main(String[] args) {
        ObjectInputStream ois = null;
        try {
            // 创建ObjectInputStream类型的对象与E:/a.txt文件关联
            ois = new ObjectInputStream(new FileInputStream("E:/a.txt"));
            // 从输入流中读取一个对象并打印
            Object object = ois.readObject();
            System.out.println("获取到的对象是:"+ object);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            // 关闭流对象并释放有关的资源
            if (null != ois){
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
​
    }
}

4.18 RandomAccessFile类:

4.18.1 基本概念:

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

4.18.2 常用的方法:

import java.io.IOException;
import java.io.RandomAccessFile;
​
public class RandomAccessFileTest {
    public static void main(String[] args) {
        RandomAccessFile raf = null;
        try {
            // 创建RandomAccessFile类型的对象与E:/a.txt文件关联
            raf = new RandomAccessFile("E:/a.txt","rw");
            // 对文件内容进行随机读写操作
            // 设置距离文件开头位置的偏移量,从文件开头位置向后偏移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 {
            // 关闭流对象并释放有关的资源
            if(null != raf){
                try {
                    raf.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

记录三:

多线程:

1、基本概念:

1.1 程序和进程的概念:

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

进程 - 主要指运行在内存中的可执行文件。

目前主流的操作系统都支持多进程,为了让操作系统同时可以执行多个任务,但进程是重量级的,也就是新建一个进程会消耗CPU和内存空间等系统资源,因此进程的数量比较局限。

1.2 线程的概念:

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

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

2、线程的创建(重中之重):

2.1 Thread类的概念:

  • java.lang.Thread类代表线程,任何线程对象都是Thread类(子类)的实例。

  • Thread类是线程的模板,封装了复杂的线程开启等操作,封装了操作系统的差异性。

2.2 创建方式:

  • 自定义类继承Thread类并重写run方法,然后创建该类的对象调用start方法。

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

2.3 相关的方法:

2.4 执行流程:

  • 执行main方法的线程叫做主线程,执行run方法的线程叫做新线程/子线程。

  • main方法是程序的入口,对于start方法之前的代码来说,由主线程执行一次,当start方法调用成功后线程的个数由1个变成了2个,新启动的线程去执行run方法的代码,主线程继续向下执行,两个线程各自独立运行互不影响。

  • 当run方法执行完毕后子线程结束,当main方法执行完毕后主线程结束。

  • 两个线程执行没有明确的先后执行次序,由操作系统调度算法来决定。

2.5 方式的比较:

  • 继承Thread类的方式代码简单,但是若该类继承Thread类后则无法继承其它类,而实现Runnable接口的方式代码复杂,但不影响该类继承其它类以及实现其它接口,因此以后的开发中推荐使用第二种方式。

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

public class ThreadNoNameTest {
    public static void main(String[] args) {
        // 匿名内部类的语法格式:父类/接口类型 引用变量名 = new 父类/接口类型(){方法的重写};
        // 使用继承加匿名内部类的方式创建并启动线程
        /*Thread t1 = new Thread(){
            @Override
            public void run(){
                System.out.println("赵云说:在吗?");
            }
        };
        t1.start();*/
        new Thread(){
            @Override
            public void run(){
                System.out.println("赵云说:在吗?");
            }
        }.start();
        // 使用实现接口加匿名内部类的方式创建并启动线程
       /* 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();
    }
}

3、线程的生命周期(熟悉)

  • 新建状态 - 使用new关键字创建之后进入的状态,此时线程并没有开始执行。

  • 就绪状态 - 调用start方法后进入的状态,此时线程还是没有开始执行。

  • 运行状态 - 使用线程调度器调用该线程后进入的状态,此时线程开始执行,当线程的时间片执行完毕后任务没有完成时回到就绪状态。

  • 消亡状态 - 当线程的任务执行完成后进入的状态,此时线程已经终止。

  • 阻塞状态 - 当线程执行的过程中发生了阻塞事件进入的状态,如:sleep方法。阻塞状态解除后进入就绪状态。

4、线程的编号和名称(熟悉):

/* 继承Runnable接口进行操作 */
public class RunnableIdNameTest implements Runnable {
​
    @Override
    public void run() {
        // 获取当前正在执行线程的引用,也就是子线程的引用
        Thread t1 = Thread.currentThread();
        System.out.println("子线程的编号是:"+ t1.getId() +",名称是:"+ t1.getName());
        t1.setName("关羽");
        System.out.println("子线程的编号是:"+ t1.getId() +",名称是:"+ t1.getName());
    }
​
    public static void main(String[] args) {
        RunnableIdNameTest rint = new RunnableIdNameTest();
        //Thread t2 = new Thread(rint);
        Thread t2 = new Thread(rint,"赵云");
        t2.start();
​
        Thread t1 = Thread.currentThread();
        System.out.println("主线程的编号是:"+ t1.getId() +",名称是:"+ t1.getName());
    }
}
​
​
/*继承Thread类进行操作*/
public class ThreadIdNameTest extends Thread{
    public ThreadIdNameTest(String name) {
        super(name); // 表示调用父类的构造方法
    }
​
    @Override
    public void run(){
        System.out.println("子线程的编号是:"+ getId() + ",名称是:"+ getName());
        // 修改名称为赵云
        setName("赵云");
        System.out.println("子线程的编号是:"+ getId() + ",名称是:"+ getName());
    }
​
    public static void main(String[] args) {
        ThreadIdNameTest tint = new ThreadIdNameTest("关羽");
        tint.start();
        // 获取当前正在执行线程的引用,当前正在执行的线程是主线程,也就是获取主线程的引用
        Thread t1 = Thread.currentThread();
        System.out.println("主线程的编号是:"+ t1.getId() +",名称是:"+ t1.getName());
    }
}

5、常用的方法(重点):

/*继承Thread类实现上述题目*/
// 线程一
public class SubThread1 extends Thread {
    @Override
    public void run(){
        // 线程1打印1~100所有的奇数
        for (int i = 1; i <= 100; i+=2){
            System.out.println("1~100间所有的奇数有:"+ i);
        }
    }
}
// 线程二
public class SubThread2 extends Thread {
    @Override
    public void run(){
        // 线程1打印1~100所有的偶数
        for (int i = 2; i <= 100; i+=2){
            System.out.println("1~100间所有的偶数有:"+ i);
        }
    }
}
// 测试继承Thread类的两个线程打印
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("主线程等待结束");
    }
}
​
/*继承Runnable接口实现上述题目*/
// 线程一:
public class SubRunnable1 implements Runnable {
    @Override
    public void run(){
        // 线程1打印1~100所有的奇数
        for (int i = 1; i <= 100; i+=2){
            System.out.println("1~100间所有的奇数有:"+ i);
        }
    }
}
// 线程二:
public class SubRunnable2 implements Runnable{
    @Override
    public void run(){
        // 线程1打印1~100所有的偶数
        for (int i = 2; i <= 100; i+=2){
            System.out.println("1~100间所有的偶数有:"+ i);
        }
    }
}
// 测试继承Runnable接口的两个线程打印
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("主线程等待结束");
    }
}

6、线程同步机制(重点):

6.1 基本概念:

  • 当多个线程同时访问同一种共享资源时,可能会造成数据的覆盖等不一致性问题,此时就需要对线程之间进行通信和协调,该机制就叫做线程的同步机制。

  • 多个线程并发读写同一个临界资源时会发生线程并发安全问题。

  • 异步操作:多线程并发的操作,各自独立运行。

  • 同步操作:多线程串行的操作,先后执行的顺序。

6.2 解决方案

  • 由程序结果可知:当两个线程同时对同一个账户进行取款时,导致最终的账户余额不合理。

  • 引发原因:线程一执行取款时还没来得及将取款后的余额写入后台,线程二就已经开始取款。

  • 解决方案:让线程一执行完毕取款操作后,再让线程二执行即可,将线程的并发操作改为串行操作。

  • 经验分享:在以后的开发尽量减少串行操作的范围,从而提高效率。

6.3 实现方式:

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

  • 使用同步代码块的方式实现部分代码的锁定,格式如下:

    synchronized(类类型的引用) {

    编写所有需要锁定的代码;

    }

  • 使用同步方法的方式实现所有代码的锁定。

    直接使用synchronized关键字来修饰整个方法即可

    该方式等价于:

    synchronized(this) { 整个方法体的代码 }

6.4 静态方法的锁定:

  • 当我们对一个静态方法加锁,如:

    public synchronized static void xxx(){….}

  • 那么该方法锁的对象是类对象。每个类都有唯一的一个类对象。获取类对象的方式:类名.class。

  • 静态方法与非静态方法同时使用了synchronized后它们之间是非互斥关系的。

  • 原因在于:静态方法锁的是类对象而非静态方法锁的是当前方法所属对象。

6.5 注意事项:

  • 使用synchronized保证线程同步应当注意:

    多个需要同步的线程在访问同步块时,看到的应该是同一个锁对象引用。

    在使用同步块时应当尽量减少同步范围以提高并发的执行效率。

// 继承Runnable接口实现同步功能(锁定)6.2标题
import java.util.concurrent.locks.ReentrantLock;
​
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) {
        //synchronized (new Demo()) {// 锁不住 要求必须是同一个对象
            // 模拟从后台查询账户余额的过程
            int temp = getBalance();
            // 模拟取款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("余额不足,请核对您的账户余额!");
            }
            // 模拟将最新的账户余额写入到后台
            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();// 也就是等待线程一取款操作结束后再启动线程二
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("最终的账户余额为:"+ account.getBalance());
    }
}
​
class Demo{
​
}
​
​
// 继承Thread类实现同步功能(锁定)6.2标题
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 /*synchronized*/ void run() {
        /*System.out.println("线程"+ Thread.currentThread().getName() + "已启动");
        //synchronized (dm) {// ok
            //synchronized (new Demo()) {// 锁不住 要求必须是同一个对象
            // 模拟从后台查询账户余额的过程
            int temp = getBalance();
            // 模拟取款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("余额不足,请核对您的账户余额!");
            }
            // 模拟将最新的账户余额写入到后台
            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()) {// 锁不住 要求必须是同一个对象
        // 模拟从后台查询账户余额的过程
        int temp = 1000;//getBalance()
        // 模拟取款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("余额不足,请核对您的账户余额!");
        }
        // 模拟将最新的账户余额写入到后台
        //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();// 也就是等待线程一取款操作结束后再启动线程二
            att2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("最终的账户余额为:"+ att2.getBalance());
    }
}

6.6 线程安全类和不安全类:

  • StringBuffer类是线程安全的类,但StringBuilder类不是线程安全的类。

  • Vector类和 Hashtable类是线程安全的类,但ArrayList类和HashMap类不是线程安全的类。

  • Collections.synchronizedList() 和 Collections.synchronizedMap()等方法实现安全。

6.7 死锁的概念:

  • 线程一执行的代码:

    public void run(){

    synchronized(a){ //持有对象锁a,等待对象锁b

         synchronized(b){

                      编写锁定的代码;

                 }

            }

    }

  • 线程二执行的代码:

    public void run(){

         synchronized(b){ //持有对象锁b,等待对象锁a

            synchronized(a){

               编写锁定的代码;

            }

         }

    }

  • 注意:

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

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

6.8.1 基本概念:

  • 从Java5开始提供了更强大的线程同步机制—使用显式定义的同步锁对象来实现。

  • java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。

  • 该接口的主要实现类是ReentrantLock类,该类拥有与synchronized相同的并发性,在以后的线程安全控制中,经常使用ReentrantLock类显式加锁和释放锁。

6.8.2 常用的方法:(详看6.5下面代码 )

6.8.3 与synchronized方式的比较:

  • Lock是显式锁,需要手动实现开启和关闭操作,而synchronized是隐式锁,执行锁定代码后自动释放。

  • Lock只有同步代码块方式的锁,而synchronized有同步代码块方式和同步方法两种锁。

  • 使用Lock锁方式时,Java虚拟机将花费较少的时间来调度线程,因此性能更好。

6.9 Object类常用的方法:

public class ThreadCommunicateTest implements Runnable {
    private int cnt = 1;
    @Override
    public void run() {
        while (true){
            synchronized (this) {
                // 每当有一个线程进来后,将wait叫醒,调用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();
    }
}
// **********题目:消费和生产产品,当仓库装满时,停止生产,当消费产品后继续生产,当无产品时,停止消费
​
/*
* 编程实现仓库类
* */
public class StoreHouse {
    private int cnt = 0;// 用于记录产品的数量
​
    public synchronized void productProduct() {
        notify();
        if (cnt < 10){
            System.out.println("线程" + Thread.currentThread().getName()+"正在生产第"+ (cnt+1) +"个产品");
            cnt++;
        } else {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
​
    public synchronized void consumerProduct() {
        notify();
        if (cnt > 0){
            System.out.println("线程" + Thread.currentThread().getName()+"正在消费第"+ cnt +"个产品");
            cnt--;
        } else {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
​
/*
 * 编程实现消费者线程,不断地消费产品
 * */
public class ConsumerThread extends Thread {
    // 声明一个仓库类型的引用作为成员变量,是为了能调用仓库类中的生产方法  合成复用原则
    private StoreHouse storeHouse;
    // 为了确保两个线程共用同一个仓库
    public ConsumerThread(StoreHouse storeHouse){
        this.storeHouse = storeHouse;
    }
    @Override
    public void run(){
        while (true){
            storeHouse.consumerProduct();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
​
    }
}
​
/*
* 编程实现生产者线程,不断地生产产品
* */
public class ProduceThread extends Thread {
    // 声明一个仓库类型的引用作为成员变量,是为了能调用仓库类中的生产方法  合成复用原则
    private StoreHouse storeHouse;
    // 为了确保两个线程共用同一个仓库
    public ProduceThread(StoreHouse storeHouse){
        this.storeHouse = storeHouse;
    }
    @Override
    public void run(){
        while (true){
            storeHouse.productProduct();
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
​
    }
}
// 测试
public class StoreHouseTest {
    public static void main(String[] args) {
        // 创建仓库对象
        StoreHouse storeHouse = new StoreHouse();
        // 创建线程类对象并启动
        ProduceThread t1 = new ProduceThread(storeHouse);
        ConsumerThread t2 = new ConsumerThread(storeHouse);
        t1.start();
        t2.start();
    }
}

6.10 线程池(熟悉):

6.10.1 实现Callable接口:

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

  • 常用的方法如下:

6.10.2 FutureTask类:

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

  • 常用的方法如下:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
​
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);
        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);
    }
}

6.10.3 线程池的由来:

  • 在服务器编程模型的原理,每一个客户端连接用一个单独的线程为之服务,当与客户端的会话结束时,线程也就结束了,即每来一个客户端连接,服务器端就要创建一个新线程。

  • 如果访问服务器的客户端很多,那么服务器要不断地创建和销毁线程,这将严重影响服务器的性能。

6.10.4 概念和原理:

  • 线程池的概念:首先创建一些线程,它们的集合称为线程池,当服务器接受到一个客户请求后,就从线程池中取出一个空闲的线程为之服务,服务完后不关闭该线程,而是将该线程还回到线程池中。

  • 在线程池的编程模式下,任务是提交给整个线程池,而不是直接交给某个线程,线程池在拿到任务后,它就在内部找有无空闲的线程,再把任务交给内部某个空闲的线程,任务是提交给整个线程池,一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务。

6.10.5 相关类和方法

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

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

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

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
​
public class ThreadPoolTest {
    public static void main(String[] args) {
        // 创建一个线程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        // 向线程池中布置任务
        executorService.submit(new ThreadCallableTest());
        // 关闭线程池
        executorService.shutdown();
    }
}

记录四:

1、网络编程的常识:

  • 目前主流的网络通讯软件有:微信、QQ、飞信、阿里旺旺、陌陌、探探、...

1.1 七层网络模型

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

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

  • 当发送数据时,需要对发送的内容按照上述七层模型进行层层加包后发送出去。

  • 当接收数据时,需要对接收的内容按照上述七层模型相反的次序层层拆包并显示出来。

1.2 相关的协议(笔试题):

1.2.1 协议的概念:

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

1.2.2 TCP协议:

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

    建立连接 => 进行通信 => 断开连接

    在传输前采用"三次握手"方式。

    在通信的整个过程中全程保持连接,形成数据传输通道。

    保证了数据传输的可靠性和有序性。

    是一种全双工的字节流通信方式,可以进行大数据量的传输。

    传输完毕后需要释放已建立的连接,发送数据的效率比较低。

1.2.3 UDP协议:

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

    在通信的整个过程中不需要保持连接,其实是不需要建立连接。

    不保证数据传输的可靠性和有序性。

    是一种全双工的数据报通信方式,每个数据报的大小限制在64K内。

    发送数据完毕后无需释放资源,开销小,发送数据的效率比较高,速度快。

1.3 IP地址(重点):

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

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

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

  • 如:

    0x01020304 => 1.2.3.4

  • 查看IP地址的方式:

    Windows系统:在dos窗口中使用ipconfifig或ipconfifig/all命令即可

    Unix/linux系统:在终端窗口中使用ifconfifig或/sbin/ifconfifig命令即可

  • 特殊的地址

    本地回环地址(hostAddress):127.0.0.1 主机名(hostName):localhost

1.4 端口号(重点):

  • IP地址 - 可以定位到具体某一台设备。

  • 端口号 - 可以定位到该设备中具体某一个进程。

  • 端口号本质上是16位二进制组成的整数,表示范围是:0 ~ 65535,其中0 ~ 1024之间的端口号通常被系统占用,建议编程从1025开始使用。

  • 特殊的端口:

    HTTP:80 FTP:21 Oracle:1521 MySQL:3306 Tomcat:8080

  • 网络编程需要提供:IP地址 + 端口号,组合在一起叫做网络套接字:Socket。

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

2.1 C/S结构的简介:

  • 在C/S模式下客户向服务器发出服务请求,服务器接收请求后提供服务。

  • 例如:在一个酒店中,顾客找服务员点菜,服务员把点菜单通知厨师,厨师按点菜单做好菜后让服务员端给客户,这就是一种C/S工作方式。如果把酒店看作一个系统,服务员就是客户端,厨师就是服务器。这种系统分工和协同工作的方式就是C/S的工作方式。

  • 客户端部分:为每个用户所专有的,负责执行前台功能。

  • 服务器部分:由多个用户共享的信息与功能,招待后台服务。

2.2 编程模型:

  • 服务器:

    (1)创建ServerSocket类型的对象并提供端口号;

    (2)等待客户端的连接请求,调用accept()方法;

    (3)使用输入输出流进行通信;

    (4)关闭Socket;

  • 客户端:

    (1)创建Socket类型的对象并提供服务器的IP地址和端口号;

    (2)使用输入输出流进行通信;

    (3)关闭Socket;

2.3 相关类和方法的解析:

2.3.1 ServerSocket类:

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

  • 常用的方法如下:

2.3.2 Socket类:

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

  • 常用的方法如下:

2.3.3 注意事项:

  • 客户端 Socket 与服务器端 Socket 对应, 都包含输入和输出流。

  • 客户端的socket.getInputStream() 连接于服务器socket.getOutputStream()。

  • 客户端的socket.getOutputStream()连接于服务器socket.getInputStream()

/* 原生c/s交互代码 实现互相通信  运行时,先启动服务器,在启动客户端*/
// 服务器端
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
​
public class ServerStringTest {
    public static void main(String[] args) {
        ServerSocket ss = null;
        Socket s = null;
        BufferedReader br = null;
        PrintStream ps = null;
        try {
            // 创建ServerSocket类型的对象并提供端口号
            ss = new ServerSocket(8888);
            // 等待客户端的连接请求,调用accept方法
            System.out.println("等待客户端的连接请求。。。");
            // 当没有客户端连接时,则服务器阻塞在accept方法的调用这里
            s = ss.accept();
            System.out.println("客户端连接成功");
            // 使用输入输出流进行通信
            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;
                }
                // 实现服务器向客户端回发字符串内容
​
                ps.println("I received!");
                System.out.println("服务器发送数据成功");
            }
​
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭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();
                }
            }
​
        }
    }
}
​
// 客户端
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
​
public class ClientStringTest {
    public static void main(String[] args) {
        Socket s = null;
        PrintStream ps = null;
        Scanner sc = null;
        BufferedReader br = null;
        try {
            // 创建Socket类型的对象并提供服务器的主机名和端口号
            s = new Socket("127.0.0.1",8888);
            System.out.println("链接服务器成功");
            // 使用输入输出流进行通信
            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 next = sc.next();
                // 实现客户端向服务器发送字符串内容 hello
​
                //ps.println("hello");
                ps.println(next);
                System.out.println("客户端发送数据内容成功");
                // 当发送的数据内容为:bye时,则聊天结束
                if ("bye".equalsIgnoreCase(next)){
                    System.out.println("聊天结束!");
                    break;
                }
                // 用于接收服务器发来的字符串内容并打印
​
                String s1 = br.readLine();
                System.out.println("服务器端发来的字符串内容是:" + s1);
            }
        } catch (IOException /*| InterruptedException*/ e) {
            e.printStackTrace();
        } finally {
            // 关闭Socket并释放有关资源
            if (null != br){
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != sc){
                sc.close();
            }
            if (null != s){
                try {
                    s.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != ps){
                ps.close();
            }
        }
​
    }
}
​
/* 优化上面代码 以多线程实现c/s交互 运行时,先启动服务器,在启动客户端*/
// 客户端 测试时 复制多个客户端代码进行检测
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
​
public class ClientStringTest {
    public static void main(String[] args) {
        Socket s = null;
        PrintStream ps = null;
        Scanner sc = null;
        BufferedReader br = null;
        try {
            // 创建Socket类型的对象并提供服务器的主机名和端口号
            s = new Socket("127.0.0.1",8888);
            System.out.println("链接服务器成功");
            // 使用输入输出流进行通信
            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 next = sc.next();
                // 实现客户端向服务器发送字符串内容 hello
​
                //ps.println("hello");
                ps.println(next);
                System.out.println("客户端发送数据内容成功");
                // 当发送的数据内容为:bye时,则聊天结束
                if ("bye".equalsIgnoreCase(next)){
                    System.out.println("聊天结束!");
                    break;
                }
                // 用于接收服务器发来的字符串内容并打印
​
                String s1 = br.readLine();
                System.out.println("服务器端发来的字符串内容是:" + s1);
            }
        } catch (IOException /*| InterruptedException*/ e) {
            e.printStackTrace();
        } finally {
            // 关闭Socket并释放有关资源
            if (null != br){
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != sc){
                sc.close();
            }
            if (null != s){
                try {
                    s.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != ps){
                ps.close();
            }
        }
​
    }
}
​
// 服务器端
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
​
public class ServerStringTest {
    public static void main(String[] args) {
        ServerSocket ss = null;
        Socket s = null;
​
        try {
            // 创建ServerSocket类型的对象并提供端口号
            ss = new ServerSocket(8888);
            // 等待客户端的连接请求,调用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 {
            // 关闭Socket并释放有关的资源
            if (null != ss){
                try {
                    ss.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
​
        }
    }
}
​
// 封装多线程运行代码
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.InetAddress;
import java.net.Socket;
​
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 {
            // 使用输入输出流进行通信
            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;
                }
                // 实现服务器向客户端回发字符串内容
​
                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();
                }
            }
        }
    }
}

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

3.1 编程模型:

  • 接收方:

    (1)创建DatagramSocket类型的对象并提供端口号;

    (2)创建DatagramPacket类型的对象并提供缓冲区;

    (3)通过Socket接收数据内容存放到Packet中,调用receive方法;

    (4)关闭Socket;

  • 发送方:

    (1)创建DatagramSocket类型的对象;

    (2)创建DatagramPacket类型的对象并提供接收方的通信地址;

    (3)通过Socket将Packet中的数据内容发送出去,调用send方法;

    (4)关闭Socket;

3.2 相关类和方法的解析:

3.2.1 DatagramSocket类:

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

  • 常用的方法如下:

3.2.2 DatagramPacket类:

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

  • 常用的方法如下:

3.2.3 InetAddress类:

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

  • 常用的方法如下:

// 接收方
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
​
public class ReceiveTest {
    public static void main(String[] args) {
        DatagramSocket ds = null;
        try {
            // 创建DatagramSocket类型的对象并提供端口号
            ds = new DatagramSocket(8888);
            // 创建DatagramPacket类型的对象并提供缓冲区
            byte[] bArr = new byte[20];
            DatagramPacket dp = new DatagramPacket(bArr, bArr.length);
            // 通过Socket接收数据内容存放到Packet里面,调用receive方法
            System.out.println("等待数据的到来。。。");
            ds.receive(dp);
            System.out.println("接收到的数据内容是:"+ new String(bArr,0,dp.getLength())+"!");
            // 实现将字符串内容回发过去
            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 {
            // 关闭Socket并释放有关的资源
            if (null != ds){
                ds.close();
            }
​
        }
​
    }
}
​
// 发送方
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
​
public class SendTest {
    public static void main(String[] args) {
        DatagramSocket ds = null;
        try {
            // 创建DatagramSocket类型的对象
            ds = new DatagramSocket();
            // 创建DatagramPacket类型的对象并提供接收方的通信地址和端口号
            byte[] bArr = "hello".getBytes();
            DatagramPacket dp = new DatagramPacket(bArr,bArr.length, InetAddress.getLocalHost(),8888);
            // 通过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 {
            // 关闭Socket并释放有关资源
            if (null != ds){
                ds.close();
            }
        }
​
    }
}

4、URL类(熟悉):

4.1 基本概念:

  • java.net.URL(Uniform Resource Identififier)类主要用于表示统一的资源定位器,也就是指向万维网上“资源”的指针。这个资源可以是简单的文件或目录,也可以是对复杂对象的引用,例如对数据库或搜索引擎的查询等。

  • 通过URL可以访问万维网上的网络资源,最常见的就是www和ftp站点,浏览器通过解析给定的URL可以在网络上查找相应的资源。

  • URL的基本结构如下:

    <传输协议>://<主机名>:<端口号>/<资源地址>

4.2 常用的方法:

4.3 URLConnection类:

4.3.1 基本概念:

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

4.3.2 HttpURLConnection类的常用方法:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
​
public class UrlTest {
    public static void main(String[] args) {
        try {
            // 使用参数指定的字符串来构造对象
            URL url = new URL("https://www.lagou.com/");
            // 获取相关信息并打印出来
            System.out.println("获取到的协议名称是:"+ url.getProtocol());
            System.out.println("获取到的主机名称是:"+ url.getHost());
            System.out.println("获取到的端口号是:"+ url.getPort());
​
            // 建立连接并读取相关信息并打印出来
            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();
        }
    }
}

记录五:反射机制:

1、基本概念:

  • 通常情况下编写代码都是固定的,无论运行多少次执行的结果也是固定的,在某些特殊场合中编写代码时不确定要创建什么类型的对象,也不确定要调用什么样的方法,这些都希望通过运行时传递的参数来决定,该机制叫做动态编程技术,也就是反射机制。

  • 通俗来说,反射机制就是用于动态创建对象并且动态调用方法的机制。

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

  • 如:

    Person p = new Person(); - 表示声明Person类型的引用指向Person类型的对象

    p.show(); - 表示调用Person类中的成员方法show

2、Class类:

2.1 基本概念:

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

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

2.2 获取Class对象的方式:

  • 使用数据类型.class的方式可以获取对应类型的Class对象(掌握)。

  • 使用引用/对象.getClass()的方式可以获取对应类型的Class对象。

  • 使用包装类.TYPE的方式可以获取对应基本数据类型的Class对象。

  • 使用Class.forName()的方式来获取参数指定类型的Class对象(掌握)。

  • 使用类加载器ClassLoader的方式获取指定类型的Class对象。

public class ClassTest {
    public static void main(String[] args) throws ClassNotFoundException {
        // 使用数据类型.class的方式可以获取对应类型的Class对象
        Class c1 = String.class;
        System.out.println("c1 = "+ c1);// 自动调用toString方法
        c1 = int.class;
        System.out.println("c1 = "+ c1);
        c1 = void.class;
        System.out.println("c1 = "+ c1);
​
        System.out.println("------------------");
        // 使用对象.getClass()方式获取对应的Class对象
        String str1 = new String("hello");
        c1 = str1.getClass();
        System.out.println("c1 = "+ c1);
        Integer it1 = 20;
        c1 = it1.getClass();
        System.out.println("c1 = "+ c1);
        //int num = 5;
        //num.getClass(); // Error: 基本数据类型的变量不能调用方法
​
        System.out.println("------------------");
        // 使用包装类.TYPE的方式来获取对应基本数据类型的Class对象
        c1 = Integer.TYPE;
        System.out.println("c1 = "+ c1);
        c1 = Integer.class;
        System.out.println("c1 = "+ c1);
​
        System.out.println("----------------------");
        // 调用Class类中的forName方法来获取对应的Class对象
        //c1 = Class.forName("String"); // Error 要求写完整的名称:包名.类名
        c1 = Class.forName("java.lang.String");
        System.out.println("c1 = "+ c1);
​
        c1 = Class.forName("java.util.Date");
        System.out.println("c1 = "+ c1);
​
        //c1 = Class.forName("int");
        //System.out.println("c1 = "+ c1);// 不能获取基本数据类型的Class对象
​
        System.out.println("--------------------------");
        // 使用类加载器的方式来获取Class对象
        ClassLoader classLoader = ClassTest.class.getClassLoader();
        System.out.println("classLoader = "+ classLoader);
        c1 = classLoader.loadClass("java.lang.String");
        System.out.println("c1 = "+ c1);
    }
}

2.3 常用的方法(掌握):

3、Constructor类:

3.1 基本概念:

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

3.2 Class类的常用方法:

3.3 Constructor类的常用方法:

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.util.Scanner;
​
public class PersonConstructorTest {
    public static void main(String[] args) throws Exception {
        // 使用原始方式以无参形式构造Person类型的对象并打印
        Person p1 = new Person();
        System.out.println("无参方式创建的对象是:"+ p1);// null 0
​
        System.out.println("--------------------");
        // 使用反射机制以无参形式构造Person类型的对象并打印
        // 创建对象的类型可以从键盘输入
        //System.out.println("请输入要创建对象的类型:");
        //Scanner sc = new Scanner(System.in);
        //String str1 = sc.next();
        //Class c1 = Class.forName("com.lagou.task20.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("---------------------------");
        // 使用原始方法以有参方式构造Person类型的对象并打印
        Person p2 = new Person("关羽",30);
        System.out.println("有参方式构造的对象是:"+ p2);
        System.out.println("------------------------------");
        // 使用反射机制以有参方式构造Person类型的对象并打印
        // 获取Class对象对应类中的有参构造方法,也就是Person类中的有参构造方法
        Constructor constructor1 = c1.getConstructor(String.class, int.class);
        // 使用获取到的有参构造方法来构造对应类型的对象,也就是Person类型的对象
        // newInstance方法中的实参是用于给有参构造方法的形参进行初始化的,也就是给name和age进行初始化
        System.out.println("有参方式构造的对象是:"+ constructor1.newInstance("赵云",18));
​
        System.out.println("------------------------");
        // 使用反射机制获取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("-------------------------------");
        }    
    }
}

4、Field类:

4.1 基本概念:

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

4.2 Class类的常用方法:

4.3 Field类的常用方法:

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
​
public class PersonFieldTest {
    public static void main(String[] args) throws Exception {
        // 使用原始方式来构造对象以及获取成员变量的数值并打印
        Person p1 = new Person("赵云",18);
        //System.out.println("获取到的成员变量数值为:" + p1.name);
​
        System.out.println("-----------------------");
        // 使用反射机制来构造对象以及获取成员变量的数值并打印
        // 获取Class对象
        Class c1 = Class.forName("com.lagou.task20.Person");
        // 根据Class对象获取对应的有参构造方法
        Constructor constructor = c1.getConstructor(String.class, int.class);
        // 使用有参构造方法来得到Person类型的对象
        Object obj = constructor.newInstance("赵云",18);
        // 根据Class对象获取对应的成员变量信息
        Field field = c1.getDeclaredField("name");
        // 设置java语言访问检查的取消 暴力反射
        field.setAccessible(true);
        // 使用Person类型的对象来获取成员变量的数值并打印
        // 获取对象obj中名字为field成员变量的数值,也就是成员变量name的数值
        System.out.println("获取到的成员变量数值为:"+ field.get(obj));
​
        System.out.println("------------------------");
        // 使用原始方式修改制定对象中成员变量的数值后再次打印
        //p1.name = "诸葛亮";
        //System.out.println("修改后的成员变量数值为:"+ p1.name);
​
        System.out.println("-----------------------------");
        // 使用反射机制修改指定对象中成员变量的数值后再次打印
        // 表示修改对象obj中名字为field成员变量的数值为刘备,也就是成员变量name的数值为刘备
        field.set(obj,"刘备");
        System.out.println("修改后的成员变量数值为:"+ field.get(obj));
​
        System.out.println("-----------------------");
        // 获取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("------------------------");
        }
    }
}

5、Method类:

5.1 基本概念:

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

5.2 Class类的常用方法:

5.3 Method类的常用方法:

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
​
public class PersonMethodTest {
    public static void main(String[] args) throws Exception {
        // 使用原始方式构造对象并调用方法打印结果
        Person p1 = new Person("赵云",18);
        System.out.println("调用方法的返回值是:"+ p1.getName());
​
        System.out.println("--------------------------");
        // 使用反射机制构造对象并调用方法打印结果
        // 获取Class对象
        Class c1 = Class.forName("com.lagou.task20.Person");
        // 根据Class对象来获取对应的有参构造方法
        Constructor constructor = c1.getConstructor(String.class, int.class);
        // 使用有参构造方法构造对象并记录
        Object obj = constructor.newInstance("赵云", 18);
        // 根据Class对象来获取对应的成员方法
        Method method = c1.getMethod("getName");
        // 使用对象调用成员方法进行打印
        // 表示使用obj对象调用method表示的方法,也就是调用getName方法获取姓名
        System.out.println("调用方法的返回值是:"+ method.invoke(obj));
​
        System.out.println("-------------------------");
        // 使用反射机制来获取类中的所有成员方法并打印
        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("----------------------");
        }
    }
}

6、获取其他结构信息:

// 创建泛型Student类继承Person类和Comparable泛型接口、Serializable接口
import java.io.Serializable;
​
@MyAnnotation
public class Student<T, E> extends Person implements Comparable<String>, Serializable {
    @Override
    public int compareTo(String o) {
        return 0;
    }
}
​
// 创建MyAnnotation接口
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
​
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
}
​
// 测试获取相应的信息
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
​
public class StudentTest {
    public static void main(String[] args) throws Exception {
        // 获取Student类型的Class对象
        Class c1 = Class.forName("com.lagou.task20.Student");
        System.out.println("获取到的包信息是:"+ c1.getPackageName());
        System.out.println("获取到的父类信息是:"+ c1.getSuperclass());
        System.out.println("获取到的接口信息是:");
        Class[] interfaces = c1.getInterfaces();
        for (Class i : interfaces){
            System.out.print(i + "  ");
        }
        System.out.println();
        System.out.println("获取到的注解信息是:");
        Annotation[] annotations = c1.getAnnotations();
        for (Annotation a : annotations){
            System.out.print(a + "  ");
        }
        System.out.println();
        System.out.println("获取到的泛型信息是:");
        Type[] genericInterfaces = c1.getGenericInterfaces();
        for (Type g : genericInterfaces){
            System.out.print(g + "  ");
        }
        System.out.println();
    }
}
​

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值