Java模块四笔记

(笔记范围:异常、IO流、多线程、网络编程、反射机制)
一、异常机制和File类
1.异常机制(重点)
1.1.基本概念
异常就是"不正常"的含义,在Java语言中主要指程序执行中发生的不正常情况。
java.lang.Throwable类是Java语言中错误(Error)和异常(Exception)的超类。
其中Error类主要用于描述Java虚拟机无法解决的严重错误,通常无法编码解决,如:JVM挂掉等。
其中Exception类主要用于描述因编程错误或偶然外在因素导致的轻微错误,通常可以编码解决
如:0作为除数等。

1.2.异常的分类
java.lang.Exception类是所有异常的超类,主要分为以下两种:
RuntimeException - 运行时异常,也叫作非检测性异常
IOException和其它异常 - 其它异常,也叫作检测性异常,所谓检测性异常就是指在编译阶段都能
被编译器检测出来的异常。

其中RuntimeException类的主要子类:
ArithmeticException类 - 算术异常
ArrayIndexOutOfBoundsException类 - 数组下标越界异常
NullPointerException - 空指针异常
ClassCastException - 类型转换异常
NumberFormatException - 数字格式异常

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

1.	public class ExceptionTest {
    
      
2.	  
3.	    public static void main(String[] args) {
    
      
4.	  
5.	        // 1.见识一下非检测性异常  运行时异常  
6.	        System.out.println(5 / 0); // 编译ok,运行阶段会发生算术异常  下面的代码不会执行  
7.	  
8.	        // 2.检测性异常  
9.	        //Thread.sleep(1000); // 编译错误  不处理就无法到运行阶段  
10.	  
11.	        System.out.println("程序正常结束了!");  
12.	    }  
13.	}  

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

1.	import java.io.IOException;  
2.	  
3.	public class ExceptionPreventTest {
    
      
4.	  
5.	    public static void main(String[] args) {
    
      
6.	  
7.	        // 会发生算术异常  
8.	        int ia = 10;  
9.	        int ib = 0;  
10.	        if (0 != ib) {
    
      
11.	            System.out.println(ia / ib);  
12.	        }  
13.	  
14.	        // 数组下标越界异常  
15.	        int[] arr = new int[5];  
16.	        int pos = 5;  
17.	        if (pos >= 0 && pos < 5) {
    
      
18.	            System.out.println(arr[pos]);  
19.	        }  
20.	  
21.	        // 发生空指针异常  
22.	        String str = null;  
23.	        if (null != str) {
    
      
24.	            System.out.println(str.length());  
25.	        }  
26.	  
27.	        // 类型转换异常  
28.	        Exception ex = new Exception();  
29.	        if (ex instanceof IOException) {
    
      
30.	            IOException ie = (IOException) ex;  
31.	        }  
32.	  
33.	        // 数字格式异常  
34.	        String str2 = "123a";  
35.	        if (str2.matches("\\d+")) {
    
      
36.	            System.out.println(Integer.parseInt(str2));  
37.	        }  
38.	  
39.	        System.out.println("程序总算正常结束了!");  
40.	    }  
41.	}  

1.4.异常的捕获
语法格式
try {
编写可能发生异常的代码;
}
catch(异常类型 引用变量名) {
编写针对该类异常的处理代码;
}

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

1.	import java.io.FileInputStream;  
2.	import java.io.FileNotFoundException;  
3.	import java.io.IOException;  
4.	  
5.	public class ExceptionCatchTest {
    
      
6.	  
7.	    public static void main(String[] args) {
    
      
8.	  
9.	        // 创建一个FileInputStream类型的对象与d:/a.txt文件关联,打开文件  
10.	        FileInputStream fis = null;  
11.	        try {
    
      
12.	            System.out.println("1");  
13.	            // 当程序执行过程中发生了异常后直奔catch分支进行处理  
14.	            fis = new FileInputStream("d:/a.txt");  
15.	            System.out.println("2");  
16.	        } catch (FileNotFoundException e) {
    
      
17.	            System.out.println("3");  
18.	            e.printStackTrace();  
19.	            System.out.println("4");  
20.	        }  
21.	        // 关闭文件  
22.	        try {
    
      
23.	            System.out.println("5");  
24.	            fis.close();  
25.	            System.out.println("6");  
26.	        } /*catch (Exception e) { 
27.	            e.printStackTrace(); 
28.	        }*/ catch (IOException e) {
    
      
29.	            System.out.println("7");  
30.	            e.printStackTrace();  
31.	            System.out.println("8");  
32.	        } catch (NullPointerException e) {
    
      
33.	            e.printStackTrace();  
34.	        } catch (Exception e) {
    
      
35.	            e.printStackTrace();  
36.	        }  
37.	  
38.	        System.out.println("世界上最真情的相依就是你在try我在catch,无论你发神马脾气我都默默承受并静静的处理,到那时再来期待我们的finally!");  
39.	        // 当程序执行过程中没有发生异常时的执行流程:1 2  5 6  世界上...  
40.	        // 当程序执行过程中发生异常又没有手动处理空指针异常时的执行流程:1 3 4  5  空指针异常导致程序终止  
41.	        // 当程序执行过程中发生异常并且手动处理空指针异常时的执行流程: 1 3 4 5 世界上...  
42.	  
43.	        // 手动处理异常和没有处理的区别:代码是否可以继续向下执行  
44.	    }  
45.	}  

注意事项
a.当需要编写多个catch分支时,切记小类型应该放在大类型的前面;
b.懒人的写法:
catch(Exception e) {}
c.finally通常用于进行善后处理,如:关闭已经打开的文件等。
执行流程
try {
a;
b; - 可能发生异常的语句
c;
}catch(Exception ex) {
d;
}finally {
e;
}
当没有发生异常时的执行流程:a b c e;
当发生异常时的执行流程:a b d e;

1.	public class ExceptionFinallyTest {
    
      
2.	  
3.	    // 笔试考点  
4.	    public static int test() {
    
      
5.	        try {
    
      
6.	            int[] arr = new int[5];  
7.	            System.out.println(arr[5]);  
8.	            return 0;  
9.	        } catch (ArrayIndexOutOfBoundsException e) {
    
      
10.	            e.printStackTrace();  
11.	            return 1;  //return 1并没有被执行
12.	        } finally {
    
      
13.	            return 2; // 提交结束方法并返回数据  
14.	        }  
15.	    }  
16.	  
17.	    public static void main(String[] args) {
    
      
18.	  
19.	        try {
    
      
20.	            int ia = 10;  
21.	            int ib = 0;  
22.	            System.out.println(ia / ib);  
23.	        } catch (ArithmeticException e) {
    
      
24.	            e.printStackTrace();  
25.	  
26.	            String str1 = null;  
27.	            //str1.length(); // 会发生空指针异常  
28.	  
29.	        } finally {
    
      
30.	            System.out.println("无论是否发生异常都记得来执行我哦!");  // 依然是执行  
31.	        }  
32.	  
33.	        System.out.println("Over!"); // 不执行了  
34.	  
35.	        System.out.println("----------------------------------------");  
36.	        int test = test();  
37.	        System.out.println("test = " + test); // 2  
38.	    }  
39.	}  

1.5.异常的抛出
基本概念
在某些特殊情况下有些异常不能处理或者不便于处理时,就可以将该异常转移给该方法的调用者,
这种方法就叫异常的抛出。当方法执行时出现异常,则底层生成一个异常类对象抛出,此时异常代
码后续的代码就不再执行。
语法格式
访问权限 返回值类型 方法名称(形参列表) throws 异常类型1,异常类型2,…{ 方法体; }
如:
public void show() throws IOException{}
方法重写的原则
a.要求方法名相同、参数列表相同以及返回值类型相同,从jdk1.5开始支持返回子类类型;
b.要求方法的访问权限不能变小,可以相同或者变大;
c.要求方法不能抛出更大的异常;

1.	import java.io.IOException;  
2.	  
3.	public class ExceptionMethod {
    
      
4.	  
5.	    public void show() throws IOException {
    
    }  
6.	}  


1.	import com.sun.jdi.ClassNotLoadedException;  
2.	import java.io.FileNotFoundException;  
3.	import java.io.IOException;  
4.	  
5.	public class SubExceptionMethod extends ExceptionMethod {
    
      
6.	  
7.	    @Override  
8.	    public void show() throws IOException {
    
    } // 子类重写的方法可以抛出和父类中方法一样的异常  
9.	    //public void show() throws FileNotFoundException {}  // 子类重写的方法可以抛出更小的异常  
10.	    //public void show() {} // 子类可以不抛出异常  
11.	    //public void show() throws ClassNotLoadedException {} // 不可以抛出平级不一样的异常  
12.	//public void show() throws Exception {} // 不可以抛出更大的异常  

注意:
子类重写的方法不能抛出更大的异常、不能抛出平级不一样的异常,但可以抛出一样的异常、更小
的异常以及不抛出异常。
经验分享
若父类中被重写的方法没有抛出异常时,则子类中重写的方法只能进行异常的捕获处理。
若一个方法内部又以递进方式分别调用了好几个其它方法,则建议这些方法内可以使用抛出
的方法处理到最后一层进行捕获方式处理。

1.	import java.io.FileInputStream;  
2.	import java.io.FileNotFoundException;  
3.	import java.io.IOException;  
4.	  
5.	public class ExceptionThrowsTest {
    
      
6.	  
7.	    public static void show() throws IOException {
    
      
8.	        FileInputStream fis = new FileInputStream("d:/a.txt");  
9.	        System.out.println("我想看看你抛出异常后是否继续向下执行???");  
10.	        fis.close();  
11.	    }  
12.	  
13.	    public static void test1() throws IOException {
    
      
14.	        show();  
15.	    }  
16.	  
17.	    public static void test2() throws IOException {
    
      
18.	        test1();  
19.	    }  
20.	  
21.	    public static void test3() throws IOException {
    
      
22.	        test2();  
23.	    }  
24.	  
25.	    // 不建议在main方法中抛出异常   JVM负担很重  
26.	    public static void main(String[] args) /*throws IOException*/ {
    
      
27.	        try {
    
      
28.	            show();  
29.	        } catch (IOException e) {
    
      
30.	            e.printStackTrace();  
31.	        }  
32.	  
33.	        System.out.println("------------------------------------");  
34.	        try {
    
      
35.	            test3();  
36.	        } catch (IOException e) {
    
      
37.	            e.printStackTrace();  
38.	        }  
39.	    }  
40.	}  

1.6.自定义异常
基本概念
当需要在程序中表达年龄不合理的情况时,而Java官方又没有提供这种针对性的异常,此时就需要
程序员自定义异常加以描述。
实现流程
a.自定义xxxException异常类继承Exception类或者其子类。
b.提供两个版本的构造方法,一个是无参构造方法,另外一个是字符串作为参数的构造方法。
异常的产生
throw new 异常类型(实参);
如:
throw new AgeException(“年龄不合理!!!”);
Java采用的异常处理机制是将异常处理的程序代码集中在一起,与正常的程序代码分开,使得程序
简洁、优雅,并易于维护。

public class AgeException extends Exception {
static final long serialVersionUID = 7818375828146090155L; // 序列化的版本号 与序列化操作有关系
public AgeException() {
}
public AgeException(String message) {
super(message);
}
}

1.	public class Person {
    
      
2.	    private String name;  
3.	    private int age;  
4.	  
5.	    public Person() {
    
      
6.	    }  
7.	  
8.	    public Person(String name, int age) /*throws AgeException */{
    
      
9.	        setName(name);  
10.	        setAge(age);  
11.	    }  
12.	  
13.	    public String getName() {
    
      
14.	        return name;  
15.	    }  
16.	  
17.	    public void setName(String name) {
    
      
18.	        this.name = name;  
19.	    }  
20.	  
21.	    public int getAge() {
    
      
22.	        return age;  
23.	    }  
24.	  
25.	    public void setAge(int age) /*throws AgeException */{
    
      
26.	        if (age > 0 && age < 150) {
    
      
27.	            this.age = age;  
28.	        } else {
    
      
29.	            //System.out.println("年龄不合理哦!!!");  
30.	            try {
    
      
31.	                throw new AgeException("年龄不合理哦!!!");  
32.	            } catch (AgeException e) {
    
      
33.	                e.printStackTrace();  
34.	            }  
35.	        }  
36.	    }  
37.	  
38.	    @Override  
39.	    public String toString() {
    
      
40.	        return "Person{" +  
41.	                "name='" + name + '\'' +  
42.	                ", age=" + age +  
43.	                '}';  
44.	    }  
45.	}  
两种情况:1、在Person类中直接捕获。2、在其他类中一直抛,直到test类中捕获。
第一种会生成person对象(zhangfei,0.第二种情况不会生成对象,Null

1.	public class PersonTest {
    
      
2.	  
3.	    public static void main(String[] args) {
    
      
4.	  
5.	        /*Person p1 = null; 
6.	        try { 
7.	            p1 = new Person("zhangfei", -30); 
8.	        } catch (AgeException e) { 
9.	            e.printStackTrace(); 
10.	        } 
11.	        System.out.println("p1 = " + p1); // zhangfei 0*/  
12.	        Person p1 = new Person("zhangfei", -30);  
13.	        System.out.println("p1 = " + p1); // zhangfei 0  null  
14.	    }  
15.	}  

异常的处理原则:
1,处理方式有两种:try 或者 throws。
2,调用到抛出异常的功能时,抛出几个,就处理几个。
一个try对应多个catch。
3,多个catch,父类的catch放到最下面。
4,catch内,需要定义针对性的处理方式。不要简单的定义printStackTrace,输出语句。
也不要不写。
当捕获到的异常,本功能处理不了时,可以继续在catch中抛出。
try
{
throw new AException();
}
catch (AException e)
{
throw e;
}

	如果该异常处理不了,但并不属于该功能出现的异常。
	可以将异常转换后,在抛出和该功能相关的异常。

	或者异常可以处理,当需要将异常产生的和本功能相关的问题提供出去,
	当调用者知道。并处理。也可以将捕获异常处理后,转换新的异常。
	try
	{
		throw new AException();
	}
	catch (AException e)
	{
		// 对AException处理。
		throw new BException();
	}

	比如,汇款的例子。

异常的注意事项:
在子父类覆盖时:
1,子类抛出的异常必须是父类的异常的子类或者子集。
2,如果父类或者接口没有异常抛出时,子类覆盖出现异常,只能try不能抛。

2.File类(重点)
2.1.基本概念
java.io.File类主要用于描述文件或目录路径的抽象表示信息,可以获取文件或目录的特征信息,
如:大小等。

2.2.常用的方法
方法声明 功能概述
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) 获取目录下满足筛选器的所有内容

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

1.	import java.io.File;  
2.	import java.io.FileFilter;  
3.	import java.io.IOException;  
4.	import java.text.SimpleDateFormat;  
5.	import java.time.LocalDateTime;  
6.	import java.util.Date;  
7.	  
8.	public class FileTest {
    
      
9.	  
10.	    // 自定义成员方法实现指定目录以及子目录中所有内容的打印  
11.	    public static void show(File file) {
    
      
12.	        // 获取目录f3下的所有内容并记录到一维数组中  
13.	        File[] filesArray = file.listFiles();  
14.	        // 遍历数组  
15.	        for (File tf: filesArray) {
    
      
16.	            String name = tf.getName();  
17.	            // 判断是否为文件,若是则直接打印文件名称  
18.	            if (tf.isFile()) {
    
      
19.	                System.out.println(name);  
20.	            }  
21.	            // 若是目录,则使用[]将目录名称括起来  
22.	            if (tf.isDirectory()) {
    
      
23.	                System.out.println("[" + name + "]");  
24.	                show(tf);  
25.	            }  
26.	        }  
27.	    }  
28.	  
29.	    public static void main(String[] args) throws IOException {
    
      
30.	  
31.	        // 1.构造File类型的对象与d:/a.txt文件关联  
32.	        File f1 = new File("d:/a.txt");  
33.	        // 2.若文件存在则获取文件的相关特征信息并打印后删除文件  
34.	        if (f1.exists()) {
    
      
35.	            System.out.println("文件的名称是:" + f1.getName());  
36.	            System.out.println("文件的大小是:" + f1.length());  
37.	            Date d1 = new Date(f1.lastModified());  
38.	            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
39.	            System.out.println("文件的最后一次修改时间:" + sdf.format(d1));  
40.	            // 绝对路径: 主要指以根目录开始的路径信息,如:c:/  d:/   /..  
41.	            // 相对路径: 主要指以当前目录所在位置开始的路径信息,如:./  ../   相对路径  
42.	            System.out.println("文件的绝对路径信息是:" + f1.getAbsolutePath());  
43.	            System.out.println(f1.delete()? "文件删除成功": "文件删除失败");  
44.	        } else {
    
      
45.	            // 3.若文件不存在则创建新的空文件  
46.	            System.out.println(f1.createNewFile()? "文件创建成功": "文件创建失败!");  
47.	        }  
48.	  
49.	        System.out.println("---------------------------------------------------------");  
50.	        // 4.实现目录的删除和创建  
51.	        File f2 = new File("d:/捣乱/猜猜我是谁/你猜我猜不猜/死鬼");  
52.	        if (f2.exists()) {
    
      
53.	            System.out.println("目录名称是:" + f2.getName());  
54.	            System.out.println(f2.delete()? "目录删除成功": "目录删除失败");  
55.	        } else {
    
      
56.	            //System.out.println(f2.mkdir()? "目录创建成功": "目录创建失败");   // 创建单层目录  
57.	            System.out.println(f2.mkdirs()? "目录创建成功": "目录创建失败");   // 创建多层目录  
58.	        }  
59.	  
60.	        System.out.println("---------------------------------------------------------");  
61.	        // 5.实现将指定目录中的所有内容打印出来  
62.	        File f3 = new File("d:/捣乱");  
63.	        // 获取目录f3下的所有内容并记录到一维数组中  
64.	        File[] filesArray = f3.listFiles();  
65.	        // 遍历数组  
66.	        for (File tf: filesArray) {
    
      
67.	            String name = tf.getName();  
68.	            // 判断是否为文件,若是则直接打印文件名称  
69.	            if (tf.isFile()) {
    
      
70.	                System.out.println(name);  
71.	            }  
72.	            // 若是目录,则使用[]将目录名称括起来  
73.	            if (tf.isDirectory()) {
    
      
74.	                System.out.println("[" + name + "]");  
75.	            }  
76.	        }  
77.	  
78.	        System.out.println("---------------------------------------------------------");  
79.	        // 6.实现目录中所有内容获取的同时进行过滤  
80.	        // 匿名内部类的语法格式:接口/父类类型 引用变量名 = new 接口/父类类型() { 方法的重写 };  
81.	        /*FileFilter fileFilter = new FileFilter() { 
82.	            @Override 
83.	            public boolean accept(File pathname) { 
84.	                // 若文件名是以.avi为结尾,则返回true表示保留   否则返回false就是表示丢弃 
85.	                return pathname.getName().endsWith(".avi"); 
86.	            } 
87.	        };*/  
88.	        // Lambda表达式的格式:(参数列表) -> {方法体}  
89.	        FileFilter fileFilter = (File pathname) -> {
    
    return pathname.getName().endsWith(".avi");};  
90.	        File[] filesArray2 = f3.listFiles(fileFilter);  
91.	        for (File tf : filesArray2) {
    
      
92.	            System.out.println(tf);  
93.	        }  
94.	  
95.	        System.out.println("---------------------------------------------------------");  
96.	        // 7.使用递归的思想获取目录以及子目录中的内容  
97.	        show(new File("d:/捣乱"));  
98.	    }  
99.	}  

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

2.2.基本分类
按照读写数据的基本单位不同,分为 字节流 和 字符流。
其中字节流主要指以字节为单位进行数据读写的流,可以读写任意类型的文件。
其中字符流主要指以字符(2个字节)为单位进行数据读写的流,只能读写文本文件。
按照读写数据的方向不同,分为 输入流 和 输出流(站在程序的角度)。
其中输入流主要指从文件中读取数据内容输入到程序中,也就是读文件。
其中输出流主要指将程序中的数据内容输出到文件中,也就是写文件。
按照流的角色不同分为节点流和处理流。
其中节点流主要指直接和输入输出源对接的流。
其中处理流主要指需要建立在节点流的基础之上的流。

2.3.体系结构

2.4.相关流的详解
2.4.1.FileWriter类(重点)
(1)基本概念
java.io.FileWriter类主要用于将文本内容写入到文本文件。
(2)常用的方法
方法声明 功能介绍
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() 关闭流对象并释放有关的资源

1.	import java.io.FileWriter;  
2.	import java.io.IOException;  
3.	  
4.	public class FileWriterTest {
    
      
5.	  
6.	    public static void main(String[] args) {
    
      
7.	        // 选中代码后可以使用 ctrl+alt+t 来生成异常的捕获代码等  
8.	        FileWriter fw = null;  
9.	  
10.	        try {
    
      
11.	            // 1.构造FileWrite类型的对象与d:/a.txt文件关联  
12.	            // 若文件不存在,该流会自动创建新的空文件  
13.	            // 若文件存在,该流会清空文件中的原有内容  
14.	            fw = new FileWriter("d:/a.txt");  
15.	            // 以追加的方式创建对象去关联文件  
16.	            // 若文件不存在则自动创建新的空文件,若文件存在则保留原有数据内容  
17.	            //fw = new FileWriter("d:/a.txt", true);  
18.	            // 2.通过流对象写入数据内容  每当写入一个字符后则文件中的读写位置向后移动一位  
19.	            fw.write('a');  
20.	  
21.	            // 准备一个字符数组  
22.	            char[] cArr = new char[]{
    
    'h', 'e', 'l', 'l', 'o'};  
23.	            // 将字符数组中的一部分内容写入进去  
24.	            fw.write(cArr, 1, 3);  // ell  
25.	            // 将整个字符数组写进去  
26.	            fw.write(cArr); // hello  
27.	  
28.	            // 刷新流  
29.	            fw.flush();  
30.	            System.out.println("写入数据成功!");  
31.	        } catch (IOException e) {
    
      
32.	            e.printStackTrace();  
33.	        } finally {
    
      
34.	            // 3.关闭流对象并释放有关的资源  
35.	            if (null != fw) {
    
      
36.	                try {
    
      
37.	                    fw.close();  
38.	                } catch (IOException e) {
    
      
39.	                    e.printStackTrace();  
40.	                }  
41.	            }  
42.	        }  
43.	    }  
44.	}  

2.4.2.FileReader类(重点)
(1)基本概念
java.io.FileReader类主要用于从文本文件读取文本数据内容。
(2)常用的方法
方法声明 功能介绍
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() 关闭流对象并释放有关的资源


```java
1.	import java.io.FileReader;  
2.	import java.io.IOException;  
3.	  
4.	public class FileReaderTest {
    
      
5.	
6.	    public static void main(String[] args) {
    
      
7.	        FileReader fr = null;  
8.	  
9.	        try {
    
      
10.	            // 1.构造FileReader类型的对象与d:/a.txt文件关联  
11.	            //fr = new FileReader("d:/a.txt");  
12.	            fr = new FileReader("d:/b.txt");  
13.	            // 2.读取数据内容并打印  
14.	            /* 
15.	            int res = fr.read(); 
16.	            System.out.println("读取到的单个字符是:" + (char)res); // 'a' 
17.	             */  
18.	            int res = 0;  
19.	            while ((res = fr.read()) != -1) {
    
      
20.	                System.out.println("读取到的单个字符是:" + (char)res + ",对应的编号是:" + res);  
21.	            }  
22.	  
23.	            // 准备一个字符数组来保存读取到的数据内容  
24.	//            char[] cArr = new char[5];  
25.	            // 期望读满字符数组中的一部分空间,也就是读取3个字符放入数组cArr中下标从1开始的位置上  
26.	            /*int res = fr.read(cArr, 1, 3); 
27.	            System.out.println("实际读取到的字符个数是:" + res); // 3 
28.	            for (char cv : cArr) { 
29.	                System.out.println("读取到的单个字符是:" + (char)cv); // 啥也没有 a e l 啥也没有 
30.	            }*/  
31.	  
32.	            // 期望读满整个字符数组  
33.	            /*int res = fr.read(cArr); 
34.	            System.out.println("实际读取到的字符个数是:" + res); // 5 
35.	            for (char cv : cArr) { 
36.	                System.out.println("读取到的单个字符是:" + (char)cv); // a e l l h 
37.	            }*/  
38.	        } catch (IOException e) {
    
      
39.	            e.printStackTrace();  
40.	        } finally {
    
      
41.	            // 3.关闭流对象并释放有关的资源  
42.	            if (null != fr) {
    
      
43.	                try {
    
      
44.	                    fr.close();  
45.	                } catch (IOException e) {
    
      
46.	                    e.printStackTrace();  
47.	                }  
48.	            }  
49.	        }  
50.	    }  
51.	}  

```java
拷贝文件
1.	import java.io.FileReader;  
2.	import java.io.FileWriter;  
3.	import java.io.IOException;  
4.	  
5.	public class FileCharCopyTest {  
6.	  
7.	    public static void main(String[] args) {  
8.	        FileReader fr = null;  
9.	        FileWriter fw = null;  
10.	  
11.	        try {  
12.	            // 1.创建FileReader类型的对象与d:/a.txt文件关联  
13.	            fr = new FileReader("d:/a.txt");  
14.	            //fr = new FileReader("d:/03 IO流的框架图.png");  
15.	            // 2.创建FileWriter类型的对象与d:/b.txt文件关联  
16.	            fw = new FileWriter("d:/b.txt");  
17.	            //fw = new FileWriter("d:/IO流的框架图.png");   拷贝图片文件失败!!!  
18.	            // 3.不断地从输入流中读取数据内容并写入到输出流中  
19.	            System.out.println("正在玩命地拷贝...");  
20.	            int res = 0;  
21.	            while ((res = fr.read()) != -1) {  
22.	                fw.write(res);  
23.	            }  
24.	            System.out.println("拷贝文件成功!");  
25.	        } catch (IOException e) {  
26.	            e.printStackTrace();  
27.	        } finally {  
28.	            // 4.关闭流对象并释放有关的资源  
29.	            if (null != fw) {  
30.	                try {  
31.	                    fw.close();  
32.	                } catch (IOException e) {  
33.	                    e.printStackTrace();  
34.	                }  
35.	            }  
36.	            if (null != fr) {  
37.	                try {  
38.	                    fr.close();  
39.	                } catch (IOException e) {  
40.	                    e.printStackTrace();  
41.	                }  
42.	            }  
43.	        }  
44.	    }  
45.	}  

2.4.3.FileOutputStream类(重点)
(1)基本概念
java.io.FileOutputStream类主要用于将图像数据之类的原始字节流写入到输出流中。
(2)常用的方法
方法声明 功能介绍
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() 关闭流对象并释放有关的资源

2.4.4.FileInputStream类(重点)
(1)基本概念
java.io.FileInputStream类主要用于从输入流中以字节流的方式读取图像数据等。
(2)常用的方法
方法声明 功能介绍
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() 获取输入流所关联文件的大小

案例题目
编程实现两个文件之间的拷贝功能。

1.	import java.io.FileInputStream;  
2.	import java.io.FileOutputStream;  
3.	import java.io.IOException;  
4.	  
5.	public class FileByteCopyTest {
    
      
6.	  
7.	    public static void main(String[] args) {
    
      
8.	  
9.	        // 获取当前系统时间距离1970年1月1日0时0分0秒的毫秒数  
10.	        long g1 = System.currentTimeMillis();  
11.	  
12.	        FileInputStream fis = null;  
13.	        FileOutputStream fos = null;  
14.	  
15.	        try {
    
      
16.	            // 1.创建FileInputStream类型的对象与d:/03 IO流的框架图.png文件关联  
17.	            //fis = new FileInputStream("d:/03 IO流的框架图.png");  
18.	            fis = new FileInputStream("d:/02_IO流的框架结构.mp4");  
19.	            // 2.创建FileOutputStream类型的对象与d:/IO流的框架图.png文件关联  
20.	            //fos = new FileOutputStream("d:/IO流的框架图.png");  
21.	            fos = new FileOutputStream("d:/IO流的框架结构.mp4");  
22.	            // 3.不断地从输入流中读取数据内容并写入到输出流中  
23.	            System.out.println("正在玩命地拷贝...");  
24.	            // 方式一:以单个字节为单位进行拷贝,也就是每次读取一个字节后再写入一个字节  
25.	            // 缺点:文件稍大时,拷贝的效率很低  
26.	            /*int res = 0; 
27.	            while ((res = fis.read()) != -1) { 
28.	                fos.write(res); 
29.	            }*/  
30.	            // 方式二:准备一个和文件大小一样的缓冲区,一次性将文件中的所有内容取出到缓冲区然后一次性写入进去  
31.	            // 缺点:若文件过大时,无法申请和文件大小一样的缓冲区,真实物理内存不足  
32.	            /*int len = fis.available(); 
33.	            System.out.println("获取到的文件大小是:" + len); 
34.	            byte[] bArr = new byte[len]; 
35.	            int res = fis.read(bArr); 
36.	            System.out.println("实际读取到的文件大小是:" + res); 
37.	            fos.write(bArr);*/  
38.	            // 方式三:准备一个相对适当的缓冲区,分多次将文件拷贝完成  
39.	            byte[] bArr = new byte[1024];  
40.	            int res = 0;  
41.	            while ((res = fis.read(bArr)) != -1) {
    
      
42.	                fos.write(bArr, 0, res);  
43.	            }  
44.	  
45.	            System.out.println("拷贝文件成功!");  
46.	        } catch (IOException e) {
    
      
47.	            e.printStackTrace();  
48.	        } finally {
    
      
49.	            // 4.关闭流对象并释放有关的资源  
50.	            if (null != fos) {
    
      
51.	                try {
    
      
52.	                    fos.close();  
53.	                } catch (IOException e) {
    
      
54.	                    e.printStackTrace();  
55.	                }  
56.	            }  
57.	            if (null != fis) {
    
      
58.	                try {
    
      
59.	                    fis.close();  
60.	                } catch (IOException e) {
    
      
61.	                    e.printStackTrace();  
62.	                }  
63.	            }  
64.	        }  
65.	  
66.	        long g2 = System.currentTimeMillis();  
67.	        System.out.println("使用文件流拷贝视频文件消耗的时间为:" + (g2-g1));  // 165  
68.	    }  
69.	}  

2.4.5.BufferedOutputStream类(重点)
(1)基本概念
java.io.BufferedOutputStream类主要用于描述缓冲输出流,此时不用为写入的每个字节调用底层
系统。
(2)常用的方法
方法声明 功能介绍
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() 关闭流对象并释放有关的资源

2.4.6.BufferedInputStream类(重点)
(1)基本概念
java.io.BufferedInputStream类主要用于描述缓冲输入流。
(2)常用的方法
方法声明 功能介绍
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() 关闭流对象并释放有关的资源

1.	import java.io.*;  
2.	  
3.	public class BufferedByteCopyTest {
    
      
4.	  
5.	    public static void main(String[] args) {
    
      
6.	  
7.	        // 获取当前系统时间距离1970年1月1日0时0分0秒的毫秒数  
8.	        long g1 = System.currentTimeMillis();  
9.	  
10.	        BufferedInputStream bis = null;  
11.	        BufferedOutputStream bos = null;  
12.	  
13.	        try {
    
      
14.	            // 1.创建BufferedInputStream类型的对象与d:/02_IO流的框架结构.mp4文件关联  
15.	            bis = new BufferedInputStream(new FileInputStream("d:/02_IO流的框架结构.mp4"));  
16.	            // 2.创建BufferedOuputStream类型的对象与d:/IO流的框架结构.mp4文件关联  
17.	            bos = new BufferedOutputStream(new FileOutputStream("d:/IO流的框架结构.mp4"));  
18.	  
19.	            // 3.不断地从输入流中读取数据并写入到输出流中  
20.	            System.out.println("正在玩命地拷贝...");  
21.	  
22.	            byte[] bArr = new byte[1024];  
23.	            int res = 0;  
24.	            while ((res = bis.read(bArr)) != -1) {
    
      
25.	                bos.write(bArr, 0, res);  
26.	            }  
27.	  
28.	            System.out.println("拷贝文件成功!");  
29.	        } catch (IOException e) {
    
      
30.	            e.printStackTrace();  
31.	        } finally {
    
      
32.	            // 4.关闭流对象并释放有关的资源  
33.	            if (null != bos) {
    
      
34.	                try {
    
      
35.	                    bos.close();  
36.	                } catch (IOException e) {
    
      
37.	                    e.printStackTrace();  
38.	                }  
39.	            }  
40.	            if (null != bis) {
    
      
41.	                try {
    
      
42.	                    bis.close();  
43.	                } catch (IOException e) {
    
      
44.	                    e.printStackTrace();  
45.	                }  
46.	            }  
47.	        }  
48.	  
49.	        long g2 = System.currentTimeMillis();  
50.	        System.out.println("使用缓冲区拷贝视频文件消耗的时间为:" + (g2-g1)); // 44  
51.	    }  
52.	}  

2.4.7.BufferedWriter类(重点)
(1)基本概念
java.io.BufferedWriter类主要用于写入单个字符、字符数组以及字符串到输出流中。
(2)常用的方法
方法声明 功能介绍
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() 关闭流对象并释放有关的资源

2.4.8.BufferedReader类(重点)
(1)基本概念
java.io.BufferedReader类用于从输入流中读取单个字符、字符数组以及字符串。
(2)常用的方法
方法声明 功能介绍
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() 关闭流对象并释放有关的资源

1.	import java.io.*;  
2.	  
3.	public class BufferedCharCopyTest {
    
      
4.	  
5.	    public static void main(String[] args) {
    
      
6.	        BufferedReader br = null;  
7.	        BufferedWriter bw = null;  
8.	  
9.	        try {
    
      
10.	            // 1.创建BufferedReader类型的对象与d:/a.txt文件关联  
11.	            br = new BufferedReader(new FileReader("d:/a.txt"));  
12.	            // 2.创建BufferedWriter类型的对象与d:/b.txt文件关联  
13.	            bw = new BufferedWriter(new FileWriter("d:/b.txt"));  
14.	            // 3.不断地从输入流中读取一行字符串并写入到输出流中  
15.	            System.out.println("正在玩命地拷贝...");  
16.	            String str = null;  
17.	            while ((str = br.readLine()) != null) {
    
      
18.	                bw.write(str);  
19.	                bw.newLine(); // 当前系统中的行分隔符是:\r\n  
20.	            }  
21.	            System.out.println("拷贝文件成功!");  
22.	        } catch (IOException e) {
    
      
23.	            e.printStackTrace();  
24.	        } finally {
    
      
25.	            // 4.关闭流对象并释放有关的资源  
26.	            if (null != bw) {
    
      
27.	                try {
    
      
28.	                    bw.close();  
29.	                } catch (IOException e) {
    
      
30.	                    e.printStackTrace();  
31.	                }  
32.	            }  
33.	            if (null != br) {
    
      
34.	                try {
    
      
35.	                    br.close();  
36.	                } catch (IOException e) {
    
      
37.	                    e.printStackTrace();  
38.	                }  
39.	            }  
40.	        }  
41.	    }  
42.	}  

2.4.9.PrintStream类
(1)基本概念
java.io.PrintStream类主要用于更加方便地打印各种数据内容。
(2)常用的方法
方法声明 功能介绍
PrintStream(OutputStream out) 根据参数指定的引用来构造对象
void print(String s) 用于将参数指定的字符串内容打印出来
void println(String x) 用于打印字符串后并终止该行
void flush() 刷新流
void close() 用于关闭输出流并释放有关的资源

2.4.10.PrintWriter类
(1)基本概念
java.io.PrintWriter类主要用于将对象的格式化形式打印到文本输出流。
(2)常用的方法
方法声明 功能介绍
PrintWriter(Writer out) 根据参数指定的引用来构造对象
void print(String s) 将参数指定的字符串内容打印出来
void println(String x) 打印字符串后并终止该行
void flush() 刷新流
void close() 关闭流对象并释放有关的资源

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

1.	import java.io.*;  
2.	import java.text.SimpleDateFormat;  
3.	import java.util.Date;  
4.	  
5.	public class PrintStreamChatTest {
    
      
6.	  
7.	    public static void main(String[] args) {
    
      
8.	  
9.	        // 由手册可知:构造方法需要的是Reader类型的引用,但Reader类是个抽象类,实参只能传递子类的对象  字符流  
10.	        // 由手册可知: System.in代表键盘输入, 而且是InputStream类型的 字节流  
11.	        BufferedReader br = null;  
12.	        PrintStream ps = null;  
13.	        try {
    
      
14.	            br = new BufferedReader(new InputStreamReader(System.in));  
15.	            ps = new PrintStream(new FileOutputStream("d:/a.txt", true));  
16.	  
17.	            // 声明一个boolean类型的变量作为发送方的代表  
18.	            boolean flag = true;  
19.	  
20.	            while(true) {
    
      
21.	                // 1.提示用户输入要发送的聊天内容并使用变量记录  
22.	                System.out.println("请" + (flag? "张三": "李四") + "输入要发送的聊天内容:");  
23.	                String str = br.readLine();  
24.	                // 2.判断用户输入的内容是否为"bye",若是则聊天结束  
25.	                if ("bye".equals(str)) {
    
      
26.	                    System.out.println("聊天结束!");  
27.	                    break;  
28.	                }  
29.	                // 3.若不是则将用户输入的内容写入到文件d:/a.txt中  
30.	                //else {  
31.	                // 获取当前系统时间并调整格式  
32.	                Date d1 = new Date();  
33.	                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
34.	                ps.println(sdf.format(d1) + (flag?" 张三说:":" 李四说:") + str);  
35.	                //}  
36.	                flag = !flag;  
37.	            }  
38.	            ps.println(); // 写入空行 与之前的聊天记录隔开  
39.	            ps.println();  
40.	            ps.println();  
41.	        } catch (IOException e) {
    
      
42.	            e.printStackTrace();  
43.	        } finally {
    
      
44.	            // 4.关闭流对象并释放有关的资源  
45.	            if (null != ps) {
    
      
46.	                ps.close();  
47.	            }  
48.	            if (null != br) {
    
      
49.	                try {
    
      
50.	                    br.close();  
51.	                } catch (IOException e) {
    
      
52.	                    e.printStackTrace();  
53.	                }  
54.	            }  
55.	        }  
56.	    }  
57.	}  

2.4.11.OutputStreamWriter类
(1)基本概念
java.io.OutputStreamWriter类主要用于实现从字符流到字节流的转换。
(2)常用的方法
方法声明 功能介绍
OutputStreamWriter(OutputStream out) 根据参数指定的引用来构造对象
OutputStreamWriter(OutputStream out, String charsetName) 根据参数指定的引用和编码构造对象
void write(String str) 将参数指定的字符串写入
void flush() 刷新流
void close() 用于关闭输出流并释放有关的资源

2.4.12.InputStreamReader类
(1)基本概念
java.io.InputStreamReader类主要用于实现从字节流到字符流的转换。
(2)常用的方法
方法声明 功能介绍
InputStreamReader(InputStream in) 根据参数指定的引用来构造对象
InputStreamReader(InputStream in, String charsetName) 根据参数指定的引用和编码来构造对象
int read(char[] cbuf) 读取字符数据到参数指定的数组
void close() 用于关闭输出流并释放有关的资源

2.4.13.字符编码
(1)编码表的由来
计算机只能识别二进制数据,早期就是电信号。为了方便计算机可以识别各个国家的文字,就需要
将各个国家的文字采用数字编号的方式进行描述并建立对应的关系表,该表就叫做编码表。
(2)常见的编码表
ASCII:美国标准信息交换码, 使用一个字节的低7位二位进制进行表示。
ISO8859-1:拉丁码表,欧洲码表,使用一个字节的8位二进制进行表示。
GB2312:中国的中文编码表,最多使用两个字节16位二进制为进行表示。
GBK:中国的中文编码表升级,融合了更多的中文文字符号,最多使用两个字节16位二进制位表
示。
Unicode:国际标准码,融合了目前人类使用的所有字符,为每个字符分配唯一的字符码。所有的
文字都用两个字节16位二进制位来表示。
(3)编码的发展
面向传输的众多 UTF(UCS Transfer Format)标准出现了,UTF-8就是每次8个位传输数据,而
UTF-16就是每次16个位。这是为传输而设计的编码并使编码无国界,这样就可以显示全世界上所
有文化的字符了。
Unicode只是定义了一个庞大的、全球通用的字符集,并为每个字符规定了唯一确定的编号,具体
存储成什么样的字节流,取决于字符编码方案。推荐的Unicode编码是UTF-8和UTF-16。
UTF-8:变长的编码方式,可用1-4个字节来表示一个字符。

2.4.14.DataOutputStream类(了解)
(1)基本概念
java.io.DataOutputStream类主要用于以适当的方式将基本数据类型写入输出流中。
(2)常用的方法
方法声明 功能介绍
DataOutputStream(OutputStream out) 根据参数指定的引用构造对象 OutputStream类是个抽象类,实参需要传递子类对象
void writeInt(int v) 用于将参数指定的整数一次性写入输出流,优先写入高字节
void close() 用于关闭文件输出流并释放有关的资源

1.	import java.io.DataOutputStream;  
2.	import java.io.FileOutputStream;  
3.	import java.io.IOException;  
4.	  
5.	public class DataOutputStreamTest {
    
      
6.	  
7.	    public static void main(String[] args) {
    
      
8.	        DataOutputStream dos = null;  
9.	  
10.	        try {
    
      
11.	            // 1.创建DataOutputStream类型的对象与d:/a.txt文件关联  
12.	            dos = new DataOutputStream(new FileOutputStream("d:/a.txt"));  
13.	            // 2.准备一个整数数据66并写入输出流  
14.	            // 66: 0000 0000 ... 0100 0010    =>   B  
15.	            int num = 66;  
16.	            //dos.writeInt(num);  // 写入4个字节  
17.	            dos.write(num);       // 写入1个字节  
18.	            System.out.println("写入数据成功!");  
19.	        } catch (IOException e) {
    
      
20.	            e.printStackTrace();  
21.	        } finally {
    
      
22.	            // 3.关闭流对象并释放有关的资源  
23.	            if (null != dos) {
    
      
24.	                try {
    
      
25.	                    dos.close();  
26.	                } catch (IOException e) {
    
      
27.	                    e.printStackTrace();  
28.	                }  
29.	            }  
30.	        }  
31.	    }  
32.	}  

2.4.15.DataInputStream类(了解)
(1)基本概念
java.io.DataInputStream类主要用于从输入流中读取基本数据类型的数据。
(2)常用的方法
方法声明 功能介绍
DataInputStream(InputStream in) 根据参数指定的引用来构造对象 InputStream类是抽象类,实参需要传递子类对象
int readInt() 用于从输入流中一次性读取一个整数数据并返回
void close() 用于关闭文件输出流并释放有关的资源


```java
1.	import java.io.DataInputStream;  
2.	import java.io.FileInputStream;  
3.	import java.io.IOException;  
4.	  
5.	public class DataInputStreamTest {
    
      
6.	  
7.	    public static void main(String[] args) {
    
      
8.	        DataInputStream dis = null;  
9.	  
10.	        try {
    
      
11.	            // 1.创建DataInputStream类型的对象与d:/a.txt文件关联  
12.	            dis = new DataInputStream(new FileInputStream("d:/a.txt"));  
13.	            // 2.从输入流中读取一个整数并打印  
14.	            //int res = dis.readInt(); // 读取4个字节  
15.	            int res = dis.read();      // 读取1个字节  
16.	            System.out.println("读取到的整数数据是:" + res); // 66  
17.	        } catch (IOException e) {
    
      
18.	            e.printStackTrace();  
19.	        } finally {
    
      
20.	            // 3.关闭流对象并释放有关的资源  
21.	            if (null != dis) {
    
      
22.	                try {
    
      
23.	                    dis.close();  
24.	                } catch (IOException e) {
    
      
25.	                    e.printStackTrace();  
26.	                }  
27.	            }  
28.	        }  
29.	    }  
30.	} 

2.4.16.ObjectOutputStream类(重点)
(1)基本概念
java.io.ObjectOutputStream类主要用于将一个对象的所有内容整体写入到输出流中。
只能将支持 java.io.Serializable 接口的对象写入流中。
类通过实现 java.io.Serializable 接口以启用其序列化功能。
所谓序列化主要指将一个对象需要存储的相关信息有效组织成字节序列的转化过程。
(2)常用的方法
方法声明	功能介绍
ObjectOutputStream(OutputStream out)	根据参数指定的引用来构造对象
void writeObject(Object obj)	用于将参数指定的对象整体写入到输出流中
void close()	用于关闭输出流并释放有关的资源


```java
User类
1.	public class User implements java.io.Serializable {  
2.	    private static final long serialVersionUID = -5814716593800822421L;  
3.	  
4.	    private String userName;  // 用户名  
5.	    private String password;  // 密码  
6.	    private transient String phoneNum;  // 手机号  表示该成员变量不参与序列化操作  
7.	  
8.	    public User() {  
9.	    }  
10.	  
11.	    public User(String userName, String password, String phoneNum) {  
12.	        this.userName = userName;  
13.	        this.password = password;  
14.	        this.phoneNum = phoneNum;  
15.	    }  
16.	  
17.	    public String getUserName() {  
18.	        return userName;  
19.	    }  
20.	  
21.	    public void setUserName(String userName) {  
22.	        this.userName = userName;  
23.	    }  
24.	  
25.	    public String getPassword() {  
26.	        return password;  
27.	    }  
28.	  
29.	    public void setPassword(String password) {  
30.	        this.password = password;  
31.	    }  
32.	  
33.	    public String getPhoneNum() {  
34.	        return phoneNum;  
35.	    }  
36.	  
37.	    public void setPhoneNum(String phoneNum) {  
38.	        this.phoneNum = phoneNum;  
39.	    }  
40.	  
41.	    @Override  
42.	    public String toString() {  
43.	        return "User{" +  
44.	                "userName='" + userName + '\'' +  
45.	                ", password='" + password + '\'' +  
46.	                ", phoneNum='" + phoneNum + '\'' +  
47.	                '}';  
48.	    }  
49.	}  

写入user类数据

1.	import java.io.FileOutputStream;  
2.	import java.io.IOException;  
3.	import java.io.ObjectOutputStream;  
4.	  
5.	public class ObjectOutputStreamTest {
    
      
6.	  
7.	    public static void main(String[] args) {
    
      
8.	        ObjectOutputStream oos = null;  
9.	  
10.	        try {
    
      
11.	            // 1.创建ObjectOutputStream类型的对象与d:/a.txt文件关联  
12.	            oos = new ObjectOutputStream(new FileOutputStream("d:/a.txt"));  
13.	            // 2.准备一个User类型的对象并初始化  
14.	            User user = new User("qidian", "123456", "13511258688");  
15.	            // 3.将整个User类型的对象写入输出流  
16.	            oos.writeObject(user);  
17.	            System.out.println("写入对象成功!");  
18.	        } catch (IOException e) {
    
      
19.	            e.printStackTrace();  
20.	        } finally {
    
      
21.	            // 4.关闭流对象并释放有关的资源  
22.	            if (null != oos) {
    
      
23.	                try {
    
      
24.	                    oos.close();  
25.	                } catch (IOException e) {
    
      
26.	                    e.printStackTrace();  
27.	                }  
28.	            }  
29.	        }  
30.	    }  
31.	}  

2.4.17.ObjectInputStream类(重点)
(1)基本概念
java.io.ObjectInputStream类主要用于从输入流中一次性将对象整体读取出来。
所谓反序列化主要指将有效组织的字节序列恢复为一个对象及相关信息的转化过程。
(2)常用的方法
方法声明 功能介绍
ObjectInputStream(InputStream in) 根据参数指定的引用来构造对象
Object readObject() 主要用于从输入流中读取一个对象并返回 无法通过返回值来判断是否读取到文件的末尾
void close() 用于关闭输入流并释放有关的资源

(3)序列化版本号
序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,
JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如
果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常
(InvalidCastException)。
(4)transient关键字
transient是Java语言的关键字,用来表示一个域不是该对象串行化的一部分。当一个对象被串行
化的时候,transient型变量的值不包括在串行化的表示中,然而非transient型的变量是被包括进
去的。
(5)经验的分享
当希望将多个对象写入文件时,通常建议将多个对象放入一个集合中,然后将集合这个整体看做一
个对象写入输出流中,此时只需要调用一次readObject方法就可以将整个集合的数据读取出来,
从而避免了通过返回值进行是否达到文件末尾的判断。

读取user类数据

1.	import java.io.FileInputStream;  
2.	import java.io.IOException;  
3.	import java.io.ObjectInputStream;  
4.	  
5.	public class ObjectInputStreamTest {
    
      
6.	  
7.	    public static void main(String[] args) {
    
      
8.	        ObjectInputStream ois = null;  
9.	  
10.	        try {
    
      
11.	            // 1.创建ObjectInputStream类型的对象与d:/a.txt文件关联  
12.	            ois = new ObjectInputStream(new FileInputStream("d:/a.txt"));  
13.	            // 2.从输入流中读取一个对象并打印  
14.	            Object obj = ois.readObject();  
15.	            System.out.println("读取到的对象是:" + obj); // qidian 123456 13511258688  null  
16.	        } catch (IOException e) {
    
      
17.	            e.printStackTrace();  
18.	        } catch (ClassNotFoundException e) {
    
      
19.	            e.printStackTrace();  
20.	        } finally {
    
      
21.	            // 3.关闭流对象并释放有关的资源  
22.	            if (null != ois) {
    
      
23.	                try {
    
      
24.	                    ois.close();  
25.	                } catch (IOException e) {
    
      
26.	                    e.printStackTrace();  
27.	                }  
28.	            }  
29.	        }  
30.	    }  
31.	}  

2.4.18.RandomAccessFile类
(1)基本概念
java.io.RandomAccessFile类主要支持对随机访问文件的读写操作。
(2)常用的方法
方法声明 功能介绍
RandomAccessFile(String name, String mode) 根据参数指定的名称和模式构造对象 r: 以只读方式打开 rw:打开以便读取和写入 rwd:打开以便读取和写入,同步文件内容的更新 rws:打开以便读取和写入,同步文件内容和元数据的更新
int read() 读取单个字节的数据
void seek(long pos) 用于设置从此文件的开头开始测量的文件指针偏移量
void write(int b) 将参数指定的单个字节写入
void close() 用于关闭流并释放有关的资源

1.	import java.io.IOException;  
2.	import java.io.RandomAccessFile;  
3.	  
4.	public class RandomAccessFileTest {
    
      
5.	  
6.	    public static void main(String[] args) {
    
      
7.	        RandomAccessFile raf = null;  
8.	  
9.	        try {
    
      
10.	            // 1.创建RandomAccessFile类型的对象与d:/a.txt文件关联  
11.	            raf = new RandomAccessFile("d:/a.txt", "rw");  
12.	            // 2.对文件内容进行随机读写操作  
13.	            // 设置距离文件开头位置的偏移量,从文件开头位置向后偏移3个字节    aellhello  
14.	            raf.seek(3);  
15.	            int res = raf.read();  
16.	            System.out.println("读取到的单个字符是:" + (char)res); // a l  
17.	            res = raf.read();  
18.	            System.out.println("读取到的单个字符是:" + (char)res); // h 指向了e  
19.	            raf.write('2'); // 执行该行代码后覆盖了字符'e'  
20.	            System.out.println("写入数据成功!");  
21.	  
22.	        } catch (IOException e) {
    
      
23.	            e.printStackTrace();  
24.	        } finally {
    
      
25.	            // 3.关闭流对象并释放有关的资源  
26.	            if (null != raf) {
    
      
27.	                try {
    
      
28.	                    raf.close();  
29.	                } catch (IOException e) {
    
      
30.	                    e.printStackTrace();  
31.	                }  
32.	            }  
33.	        }  
34.	    }  
35.	} 

三、多线程
3.1.基本概念
几乎所有操作系统都支持进程的概念,所有运行中的任务通常对应一条进程(Process )。当一个程序进入内存运行,即变成一个进程。进程是处于运行过程中的程序,并且具有一定独立功能,进程是系统进行资源分配和调度的一个独立单位。
进程包含如下三个特征:
 独立性:进程是系统中独立存在的实体,它可以拥有自己独立的资源,每一个进程都拥有自己私有的地址空间。在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间。
 动态性:进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合。在进程中加入了时间的概念。进程具有自己的生命周期和各种不同的状态,这些概念在程序中都是不具备的。
 并发性:多个进程可以在单个处理器上并发执行,多个进程之间不会互相影响。

区别:
并发性(concurrency)和并行性(parallel )是两个概念,并行指在同一时刻,有多条指令在多个处理器上同时执行;并发指在同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行,使得在宏观上具有多个进程同时执行的效果。

多线程则扩展了多进程的概念,使得同一个进程可以同时并发处理多个任务。线程(Thread )也被称作轻量级进程(Lightweight Process ),线程是进程的执行单元。就像进程在操作系统中的地位一样,线程在程序中是独立的、并发的执行流。当进程被初始化后,主线程就被创建了。对于绝大多数的应用程序来说,通常仅要求有一个主线程,但我们也可以在该进程内创建多条顺序执行流,这些顺序执行流就是线程,每条线程也是互相独立的。
线程是进程的组成部分,一个进程可以拥有多个线程,一个线程必须有一个父进程。线程可以拥有自己的堆栈、自己的程序计数器和自己的局部变量,但不再拥有系统资源,它与父进程的其他线程共享该进程所拥有的全部资源。因为多个线程共享父进程里的全部资源,因此编程更加方便;但必须更加小心,我们必须确保线程不会妨碍同一进程里的其他线程。
一个程序运行后至少有一个进程,一个进程里可以包含多个线程,但至少要包含一个线程。
线程共享的环境包括:进程代码段、进程的公有数据等。利用这些共享的数据等,线程很容易实现相互之间的通信。
多线程编程包含如下几个优点:
 进程间不能共享内存,但线程之间共享内存非常容易。
 系统创建进程需要为该进程重新分配系统资源,但创建线程则代价小得多,因此使用多线程来实现多任务并发比多进程的效率高。
 Java语言内置多线程功能支持,而不是单纯地作为底层操作系统的调度方式,从而简化了Java的多线程编。

线程的创建和启动
Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每条线程的作用是完成一定的任务,实际上就是执行一段程序流(一段顺序执行的代码)。Java使用run方法来封装这样一段程序流。

3.1.1.程序和进程的概念
程序 - 数据结构 + 算法,主要指存放在硬盘上的可执行文件。
进程 - 主要指运行在内存中的可执行文件。
目前主流的操作系统都支持多进程,为了让操作系统同时可以执行多个任务,但进程是重量级的,
也就是新建一个进程会消耗CPU和内存空间等系统资源,因此进程的数量比较局限。

3.1.2.线程的概念
为了解决上述问题就提出线程的概念,线程就是进程内部的程序流,也就是说操作系统内部支持多进程的,而每个进程的内部又是支持多线程的,线程是轻量的,新建线程会共享所在进程的系统资源,因此目前主流的开发都是采用多线程。
多线程是采用时间片轮转法来保证多个线程的并发执行,所谓并发就是指宏观并行微观串行的机制。

3.2.线程的创建(重中之重)
3.2.1.Thread类的概念
java.lang.Thread类代表线程,任何线程对象都是Thread类(子类)的实例。
Thread类是线程的模板,封装了复杂的线程开启等操作,封装了操作系统的差异性。

1.	public class ThreadTest {
    
      
2.	  
3.	    public static void main(String[] args) {
    
      
4.	  
5.	        // 1.使用无参方式构造Thread类型的对象  
6.	        // 由源码可知:Thread类中的成员变量target的数值为null。  
7.	        Thread t1 = new Thread();  
8.	        // 2.调用run方法进行测试  
9.	        // 由源码可知:由于成员变量target的数值为null,因此条件if (target != null)不成立,跳过{}中的代码不执行  
10.	        //  而run方法中除了上述代码再无代码,因此证明run方法确实啥也不干  
11.	        t1.run();  
12.	        // 3.打印一句话  
13.	        System.out.println("我想看看你到底是否真的啥也不干!");  
14.	    }  
15.	}  

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

继承thread类实现多线程

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


1.	public class SubThreadRunTest {
    
      
2.	  
3.	    public static void main(String[] args) {
    
      
4.	  
5.	        // 1.声明Thread类型的引用指向子类类型的对象  
6.	        Thread t1 = new SubThreadRun();  
7.	        // 2.调用run方法测试,本质上就是相当于对普通成员方法的调用,因此执行流程就是run方法的代码执行完毕后才能继续向下执行  
8.	        //t1.run();  
9.	        // 用于启动线程,Java虚拟机会自动调用该线程类中的run方法  
10.	        // 相当于又启动了一个线程,加上执行main方法的线程是两个线程  
11.	        t1.start();  
12.	  
13.	        // 打印1 ~ 20之间的所有整数  
14.	        for (int i = 1; i <= 20; i++) {
    
      
15.	            System.out.println("-----------------main方法中:i = " + i); // 1 2 ... 20  
16.	        }  
17.	    }  
18.	} 

实现Runnable接口实现多线程

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

1.	public class SubRunnableRunTest {
    
      
2.	  
3.	    public static void main(String[] args) {
    
      
4.	  
5.	        // 1.创建自定义类型的对象,也就是实现Runnable接口类的对象  
6.	        SubRunnableRun srr = new SubRunnableRun();  
7.	        // 2.使用该对象作为实参构造Thread类型的对象  
8.	        // 由源码可知:经过构造方法的调用之后,Thread类中的成员变量target的数值为srr。  
9.	        Thread t1 = new Thread(srr);  
10.	        // 3.使用Thread类型的对象调用start方法  
11.	        // 若使用Runnable引用构造了线程对象,调用该方法(run)时最终调用接口中的版本  
12.	        // 由run方法的源码可知:if (target != null) {  
13.	        //                         target.run();  
14.	        //                    }  
15.	        // 此时target的数值不为空这个条件成立,执行target.run()的代码,也就是srr.run()的代码  
16.	        t1.start();  
17.	        //srr.start();  Error  
18.	  
19.	        // 打印1 ~ 20之间的所有整数  
20.	        for (int i = 1; i <= 20; i++) {
    
      
21.	            System.out.println("-----------------main方法中:i = " + i); // 1 2 ... 20  
22.	        }  
23.	    }  
24.	}  

3.2.3.相关的方法
方法声明 功能介绍
Thread() 使用无参的方式构造对象
Thread(String name) 根据参数指定的名称来构造对象
Thread(Runnable target) 根据参数指定的引用来构造对象,其中Runnable是个接口类型
Thread(Runnable target, String name) 根据参数指定引用和名称来构造对象
void run() 若使用Runnable引用构造了线程对象,调用该方法时最终调用接口中的版本
若没有使用Runnable引用构造线程对象,调用该方法时则啥也不做
void start() 用于启动线程,Java虚拟机会自动调用该线程的run方法

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

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

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

1.	public class ThreadNoNameTest {
    
      
2.	  
3.	    public static void main(String[] args) {
    
      
4.	  
5.	        // 匿名内部类的语法格式:父类/接口类型 引用变量名 = new 父类/接口类型() { 方法的重写 };  
6.	        // 1.使用继承加匿名内部类的方式创建并启动线程  
7.	        /*Thread t1 = new Thread() { 
8.	            @Override 
9.	            public void run() { 
10.	                System.out.println("张三说:在吗?"); 
11.	            } 
12.	        }; 
13.	        t1.start();*/  
14.	        new Thread() {
    
      
15.	            @Override  
16.	            public void run() {
    
      
17.	                System.out.println("张三说:在吗?");  
18.	            }  
19.	        }.start();  
20.	  
21.	        // 2.使用实现接口加匿名内部类的方式创建并启动线程  
22.	        /*Runnable ra = new Runnable() { 
23.	            @Override 
24.	            public void run() { 
25.	                System.out.println("李四说:不在。"); 
26.	            } 
27.	        }; 
28.	        Thread t2 = new Thread(ra); 
29.	        t2.start();*/  
30.	        /*new Thread(new Runnable() { 
31.	            @Override 
32.	            public void run() { 
33.	                System.out.println("李四说:不在。"); 
34.	            } 
35.	        }).start();*/  
36.	        // Java8开始支持lambda表达式: (形参列表)->{方法体;}  
37.	        /*Runnable ra = ()-> System.out.println("李四说:不在。"); 
38.	        new Thread(ra).start();*/  
39.	  
40.	        new Thread(()-> System.out.println("李四说:不在。")).start();  
41.	    }  
42.	}  

3.3.线程的生命周期(熟悉)

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

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

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

继承thread类实现名称打印

1.	public class ThreadIdNameTest extends Thread {
    
      
2.	  
3.	    public ThreadIdNameTest(String name) {
    
      
4.	        super(name); // 表示调用父类的构造方法  
5.	    }  
6.	  
7.	    @Override  
8.	    public void run() {
    
      
9.	        System.out.println("子线程的编号是:" + getId() + ",名称是:" + getName()); // 14  Thread-0 guanyu  
10.	        // 修改名称为"zhangfei"  
11.	        setName("zhangfei");  
12.	        System.out.println("修改后子线程的编号是:" + getId() + ",名称是:" + getName()); // 14  zhangfei  
13.	    }  
14.	  
15.	    public static void main(String[] args) {
    
      
16.	  
17.	        ThreadIdNameTest tint = new ThreadIdNameTest("guanyu");  
18.	        tint.start();  
19.	  
20.	        // 获取当前正在执行线程的引用,当前正在执行的线程是主线程,也就是获取主线程的引用  
21.	        Thread t1 = Thread.currentThread();  
22.	        System.out.println("主线程的编号是:" + t1.getId() + ", 名称是:" + t1.getName());  
23.	    }  
24.	}  

实现Runnable接口实现ID和name打印

1.	public class RunnableIdNameTest implements Runnable {
    
      
2.	    @Override  
3.	    public void run() {
    
      
4.	        // 获取当前正在执行线程的引用,也就是子线程的引用  
5.	        Thread t1 = Thread.currentThread();  
6.	        System.out.println("子线程的编号是:" + t1.getId() + ", 名称是:" + t1.getName()); // 14 guanyu  
7.	        t1.setName("zhangfei");  
8.	        System.out.println("修改后子线程的编号是:" + t1.getId() + ", 名称是:" + t1.getName()); // 14 zhangfei  
9.	    }  
10.	  
11.	    public static void main(String[] args) {
    
      
12.	  
13.	        RunnableIdNameTest rint = new RunnableIdNameTest();  
14.	        //Thread t2 = new Thread(rint);  
15.	        Thread t2 = new Thread(rint, "guanyu");  
16.	        t2.start();  
17.	  
18.	        // 获取当前正在执行线程的引用,当前正在执行的线程是主线程,也就是获取主线程的引用  
19.	        Thread t1 = Thread.currentThread();  
20.	        System.out.println("主线程的编号是:" + t1.getId() + ", 名称是:" + t1.getName());  
21.	    }  
22.	} 

3.5.常用的方法(重点)
方法声明 功能介绍
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) 用于设置线程为守护线程

Sleep方法使用

1.	import java.text.SimpleDateFormat;  
2.	import java.time.LocalDateTime;  
3.	import java.util.Date;  
4.	  
5.	public class ThreadSleepTest extends Thread {
    
      
6.	    // 声明一个布尔类型的变量作为循环是否执行的条件  
7.	    private boolean flag = true;  
8.	  
9.	    // 子类中重写的方法不能抛出更大的异常  
10.	    @Override  
11.	    public void run() {
    
      
12.	        // 每隔一秒获取一次系统时间并打印,模拟时钟的效果  
13.	        while (flag) {
    
      
14.	            // 获取当前系统时间并调整格式打印  
15.	//            LocalDateTime.now();  
16.	            Date d1 = new Date();  
17.	            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
18.	            System.out.println(sdf.format(d1));  
19.	  
20.	            // 睡眠1秒钟  
21.	            try {
    
      
22.	                Thread.sleep(1000);  
23.	            } catch (InterruptedException e) {
    
      
24.	                e.printStackTrace();  
25.	            }  
26.	        }  
27.	    }  
28.	  
29.	    public static void main(String[] args) {
    
      
30.	  
31.	        ThreadSleepTest tst = new ThreadSleepTest();  
32.	        tst.start();  
33.	  
34.	        // 主线程等待5秒后结束子线程  
35.	        System.out.println("主线程开始等待...");  
36.	        try {
    
      
37.	            Thread.sleep(5000);  
38.	        } catch (InterruptedException e) {
    
      
39.	            e.printStackTrace();  
40.	        }  
41.	        // 停止子线程  过时  不建议使用  
42.	        //tst.stop();  
43.	        tst.flag = false;  
44.	        System.out.println("主线程等待结束!");  
45.	    }  
46.	}  

Priority相关方法使用方法

1.	public class ThreadPriorityTest extends Thread {
    
      
2.	    @Override  
3.	    public void run() {
    
      
4.	        //System.out.println("子线程的优先级是:" + getPriority()); // 5  10  优先级越高的线程不一定先执行。  
5.	        for (int i = 0; i < 20; i++) {
    
      
6.	            System.out.println("子线程中:i = " + i);  
7.	        }  
8.	    }  
9.	  
10.	    public static void main(String[] args) {
    
      
11.	  
12.	        ThreadPriorityTest tpt = new ThreadPriorityTest();  
13.	        // 设置子线程的优先级  
14.	        tpt.setPriority(Thread.MAX_PRIORITY);  
15.	        tpt.start();  
16.	  
17.	        Thread t1 = Thread.currentThread();  
18.	        //System.out.println("主线程的优先级是:" + t1.getPriority()); // 5 普通的优先级  
19.	        for (int i = 0; i < 20; i++) {
    
      
20.	            System.out.println("--主线程中:i = " + i);  
21.	        }  
22.	    }  
23.	  
24.	}  

isDaemon方法的使用

1.	public class ThreadDaemonTest extends Thread {
    
      
2.	    @Override  
3.	    public void run() {
    
      
4.	        //System.out.println(isDaemon()? "该线程是守护线程": "该线程不是守护线程"); // 默认不是守护线程  
5.	        // 当子线程不是守护线程时,虽然主线程先结束了,但是子线程依然会继续执行,直到打印完毕所有数据为止  
6.	        // 当子线程是守护线程时,当主线程结束后,则子线程随之结束  
7.	        for (int i = 0; i < 50; i++) {
    
      
8.	            System.out.println("子线程中:i = " + i);  
9.	        }  
10.	    }  
11.	  
12.	    public static void main(String[] args) {
    
      
13.	  
14.	        ThreadDaemonTest tdt = new ThreadDaemonTest();  
15.	        // 必须在线程启动之前设置子线程为守护线程  
16.	        tdt.setDaemon(true);  
17.	        tdt.start();  
18.	  
19.	        for (int i = 0; i < 20; i++) {
    
      
20.	            System.out.println("-------主线程中:i = " + i);  
21.	        }  
22.	    }  
23.	}  

Join方法的使用

1.	public class ThreadJoinTest extends Thread {
    
      
2.	    @Override  
3.	    public void run() {
    
      
4.	        // 模拟倒数10个数的效果  
5.	        System.out.println("倒计时开始...");  
6.	        for (int i = 10; i > 0; i--) {
    
      
7.	            System.out.println(i);  
8.	            try {
    
      
9.	                Thread.sleep(1000);  
10.	            } catch (InterruptedException e) {
    
      
11.	                e.printStackTrace();  
12.	            }  
13.	        }  
14.	        System.out.println("新年快乐!");  
15.	    }  
16.	  
17.	    public static void main(String[] args) {
    
      
18.	  
19.	        ThreadJoinTest tjt = new ThreadJoinTest();  
20.	        tjt.start();  
21.	  
22.	        // 主线程开始等待  
23.	        System.out.println("主线程开始等待...");  
24.	        try {
    
      
25.	            // 表示当前正在执行的线程对象等待调用线程对象,也就是主线程等待子线程终止  
26.	            //tjt.join();  
27.	            tjt.join(5000); // 最多等待5秒  
28.	        } catch (InterruptedException e) {
    
      
29.	            e.printStackTrace();  
30.	        }  
31.	        //System.out.println("终于等到你,还好没放弃!");  
32.	        System.out.println("可惜不是你,陪我到最后!");  
33.	    }  
34.	}  

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

实现Runnable接口实现多线程

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

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

1.	public class SubRunnableTest {
    
      
2.	  
3.	    public static void main(String[] args) {
    
      
4.	  
5.	        SubRunnable1 sr1 = new SubRunnable1();  
6.	        SubRunnable2 sr2 = new SubRunnable2();  
7.	  
8.	        Thread t1 = new Thread(sr1);  
9.	        Thread t2 = new Thread(sr2);  
10.	  
11.	        t1.start();  
12.	        t2.start();  
13.	  
14.	        System.out.println("主线程开始等待...");  
15.	        try {
    
      
16.	            t1.join();  
17.	            t2.join();  
18.	        } catch (InterruptedException e) {
    
      
19.	            e.printStackTrace();  
20.	        }  
21.	        System.out.println("主线程等待结束!");  
22.	    }  
23.	}  

继承Thread类实现多线程

1.	public class SubThread1 extends Thread {
    
      
2.	    @Override  
3.	    public void run() {
    
      
4.	        // 打印1 ~ 100之间的所有奇数  
5.	        for (int i = 1; i <= 100; i += 2) {
    
      
6.	            System.out.println("子线程一中: i = " + i);  
7.	        }  
8.	    }  
9.	}  
1.	public class SubThread2 extends Thread {
    
      
2.	    @Override  
3.	    public void run() {
    
      
4.	        // 打印1 ~ 100之间的所有偶数  
5.	        for (int i = 2; i <= 100; i += 2) {
    
      
6.	            System.out.println("------子线程二中: i = " + i);  
7.	        }  
8.	    }  
9.	}  
1.	public class SubThreadTest {
    
      
2.	  
3.	    public static void main(String[] args) {
    
      
4.	  
5.	        SubThread1 st1 = new SubThread1();  
6.	        SubThread2 st2 = new SubThread2();  
7.	  
8.	        st1.start();  
9.	        st2.start();  
10.	  
11.	        System.out.println("主线程开始等待...");  
12.	        try {
    
      
13.	            st1.join();  
14.	            st2.join();  
15.	        } catch (InterruptedException e) {
    
      
16.	            e.printStackTrace();  
17.	        }  
18.	        System.out.println("主线程等待结束!");  
19.	    }  
20.	}

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

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

3.6.3.实现方式
在Java语言中使用synchronized关键字来实现同步/对象锁机制从而保证线程执行的原子性,具体
方式如下:
使用同步代码块的方式实现部分代码的锁定,格式如下:
synchronized(类类型的引用) {
编写所有需要锁定的代码;
}
使用同步方法的方式实现所有代码的锁定。
直接使用synchronized关键字来修饰整个方法即可
该方式等价于:
synchronized(this) { 整个方法体的代码 }

同步的前提:
1,必须要有两个或者两个以上的线程。
2,必须是多个线程使用同一个锁。

必须保证同步中只能有一个线程在运行。
好处:解决了多线程的安全问题。

弊端:多个线程需要判断锁,较为消耗资源,

同步函数用的是哪一个锁呢?
函数需要被对象调用。那么函数都有一个所属对象引用。就是this。
所以同步函数使用的锁是this。
如果同步函数被静态修饰后,使用的锁是什么呢?

通过验证,发现不在是this。因为静态方法中也不可以定义this。

静态进内存是,内存中没有本类对象,但是一定有该类对应的字节码文件对象。
类名.class 该对象的类型是Class

静态的同步方法,使用的锁是该方法所在类的字节码文件对象。 类名.class

如何找问题:
1,明确哪些代码是多线程运行代码。
2,明确共享数据。
3,明确多线程运行代码中哪些语句是操作共享数据的。

实现Runnable接口的方式实现线程同步

1.	import java.util.concurrent.locks.ReentrantLock;  
2.	  
3.	public class AccountRunnableTest implements Runnable {
    
      
4.	    private int balance; // 用于描述账户的余额  
5.	    private Demo dm = new Demo();  
6.	    private ReentrantLock lock = new ReentrantLock();  // 准备了一把锁  
7.	  
8.	    public AccountRunnableTest() {
    
      
9.	    }  
10.	  
11.	    public AccountRunnableTest(int balance) {
    
      
12.	        this.balance = balance;  
13.	    }  
14.	  
15.	    public int getBalance() {
    
      
16.	        return balance;  
17.	    }  
18.	  
19.	    public void setBalance(int balance) {
    
      
20.	        this.balance = balance;  
21.	    }  
22.	  
23.	    @Override  
24.	    public /*synchronized*/ void run() {
    
      
25.	        // 开始加锁  
26.	        lock.lock();  
27.	  
28.	        // 由源码可知:最终是account对象来调用run方法,因此当前正在调用的对象就是account,也就是说this就是account  
29.	        //synchronized (this) { // ok  
30.	        System.out.println("线程" + Thread.currentThread().getName() + "已启动...");  
31.	        //synchronized (dm) { // ok  
32.	        //synchronized (new Demo()) { // 锁不住  要求必须是同一个对象  
33.	            // 1.模拟从后台查询账户余额的过程  
34.	            int temp = getBalance(); // temp = 1000  temp = 1000  
35.	            // 2.模拟取款200元的过程  
36.	            if (temp >= 200) {
    
      
37.	                System.out.println("正在出钞,请稍后...");  
38.	                temp -= 200;  // temp = 800   temp = 800  
39.	                try {
    
      
40.	                    Thread.sleep(5000);  
41.	                } catch (InterruptedException e) {
    
      
42.	                    e.printStackTrace();  
43.	                }  
44.	                System.out.println("请取走您的钞票!");  
45.	            } else {
    
      
46.	                System.out.println("余额不足,请核对您的账户余额!");  
47.	            }  
48.	            // 3.模拟将最新的账户余额写入到后台  
49.	            setBalance(temp); // balance = 800  balance = 800  
50.	        //}  
51.	        lock.unlock(); // 实现解锁  
52.	    }  
53.	  
54.	    public static void main(String[] args) {
    
      
55.	  
56.	        AccountRunnableTest account = new AccountRunnableTest(1000);  
57.	        //AccountRunnableTest account2 = new AccountRunnableTest(1000);  
58.	        Thread t1 = new Thread(account);  
59.	        Thread t2 = new Thread(account);  
60.	        //Thread t2 = new Thread(account2);  
61.	        t1.start();  
62.	        t2.start();  
63.	  
64.	        System.out.println("主线程开始等待...");  
65.	        try {
    
      
66.	            t1.join();  
67.	            //t2.start(); // 也就是等待线程一取款操作结束后再启动线程二  
68.	            t2.join();  
69.	        } catch (InterruptedException e) {
    
      
70.	            e.printStackTrace();  
71.	        }  
72.	        System.out.println("最终的账户余额为:" + account.getBalance()); // 600  800  
73.	    }  
74.	}  
75.	  
76.	class Demo{
    
    }  

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

继承Thread类的方式实现线程同步

1.	public class AccountThreadTest extends Thread {
    
      
2.	    private int balance; // 用于描述账户的余额  
3.	    private static Demo dm = new Demo(); // 隶属于类层级,所有对象共享同一个  
4.	  
5.	    public AccountThreadTest() {
    
      
6.	    }  
7.	  
8.	    public AccountThreadTest(int balance) {
    
      
9.	        this.balance = balance;  
10.	    }  
11.	  
12.	    public int getBalance() {
    
      
13.	        return balance;  
14.	    }  
15.	  
16.	    public void setBalance(int balance) {
    
      
17.	        this.balance = balance;  
18.	    }  
19.	  
20.	    @Override  
21.	    public /*static*/ /*synchronized*/ void run() {
    
      
22.	        /*System.out.println("线程" + Thread.currentThread().getName() + "已启动..."); 
23.	        //synchronized (dm) { // ok 
24.	            //synchronized (new Demo()) { // 锁不住  要求必须是同一个对象 
25.	            // 1.模拟从后台查询账户余额的过程 
26.	            int temp = getBalance(); // temp = 1000  temp = 1000 
27.	            // 2.模拟取款200元的过程 
28.	            if (temp >= 200) { 
29.	                System.out.println("正在出钞,请稍后..."); 
30.	                temp -= 200;  // temp = 800   temp = 800 
31.	                try { 
32.	                    Thread.sleep(5000); 
33.	                } catch (InterruptedException e) { 
34.	                    e.printStackTrace(); 
35.	                } 
36.	                System.out.println("请取走您的钞票!"); 
37.	            } else { 
38.	                System.out.println("余额不足,请核对您的账户余额!"); 
39.	            } 
40.	            // 3.模拟将最新的账户余额写入到后台 
41.	            setBalance(temp); // balance = 800  balance = 800 
42.	        //}*/  
43.	        test();  
44.	    }  
45.	  
46.	    public /*synchronized*/ static void test() {
    
      
47.	        synchronized (AccountThreadTest.class) {
    
     // 该类型对应的Class对象,由于类型是固定的,因此Class对象也是唯一的,因此可以实现同步  
48.	            System.out.println("线程" + Thread.currentThread().getName() + "已启动...");  
49.	            //synchronized (dm) { // ok  
50.	            //synchronized (new Demo()) { // 锁不住  要求必须是同一个对象  
51.	            // 1.模拟从后台查询账户余额的过程  
52.	            int temp = 1000; //getBalance(); // temp = 1000  temp = 1000  
53.	            // 2.模拟取款200元的过程  
54.	            if (temp >= 200) {
    
      
55.	                System.out.println("正在出钞,请稍后...");  
56.	                temp -= 200;  // temp = 800   temp = 800  
57.	                try {
    
      
58.	                    Thread.sleep(5000);  
59.	                } catch (InterruptedException e) {
    
      
60.	                    e.printStackTrace();  
61.	                }  
62.	                System.out.println("请取走您的钞票!");  
63.	            } else {
    
      
64.	                System.out.println("余额不足,请核对您的账户余额!");  
65.	            }  
66.	            // 3.模拟将最新的账户余额写入到后台  
67.	            //setBalance(temp); // balance = 800  balance = 800  
68.	        }  
69.	    }  
70.	  
71.	    public static void main(String[] args) {
    
      
72.	  
73.	        AccountThreadTest att1 = new AccountThreadTest(1000);  
74.	        att1.start();  
75.	  
76.	        AccountThreadTest att2 = new AccountThreadTest(1000);  
77.	        att2.start();  
78.	  
79.	        System.out.println("主线程开始等待...");  
80.	        try {
    
      
81.	            att1.join();  
82.	            //t2.start(); // 也就是等待线程一取款操作结束后再启动线程二  
83.	            att2.join();  
84.	        } catch (InterruptedException e) {
    
      
85.	            e.printStackTrace();  
86.	        }  
87.	        System.out.println("最终的账户余额为:" + att1.getBalance()); // 800  
88.	  
89.	    }  
90.	  
91.	    }  

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

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

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

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

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

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

(2)常用的方法
方法声明 功能介绍
ReentrantLock() 使用无参方式构造对象
void lock() 获取锁
void unlock() 释放锁

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

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

Object的wait()和notify()方法应用

1.	public class ThreadCommunicateTest implements Runnable {
    
      
2.	    private int cnt = 1;  
3.	  
4.	    @Override  
5.	    public void run() {
    
      
6.	        while (true) {
    
      
7.	            synchronized (this) {
    
      
8.	                // 每当有一个线程进来后先大喊一声,调用notify方法  
9.	                notify();  
10.	                if (cnt <= 100) {
    
      
11.	                    System.out.println("线程" + Thread.currentThread().getName() + "中:cnt = " + cnt);  
12.	                    try {
    
      
13.	                        Thread.sleep(100);  
14.	                    } catch (InterruptedException e) {
    
      
15.	                        e.printStackTrace();  
16.	                    }  
17.	                    cnt++;  
18.	                    // 当前线程打印完毕一个整数后,为了防止继续打印下一个数据,则调用wait方法  
19.	                    try {
    
      
20.	                        wait(); // 当前线程进入阻塞状态,自动释放对象锁,必须在锁定的代码中调用  
21.	                    } catch (InterruptedException e) {
    
      
22.	                        e.printStackTrace();  
23.	                    }  
24.	                } else {
    
      
25.	                    break;  
26.	                }  
27.	            }  
28.	        }  
29.	    }  
30.	  
31.	    public static void main(String[] args) {
    
      
32.	  
33.	        ThreadCommunicateTest tct = new ThreadCommunicateTest();  
34.	        Thread t1 = new Thread(tct);  
35.	        t1.start();  
36.	  
37.	        Thread t2 = new Thread(tct);  
38.	        t2.start();  
39.	    }  
40.	}  

生产者消费者模式

1.	/** 
2.	 * 编程实现仓库类 
3.	 */  
4.	public class StoreHouse {
    
      
5.	    private int cnt = 0; // 用于记录产品的数量  
6.	  
7.	    public synchronized void produceProduct() {
    
      
8.	        notify();  
9.	        if (cnt < 10) {
    
      
10.	            System.out.println("线程" + Thread.currentThread().getName() + "正在生产第" + (cnt+1) + "个产品...");  
11.	            cnt++;  
12.	        } else {
    
      
13.	            try {
    
      
14.	                wait();  
15.	            } catch (InterruptedException e) {
    
      
16.	                e.printStackTrace();  
17.	            }  
18.	        }  
19.	    }  
20.	  
21.	    public synchronized void consumerProduct() {
    
      
22.	        notify();  
23.	        if (cnt > 0) {
    
      
24.	            System.out.println("线程" + Thread.currentThread().getName() + "消费第" + cnt + "个产品");  
25.	            cnt--;  
26.	        } else {
    
      
27.	            try {
    
      
28.	                wait();  
29.	            } catch (InterruptedException e) {
    
      
30.	                e.printStackTrace();  
31.	            }  
32.	        }  
33.	    }  
34.	}  



1.	/** 
2.	 * 编程实现生产者线程,不断地生产产品 
3.	 */  
4.	public class ProduceThread extends Thread {
    
      
5.	    // 声明一个仓库类型的引用作为成员变量,是为了能调用调用仓库类中的生产方法   合成复用原则  
6.	    private StoreHouse storeHouse;  
7.	    // 为了确保两个线程共用同一个仓库  
8.	    public ProduceThread(StoreHouse storeHouse) {
    
      
9.	        this.storeHouse = storeHouse;  
10.	    }  
11.	  
12.	    @Override  
13.	    public void run() {
    
      
14.	        while (true) {
    
      
15.	            storeHouse.produceProduct();  
16.	            try {
    
      
17.	                Thread.sleep(1000);  
18.	            } catch (InterruptedException e) {
    
      
19.	                e.printStackTrace();  
20.	            }  
21.	        }  
22.	    }  
23.	}  

消费者模式

1.	public class ConsumerThread extends Thread {
    
      
2.	    // 声明一个仓库类型的引用作为成员变量,是为了能调用调用仓库类中的生产方法   合成复用原则  
3.	    private StoreHouse storeHouse;  
4.	    // 为了确保两个线程共用同一个仓库  
5.	    public ConsumerThread(StoreHouse storeHouse) {
    
      
6.	        this.storeHouse = storeHouse;  
7.	    }  
8.	  
9.	    @Override  
10.	    public void run() {
    
      
11.	        while (true) {
    
      
12.	            storeHouse.consumerProduct();  
13.	            try {
    
      
14.	                Thread.sleep(100);  
15.	            } catch (InterruptedException e) {
    
      
16.	                e.printStackTrace();  
17.	            }  
18.	        }  
19.	    }  
20.	}  

1.	public class StoreHouseTest {
    
      
2.	  
3.	    public static void main(String[] args) {
    
      
4.	  
5.	        // 创建仓库类的对象  
6.	        StoreHouse storeHouse = new StoreHouse();  
7.	        // 创建线程类对象并启动  
8.	        ProduceThread t1 = new ProduceThread(storeHouse);  
9.	        ConsumerThread t2 = new ConsumerThread(storeHouse);  
10.	        t1.start();  
11.	        t2.start();  
12.	    }  
13.	}  

3.6.10.线程池(熟悉)
(1)实现Callable接口
从Java5开始新增加创建线程的第三种方式为实现java.util.concurrent.Callable接口。
常用的方法如下:
方法声明 功能介绍
V call() 计算结果并返回

(2)FutureTask类
 java.util.concurrent.FutureTask类用于描述可取消的异步计算,该类提供了Future接口的基本实现,包括启动和取消计算、查询计算是否完成以及检索计算结果的方法,也可以用于获取方法调用后的返回结果。
 常用的方法如下:
方法声明 功能介绍
FutureTask(Callable callable) 根据参数指定的引用来创建一个未来任务
V get() 获取call方法计算的结果

1.	import java.util.concurrent.Callable;  
2.	import java.util.concurrent.ExecutionException;  
3.	import java.util.concurrent.FutureTask;  
4.	  
5.	public class ThreadCallableTest implements Callable {
    
      
6.	  
7.	    @Override  
8.	    public Object call() throws Exception {
    
      
9.	        // 计算1 ~ 10000之间的累加和并打印返回  
10.	        int sum = 0;  
11.	        for (int i = 1; i <= 10000; i++) {
    
      
12.	            sum +=i;  
13.	        }  
14.	        System.out.println("计算的累加和是:" + sum); // 50005000  
15.	        return sum;  
16.	    }  
17.	  
18.	    public static void main(String[] args) {
    
      
19.	  
20.	        ThreadCallableTest tct = new ThreadCallableTest();  
21.	        FutureTask ft = new FutureTask(tct);  
22.	        Thread t1 = new Thread(ft);  
23.	        t1.start();  
24.	        Object obj = null;  
25.	        try {
    
      
26.	            obj = ft.get();  
27.	        } catch (InterruptedException e) {
    
      
28.	            e.printStackTrace();  
29.	        } catch (ExecutionException e) {
    
      
30.	            e.printStackTrace();  
31.	        }  
32.	        System.out.println("线程处理方法的返回值是:" + obj); // 50005000  
33.	    }  
34.	}  

(3)线程池的由来
 在服务器编程模型的原理,每一个客户端连接用一个单独的线程为之服务,当与客户端的会话结束时,线程也就结束了,即每来一个客户端连接,服务器端就要创建一个新线程。
 如果访问服务器的客户端很多,那么服务器要不断地创建和销毁线程,这将严重影响服务器的性能。
(4)概念和原理
 线程池的概念:首先创建一些线程,它们的集合称为线程池,当服务器接受到一个客户请求后,就从线程池中取出一个空闲的线程为之服务,服务完后不关闭该线程,而是将该线程还回到线程池中。
 在线程池的编程模式下,任务是提交给整个线程池,而不是直接交给某个线程,线程池在拿到任务后,它就在内部找有无空闲的线程,再把任务交给内部某个空闲的线程,任务是提交给整个线程池,一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务。
(5)相关类和方法
 从Java5开始提供了线程池的相关类和接口:java.util.concurrent.Executors类和java.util.concurrent.ExecutorService接口。
 其中Executors是个工具类和线程池的工厂类,可以创建并返回不同类型的线程池,常用方法下:
方法声明 功能介绍
static ExecutorService newCachedThreadPool() 创建一个可根据需要创建新线程的线程池
static ExecutorService newFixedThreadPool(int nThreads) 创建一个可重用固定线程数的线程池
static ExecutorService newSingleThreadExecutor() 创建一个只有一个线程的线程池

 其中ExecutorService接口是真正的线程池接口,主要实现类是ThreadPoolExecutor,常用方法如下:
方法声明 功能介绍
void execute(Runnable command) 执行任务和命令,通常用于执行Runnable
Future submit(Callable task) 执行任务和命令,通常用于执行Callable
void shutdown() 启动有序关闭

1.	import java.util.concurrent.ExecutorService;  
2.	import java.util.concurrent.Executors;  
3.	  
4.	public class ThreadPoolTest {
    
      
5.	  
6.	    public static void main(String[] args) {
    
      
7.	  
8.	        // 1.创建一个线程池  
9.	        ExecutorService executorService = Executors.newFixedThreadPool(10);  
10.	        // 2.向线程池中布置任务  
11.	        executorService.submit(new ThreadCallableTest());  
12.	        // 3.关闭线程池  
13.	        executorService.shutdown();  
14.	    }  
15.	}  

四、网络编程
4.1.网络编程的常识
目前主流的网络通讯软件有:微信、QQ、飞信、阿里旺旺、陌陌、探探、…

4.1.1.七层网络模型
 OSI(Open System Interconnect),即开放式系统互联,是ISO(国际标准化组织)组织在1985年研究的网络互连模型。
 OSI七层模型和TCP/IP五层模型的划分如下:

 当发送数据时,需要对发送的内容按照上述七层模型进行层层加包后发送出去。
 当接收数据时,需要对接收的内容按照上述七层模型相反的次序层层拆包并显示出来。

4.1.2.相关的协议(笔试题)
(1)协议的概念
计算机在网络中实现通信就必须有一些约定或者规则,这种约定和规则就叫做通信协议,通信协议可以对速率、传输代码、代码结构、传输控制步骤、出错控制等制定统一的标准。
(2)TCP协议
传输控制协议(Transmission Control Protocol),是一种面向连接的协议,类似于打电话。
 建立连接 => 进行通信 => 断开连接
 在传输前采用"三次握手"方式。
 在通信的整个过程中全程保持连接,形成数据传输通道。
 保证了数据传输的可靠性和有序性。
 是一种全双工的字节流通信方式,可以进行大数据量的传输。
 传输完毕后需要释放已建立的连接,发送数据的效率比较低。
(3)UDP协议
用户数据报协议(User Datagram Protocol),是一种非面向连接的协议,类似于写信。
 在通信的整个过程中不需要保持连接,其实是不需要建立连接。
 不保证数据传输的可靠性和有序性。
 是一种全双工的数据报通信方式,每个数据报的大小限制在64K内。
 发送数据完毕后无需释放资源,开销小,发送数据的效率比较高,速度快。

4.1.3.IP地址(重点)
 192.168.1.1 - 是绝大多数路由器的登录地址,主要配置用户名和密码以及Mac过滤。
 IP地址是互联网中的唯一地址标识,本质上是由32位二进制组成的整数,叫做IPv4,当然也有128位二进制组成的整数,叫做IPv6,目前主流的还是IPv4。
 日常生活中采用点分十进制表示法来进行IP地址的描述,将每个字节的二进制转化为一个十进制整数,不同的整数之间采用小数点隔开。
 如:
0x01020304 => 1.2.3.4
 查看IP地址的方式:
Windows系统:在dos窗口中使用ipconfig或ipconfig/all命令即可
Unix/linux系统:在终端窗口中使用ifconfig或/sbin/ifconfig命令即可
 特殊的地址
本地回环地址(hostAddress):127.0.0.1 主机名(hostName):localhost

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

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

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

4.2.3.相关类和方法的解析
(1)ServerSocket类
 java.net.ServerSocket类主要用于描述服务器套接字信息(大插排)。
 常用的方法如下:
方法声明 功能介绍
ServerSocket(int port) 根据参数指定的端口号来构造对象
Socket accept() 侦听并接收到此套接字的连接请求
void close() 用于关闭套接字

(2)Socket类
 java.net.Socket类主要用于描述客户端套接字,是两台机器间通信的端点(小插排)。
 常用的方法如下:
方法声明 功能介绍
Socket(String host, int port) 根据指定主机名和端口来构造对象
InputStream getInputStream() 用于获取当前套接字的输入流
OutputStream getOutputStream() 用于获取当前套接字的输出流
void close() 用于关闭套接字

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

创建TCP通信过程

  1. 创建线程用于结束新连接客户端服务
2.	import java.io.BufferedReader;  
3.	import java.io.IOException;  
4.	import java.io.InputStreamReader;  
5.	import java.io.PrintStream;  
6.	import java.net.InetAddress;  
7.	import java.net.Socket;  
8.	  
9.	public class ServerThread extends Thread {
    
      
10.	    private Socket s;  
11.	  
12.	    public ServerThread(Socket s) {
    
      
13.	        this.s = s;  
14.	    }  
15.	  
16.	    @Override  
17.	    public void run() {
    
      
18.	        BufferedReader br = null;  
19.	        PrintStream ps = null;  
20.	  
21.	        try {
    
      
22.	            // 3.使用输入输出流进行通信  
23.	            br = new BufferedReader(new InputStreamReader(s.getInputStream()));  
24.	            ps = new PrintStream(s.getOutputStream());  
25.	  
26.	            while(true) {
    
      
27.	                // 实现对客户端发来字符串内容的接收并打印  
28.	                // 当没有数据发来时,下面的方法会形成阻塞  
29.	                String s1 = br.readLine();  
30.	                InetAddress inetAddress = s.getInetAddress();  
31.	                System.out.println("客户端" + inetAddress + "发来的字符串内容是:" + s1);  
32.	                // 当客户端发来的内容为"bye"时,则聊天结束  
33.	                if ("bye".equalsIgnoreCase(s1)) {
    
      
34.	                    System.out.println("客户端" + inetAddress + "已下线!");  
35.	                    break;  
36.	                }  
37.	                // 实现服务器向客户端回发字符串内容"I received!"  
38.	                ps.println("I received!");  
39.	                System.out.println("服务器发送数据成功!");  
40.	            }  
41.	        } catch (IOException e) {
    
      
42.	            e.printStackTrace();  
43.	        } finally {
    
      
44.	            if (null != ps) {
    
      
45.	                ps.close();  
46.	            }  
47.	            if (null != br) {
    
      
48.	                try {
    
      
49.	                    br.close();  
50.	                } catch (IOException e) {
    
      
51.	                    e.printStackTrace();  
52.	                }  
53.	            }  
54.	            if (null != s) {
    
      
55.	                try {
    
      
56.	                    s.close();  
57.	                } catch (IOException e) {
    
      
58.	                    e.printStackTrace();  
59.	                }  
60.	            }  
61.	        }  
62.	  
63.	    }  
64.	}  

2.创建服务端

1.	import java.io.BufferedReader;  
2.	import java.io.IOException;  
3.	import java.io.InputStreamReader;  
4.	import java.io.PrintStream;  
5.	import java.net.ServerSocket;  
6.	import java.net.Socket;  
7.	  
8.	public class ServerStringTest {
    
      
9.	  
10.	    public static void main(String[] args) {
    
      
11.	        ServerSocket ss = null;  
12.	        Socket s = null;  
13.	  
14.	        try {
    
      
15.	            // 1.创建ServerSocket类型的对象并提供端口号  
16.	            ss = new ServerSocket(8888);  
17.	  
18.	            // 2.等待客户端的连接请求,调用accept方法  
19.	            while(true) {
    
      
20.	                System.out.println("等待客户端的连接请求...");  
21.	                // 当没有客户端连接时,则服务器阻塞在accept方法的调用这里  
22.	                s = ss.accept();  
23.	                System.out.println("客户端" + s.getInetAddress() + "连接成功!");  
24.	                // 每当有一个客户端连接成功,则需要启动一个新的线程为之服务  
25.	                new ServerThread(s).start();  
26.	            }  
27.	        } catch (IOException e) {
    
      
28.	            e.printStackTrace();  
29.	        } finally {
    
      
30.	            // 4.关闭Socket并释放有关的资源  
31.	            if (null != ss) {
    
      
32.	                try {
    
      
33.	                    ss.close();  
34.	                } catch (IOException e) {
    
      
35.	                    e.printStackTrace();  
36.	                }  
37.	            }  
38.	        }  
39.	    }  
40.	}  

3.创建客户端

1.	import com.lagou.task10.StaticOuter;  
2.	  
3.	import java.io.BufferedReader;  
4.	import java.io.IOException;  
5.	import java.io.InputStreamReader;  
6.	import java.io.PrintStream;  
7.	import java.net.Socket;  
8.	import java.util.Scanner;  
9.	  
10.	public class ClientStringTest {
    
      
11.	  
12.	    public static void main(String[] args) {
    
      
13.	        Socket s = null;  
14.	        PrintStream ps = null;  
15.	        Scanner sc = null;  
16.	        BufferedReader br = null;  
17.	  
18.	        try {
    
      
19.	            // 1.创建Socket类型的对象并提供服务器的主机名和端口号  
20.	            s = new Socket("127.0.0.1", 8888);  
21.	            System.out.println("连接服务器成功!");  
22.	  
23.	            // 2.使用输入输出流进行通信  
24.	            sc = new Scanner(System.in);  
25.	            ps = new PrintStream(s.getOutputStream());  
26.	            br = new BufferedReader(new InputStreamReader(s.getInputStream()));  
27.	  
28.	            while(true) {
    
      
29.	                //Thread.sleep(10000);  
30.	                // 实现客户端发送的内容由用户从键盘输入  
31.	                System.out.println("请输入要发送的数据内容:");  
32.	                String str1 = sc.next();  
33.	                // 实现客户端向服务器发送字符串内容"hello"  
34.	                //ps.println("hello");  
35.	                ps.println(str1);  
36.	                System.out.println("客户端发送数据内容成功!");  
37.	                // 当发送的数据内容为"bye"时,则聊天结束  
38.	                if ("bye".equalsIgnoreCase(str1)) {
    
      
39.	                        System.out.println("聊天结束!");  
40.	                        break;  
41.	                }  
42.	                // 实现接收服务器发来的字符串内容并打印  
43.	                String str2 = br.readLine();  
44.	                System.out.println("服务器回发的消息是:" + str2);  
45.	            }  
46.	  
47.	        } catch (IOException /*| InterruptedException*/ e) {
    
      
48.	            e.printStackTrace();  
49.	        } finally {
    
      
50.	            // 3.关闭Socket并释放有关的资源  
51.	            if (null != br) {
    
      
52.	                try {
    
      
53.	                    br.close();  
54.	                } catch (IOException e) {
    
      
55.	                    e.printStackTrace();  
56.	                }  
57.	            }  
58.	            if (null != ps) {
    
      
59.	                ps.close();  
60.	            }  
61.	            if (null != sc) {
    
      
62.	                sc.close();  
63.	            }  
64.	            if (null != s) {
    
      
65.	                try {
    
      
66.	                    s.close();  
67.	                } catch (IOException e) {
    
      
68.	                    e.printStackTrace();  
69.	                }  
70.	            }  
71.	        }  
72.	    }  
73.	}  

4.3.基于udp协议的编程模型(熟悉)
4.3.1.编程模型
接收方:
(1)创建DatagramSocket类型的对象并提供端口号;
(2)创建DatagramPacket类型的对象并提供缓冲区;
(3)通过Socket接收数据内容存放到Packet中,调用receive方法;
(4)关闭Socket;
发送方:
(1)创建DatagramSocket类型的对象;
(2)创建DatagramPacket类型的对象并提供接收方的通信地址;
(3)通过Socket将Packet中的数据内容发送出去,调用send方法;
(4)关闭Socket;

4.3.2.相关类和方法的解析
(1)DatagramSocket类
 java.net.DatagramSocket类主要用于描述发送和接收数据报的套接字(邮局)。换句话说,该类就是包裹投递服务的发送或接收点。
 常用的方法如下:
方法声明 功能介绍
DatagramSocket() 使用无参的方式构造对象
DatagramSocket(int port) 根据参数指定的端口号来构造对象
void receive(DatagramPacket p) 用于接收数据报存放到参数指定的位置
void send(DatagramPacket p) 用于将参数指定的数据报发送出去
void close() 关闭Socket并释放相关资源

(2)DatagramPacket类
 java.net.DatagramPacket类主要用于描述数据报,数据报用来实现无连接包裹投递服务。
 常用的方法如下:
方法声明 功能介绍
DatagramPacket(byte[] buf, int length) 根据参数指定的数组来构造对象,用于接收长度为length的数据报
DatagramPacket(byte[] buf, int length, InetAddress address, int port) 根据参数指定数组来构造对象,将数据报发送到指定地址和端口
InetAddress getAddress() 用于获取发送方或接收方的通信地址
int getPort() 用于获取发送方或接收方的端口号
int getLength() 用于获取发送数据或接收数据的长度

(3)InetAddress类
 java.net.InetAddress类主要用于描述互联网通信地址信息。
 常用的方法如下:
方法声明 功能介绍
static InetAddress getLocalHost() 用于获取当前主机的通信地址
static InetAddress getByName(String host) 根据参数指定的主机名获取通信地址

1.send测试

1.	import java.io.IOException;  
2.	import java.net.DatagramPacket;  
3.	import java.net.DatagramSocket;  
4.	import java.net.InetAddress;  
5.	  
6.	public class SendTest {
    
      
7.	  
8.	    public static void main(String[] args) {
    
      
9.	        DatagramSocket ds = null;  
10.	  
11.	        try {
    
      
12.	            // 1.创建DatagramSocket类型的对象  
13.	            ds = new DatagramSocket();  
14.	            // 2.创建DatagramPacket类型的对象并提供接收方的通信地址和端口号  
15.	            byte[] bArr = "hello".getBytes();  
16.	            DatagramPacket dp = new DatagramPacket(bArr, bArr.length, InetAddress.getLocalHost(), 8888);  
17.	            // 3.通过Socket发送Packet,调用send方法  
18.	            ds.send(dp);  
19.	            System.out.println("发送数据成功!");  
20.	  
21.	            // 接收回发的数据内容  
22.	            byte[] bArr2 = new byte[20];  
23.	            DatagramPacket dp2 = new DatagramPacket(bArr2, bArr2.length);  
24.	            ds.receive(dp2);  
25.	            System.out.println("接收到的回发消息是:" + new String(bArr2, 0, dp2.getLength()));  
26.	  
27.	        } catch (IOException e) {
    
      
28.	            e.printStackTrace();  
29.	        } finally {
    
      
30.	            // 4.关闭Socket并释放有关的资源  
31.	            if (null != ds) {
    
      
32.	                ds.close();  
33.	            }  
34.	        }  
35.	    }  
36.	}  

2.Receive

1.	import java.io.IOException;  
2.	import java.net.DatagramPacket;  
3.	import java.net.DatagramSocket;  
4.	  
5.	public class ReceiveTest {
    
      
6.	  
7.	    public static void main(String[] args) {
    
      
8.	        DatagramSocket ds = null;  
9.	  
10.	        try {
    
      
11.	            // 1.创建DatagramSocket类型的对象并提供端口号  
12.	            ds = new DatagramSocket(8888);  
13.	  
14.	            // 2.创建DatagramPacket类型的对象并提供缓冲区  
15.	            byte[] bArr = new byte[20];  
16.	            DatagramPacket dp = new DatagramPacket(bArr, bArr.length);  
17.	            // 3.通过Socket接收数据内容存放到Packet里面,调用receive方法  
18.	            System.out.println("等待数据的到来...");  
19.	            ds.receive(dp);  
20.	            System.out.println("接收到的数据内容是:" + new String(bArr, 0, dp.getLength()) + "!");  
21.	  
22.	            // 实现将字符串内容"I received!"回发过去  
23.	            byte[] bArr2 = "I received!".getBytes();  
24.	            DatagramPacket dp2 = new DatagramPacket(bArr2, bArr2.length, dp.getAddress(), dp.getPort());  
25.	            ds.send(dp2);  
26.	            System.out.println("回发数据成功!");  
27.	  
28.	        } catch (IOException e) {
    
      
29.	            e.printStackTrace();  
30.	        } finally {
    
      
31.	            // 4.关闭Socket并释放有关的资源  
32.	            if (null != ds) {
    
      
33.	                ds.close();  
34.	            }  
35.	        }  
36.	    }  
37.	}  

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

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

4.4.3.URLConnection类
(1)基本概念
 java.net.URLConnection类是个抽象类,该类表示应用程序和URL之间的通信链接的所有类的超类,主要实现类有支持HTTP特有功能的HttpURLConnection类。
(2)HttpURLConnection类的常用方法
方法声明 功能介绍
InputStream getInputStream() 获取输入流
void disconnect() 断开连接

1.	import java.io.BufferedReader;  
2.	import java.io.IOException;  
3.	import java.io.InputStream;  
4.	import java.io.InputStreamReader;  
5.	import java.net.HttpURLConnection;  
6.	import java.net.MalformedURLException;  
7.	import java.net.URL;  
8.	import java.net.URLConnection;  
9.	  
10.	public class URLTest {
    
      
11.	  
12.	    public static void main(String[] args) {
    
      
13.	  
14.	        try {
    
      
15.	            // 1.使用参数指定的字符串来构造对象  
16.	            URL url = new URL("https://www.lagou.com/");  
17.	            // 2.获取相关信息并打印出来  
18.	            System.out.println("获取到的协议名称是:" + url.getProtocol());  
19.	            System.out.println("获取到的主机名称是:" + url.getHost());  
20.	            System.out.println("获取到的端口号是:" + url.getPort());  
21.	  
22.	            // 3.建立连接并读取相关信息打印出来  
23.	            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();  
24.	            InputStream inputStream = urlConnection.getInputStream();  
25.	            BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));  
26.	            String str = null;  
27.	            while ((str = br.readLine()) != null) {
    
      
28.	                System.out.println(str);  
29.	            }  
30.	            br.close();  
31.	            // 断开连接  
32.	            urlConnection.disconnect();  
33.	  
34.	        } catch (MalformedURLException e) {
    
      
35.	            e.printStackTrace();  
36.	        } catch (IOException e) {
    
      
37.	            e.printStackTrace();  
38.	        }  
39.	  
40.	    }  
41.	}  

五、反射机制
5.1.基本概念
通常情况下编写代码都是固定的,无论运行多少次执行的结果也是固定的,在某些特殊场合中编写代码时不确定要创建什么类型的对象,也不确定要调用什么样的方法,这些都希望通过运行时传递的参数来决定,该机制叫做动态编程技术,也就是反射机制。
通俗来说,反射机制就是用于动态创建对象并且动态调用方法的机制。
目前主流的框架底层都是采用反射机制实现的。
如:
Person p = new Person(); - 表示声明Person类型的引用指向Person类型的对象 p.show(); - 表示调用Person类中的成员方法show
5.2.Class类

5.2.1.基本概念
java.lang.Class类的实例可以用于描述Java应用程序中的类和接口,也就是一种数据类型。
该类没有公共构造方法,该类的实例由Java虚拟机和类加载器自动构造完成,本质上就是加载到内存中的运行时类。
5.2.2.获取Class对象的方式
使用数据类型.class的方式可以获取对应类型的Class对象(掌握)。
使用引用/对象.getClass()的方式可以获取对应类型的Class对象。使用包装类.TYPE的方式可以获取对应基本数据类型的Class对象。
使用Class.forName()的方式来获取参数指定类型的Class对象(掌握)。
使用类加载器ClassLoader的方式获取指定类型的Class对象。

1.	//获取class对象的方式
2.	public class ClassTest {
    
      
3.	  
4.	    public static void main(String[] args) throws ClassNotFoundException {
    
      
5.	  
6.	        // 1.使用数据类型.class的方式可以获取对应类型的Class对象  
7.	        Class c1 = String.class;  
8.	        System.out.println("c1 = " + c1); // 自动调用toString方法  class java.lang.String  
9.	        c1 = int.class;  
10.	        System.out.println("c1 = " + c1); // int  
11.	        c1 = void.class;  
12.	        System.out.println("c1 = " + c1); // void  
13.	  
14.	        System.out.println("---------------------------------------------------");  
15.	        // 2.使用对象.getClass()的方式获取对应的Class对象  
16.	        String str1 = new String("hello");  
17.	        c1 = str1.getClass();  
18.	        System.out.println("c1 = " + c1); // class java.lang.String  
19.	  
20.	        Integer it1 = 20;  
21.	        c1 = it1.getClass();  
22.	        System.out.println("c1 = " + c1); // class java.lang.Integer  
23.	  
24.	        int num = 5;  
25.	        //num.getClass(); Error: 基本数据类型的变量不能调用方法  
26.	  
27.	        System.out.println("---------------------------------------------------");  
28.	        // 3.使用包装类.TYPE的方式来获取对应基本数据类型的Class对象  
29.	        c1 = Integer.TYPE;  
30.	        System.out.println("c1 = " + c1); // int  
31.	  
32.	        c1 = Integer.class;  
33.	        System.out.println("c1 = " + c1); // class java.lang.Integer  
34.	  
35.	        System.out.println("---------------------------------------------------");  
36.	        // 4.调用Class类中的forName方法来获取对应的Class对象  
37.	        //c1 = Class.forName("String"); // Error  要求写完整的名称:包名.类名  
38.	        c1 = Class.forName("java.lang.String");  
39.	        System.out.println("c1 = " + c1); // class java.lang.String  
40.	  
41.	        c1 = Class.forName("java.util.Date");  
42.	        System.out.println("c1 = " + c1); // class java.util.Date  
43.	  
44.	        //c1 = Class.forName("int");  
45.	        //System.out.println("c1 = " + c1); // 不能获取基本数据类型的Class对象  
46.	  
47.	        System.out.println("---------------------------------------------------");  
48.	        // 5.使用类加载器的方式来获取Class对象  
49.	        ClassLoader classLoader = ClassTest.class.getClassLoader();  
50.	        System.out.println("classLoader = " + classLoader); // null  
51.	        c1 = classLoader.loadClass("java.lang.String");  
52.	        System.out.println("c1 = " + c1); // class java.lang.String  
53.	    }  
54.	}  

5.2.3.常用的方法(掌握)
方法声明 功能介绍
static Class<?> forName(String className) 用于获取参数指定类型对应的Class对象并返回
T newInstance() 用于创建该Class对象所表示类的新实例
Person类

1.	import java.io.IOException;  
2.	  
3.	public class Person {
    
      
4.	    private String name;  
5.	    //public String name;  
6.	    private int age;  
7.	  
8.	    public Person() {
    
      
9.	    }  
10.	  
11.	    public Person(String name, int age) {
    
      
12.	        this.name = name;  
13.	        this.age = age;  
14.	    }  
15.	  
16.	    public String getName() {
    
      
17.	        return name;  
18.	    }  
19.	  
20.	    public void setName(String name) {
    
      
21.	        this.name = name;  
22.	    }  
23.	  
24.	    public int getAge() {
    
      
25.	        return age;  
26.	    }  
27.	  
28.	    public void setAge(int age) throws IOException {
    
      
29.	        this.age = age;  
30.	    }  
31.	  
32.	    @Override  
33.	    public String toString() {
    
      
34.	        return "Person{" +  
35.	                "name='" + name + '\'' +  
36.	                ", age=" + age +  
37.	                '}';  
38.	    }  
39.	}  

5.3.Constructor类

5.3.1.基本概念
java.lang.reflect.Constructor类主要用于描述获取到的构造方法信息
5.3.2.Class类的常用方法
方法声明 功能介绍
Constructor getConstructor(Class<?>…
parameterTypes) 用于获取此Class对象所表示类型中参数指定的公共构造方法
Constructor<?>[] getConstructors() 用于获取此Class对象所表示类型中所有的公共构造方法
5.3.3.Constructor类的常用方法
方法声明 功能介绍
T newInstance(Object…
initargs) 使用此Constructor对象描述的构造方法来构造Class对象代表类型的新实例
int getModifiers() 获取方法的访问修饰符
String getName() 获取方法的名称
Class<?>[] getParameterTypes() 获取方法所有参数的类型

1.	import java.io.BufferedReader;  
2.	import java.io.FileInputStream;  
3.	import java.io.InputStreamReader;  
4.	import java.lang.reflect.Constructor;  
5.	import java.util.Scanner;  
6.	  
7.	public class PersonConstructorTest {
    
      
8.	  
9.	    public static void main(String[] args) throws Exception {
    
      
10.	  
11.	        // 1.使用原始方式以无参形式构造Person类型的对象并打印  
12.	        Person p1 = new Person();  
13.	        System.out.println("无参方式创建的对象是:" + p1); // null 0  
14.	  
15.	        System.out.println("---------------------------------------------------");  
16.	        // 2.使用反射机制以无参形式构造Person类型的对象并打印  
17.	        // 创建对象的类型可以从键盘输入  
18.	        //System.out.println("请输入要创建对象的类型:");  
19.	        //Scanner sc = new Scanner(System.in);  
20.	        //String str1 = sc.next();  
21.	        //Class c1 = Class.forName("com.lagou.task20.Person");  
22.	        // 创建对象的类型可以从配置文件中读取  
23.	        BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("d:/a.txt")));  
24.	        String str1 = br.readLine();  
25.	        Class c1 = Class.forName(str1);  
26.	        //System.out.println("无参方式创建的对象是:" + c1.newInstance()); // null 0  
27.	        // 获取Class对象对应类中的无参构造方法,也就是Person类中的无参构造方法  
28.	        Constructor constructor = c1.getConstructor();  
29.	        // 使用获取到的无参构造方法来构造对应类型的对象,也就是Person类型的对象  
30.	        System.out.println("无参方式创建的对象是:" + constructor.newInstance());  
31.	        //sc.close();  
32.	        br.close();  
33.	  
34.	        System.out.println("---------------------------------------------------");  
35.	        // 3.使用原始方式以有参方式构造Person类型的对象并打印  
36.	        Person p2 = new Person("zhangfei", 30);  
37.	        System.out.println("有参方式构造的对象是:" + p2); // zhangfei 30  
38.	  
39.	        System.out.println("---------------------------------------------------");  
40.	        // 4.使用反射机制以有参方式构造Person类型的对象并打印  
41.	        // 获取Class对象对应类中的有参构造方法,也就是Person类中的有参构造方法  
42.	        Constructor constructor1 = c1.getConstructor(String.class, int.class);  
43.	        // 使用获取到的有参构造方法来构造对应类型的对象,也就是Person类型的对象  
44.	        // newInstance方法中的实参是用于给有参构造方法的形参进行初始化的,也就是给name和age进行初始化的  
45.	        System.out.println("有参方式构造的对象是:" + constructor1.newInstance("zhangfei", 30)); // zhangfei 30  
46.	  
47.	        System.out.println("---------------------------------------------------");  
48.	        // 5.使用反射机制获取Person类中所有的公共构造方法并打印  
49.	        Constructor[] constructors = c1.getConstructors();  
50.	        for (Constructor ct : constructors) {
    
      
51.	            System.out.println("构造方法的访问修饰符是:" + ct.getModifiers());  
52.	            System.out.println("构造方法的方法名称是:" + ct.getName());  
53.	            Class[] parameterTypes = ct.getParameterTypes();  
54.	            System.out.print("构造方法的所有参数类型是:");  
55.	            for (Class cs : parameterTypes) {
    
      
56.	                System.out.print(cs + " ");  
57.	            }  
58.	            System.out.println();  
59.	            System.out.println("-------------------------------------------------");  
60.	        }  
61.	    }  
62.	}  

5.4.Field类

5.4.1.基本概念
java.lang.reflect.Field类主要用于描述获取到的单个成员变量信息。
5.4.2.Class类的常用方法
方法声明 功能介绍
Field getDeclaredField(String name) 用于获取此Class对象所表示类中参数指定的单个成员变量信息
Field[] getDeclaredFields() 用于获取此Class对象所表示类中所有成员变量信息
5.4.3.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() 获取成员变量的名称

 
1.	import java.lang.reflect.Constructor;  
2.	import java.lang.reflect.Field;  
3.	  
4.	public class PersonFieldTest {
    
      
5.	  
6.	    public static void main(String[] args) throws Exception {
    
      
7.	  
8.	        // 1.使用原始方式来构造对象以及获取成员变量的数值并打印  
9.	        Person p1 = new Person("zhangfei", 30);  
10.	        //System.out.println("获取到的成员变量数值为:" + p1.name); // zhangfei  
11.	  
12.	        System.out.println("-------------------------------------------------------");  
13.	        // 2.使用反射机制来构造对象以及获取成员变量的数值并打印  
14.	        // 2.1 获取Class对象  
15.	        Class c1 = Class.forName("com.lagou.task20.Person");  
16.	        // 2.2 根据Class对象获取对应的有参构造方法  
17.	        Constructor constructor = c1.getConstructor(String.class, int.class);  
18.	        // 2.3 使用有参构造方法来得到Person类型的对象  
19.	        Object object = constructor.newInstance("zhangfei", 30);  
20.	        // 2.4 根据Class对象获取对应的成员变量信息  
21.	        Field field = c1.getDeclaredField("name");  
22.	        // 设置Java语言访问检查的取消  暴力反射  
23.	        field.setAccessible(true);  
24.	        // 2.5 使用Person类型的对象来获取成员变量的数值并打印  
25.	        // 获取对象object中名字为field成员变量的数值,也就是成员变量name的数值  
26.	        System.out.println("获取到的成员变量数值为:" + field.get(object)); // zhangfei  
27.	  
28.	        System.out.println("-------------------------------------------------------");  
29.	        // 3.使用原始方式修改指定对象中成员变量的数值后再次打印  
30.	        //p1.name = "guanyu";  
31.	        //System.out.println("修改后成员变量的数值为:" + p1.name); // guanyu  
32.	  
33.	        System.out.println("-------------------------------------------------------");  
34.	        // 4.使用反射机制修改指定对象中成员变量的数值后再次打印  
35.	        // 表示修改对象object中名字为field成员变量的数值为guanyu,也就是成员变量name的数值为guanyu  
36.	        field.set(object, "guanyu");  
37.	        System.out.println("修改后成员变量的数值为:" + field.get(object)); // guanyu  
38.	  
39.	        System.out.println("-------------------------------------------------------");  
40.	        // 5.获取Class对象对应类中所有的成员变量  
41.	        Field[] declaredFields = c1.getDeclaredFields();  
42.	        for (Field ft : declaredFields) {
    
      
43.	            System.out.println("获取到的访问修饰符为:" + ft.getModifiers());  
44.	            System.out.println("获取到的数据类型为:" + ft.getType());  
45.	            System.out.println("获取到的成员变量名称是:" + ft.getName());  
46.	            System.out.println("---------------------------------------------");  
47.	        }  
48.	    }  
49.	}  

5.5.Method类

5.5.1.基本概念
java.lang.reflect.Method类主要用于描述获取到的单个成员方法信息。
5.5.2.Class类的常用方法
方法声明 功能介绍
Method getMethod(String name, Class<?>… parameterTypes) 用于获取该Class对象表示类中名字为name参数为 parameterTypes的指定公共成员方法
Method[] getMethods() 用于获取该Class对象表示类中所有公共成员方法
5.5.3.Method类的常用方法
方法声明 功能介绍
Object invoke(Object obj, Object… args) 使用对象obj来调用此Method对象所表示的成员方法,实参传递args
int getModifiers() 获取方法的访问修饰符
Class<?> getReturnType() 获取方法的返回值类型
String getName() 获取方法的名称
Class<?>[] getParameterTypes() 获取方法所有参数的类型
Class<?>[] getExceptionTypes() 获取方法的异常信息

1.	import java.lang.reflect.Constructor;  
2.	import java.lang.reflect.Method;  
3.	  
4.	public class PersonMethodTest {
    
      
5.	  
6.	    public static void main(String[] args) throws Exception {
    
      
7.	  
8.	        // 1.使用原始方式构造对象并调用方法打印结果  
9.	        Person p1 = new Person("zhangfei", 30);  
10.	        System.out.println("调用方法的返回值是:" + p1.getName()); // zhangfei  
11.	  
12.	        System.out.println("------------------------------------------------------");  
13.	        // 2.使用反射机制构造对象并调用方法打印结果  
14.	        // 2.1 获取Class对象  
15.	        Class c1 = Class.forName("com.lagou.task20.Person");  
16.	        // 2.2 根据Class对象来获取对应的有参构造方法  
17.	        Constructor constructor = c1.getConstructor(String.class, int.class);  
18.	        // 2.3 使用有参构造方法构造对象并记录  
19.	        Object object = constructor.newInstance("zhangfei", 30);  
20.	        // 2.4 根据Class对象来获取对应的成员方法  
21.	        Method method = c1.getMethod("getName");  
22.	        // 2.5 使用对象调用成员方法进行打印  
23.	        // 表示使用object对象调用method表示的方法,也就是调用getName方法来获取姓名  
24.	        System.out.println("调用方法的返回值是:" + method.invoke(object)); // zhangfei  
25.	  
26.	        System.out.println("------------------------------------------------------");  
27.	        // 3.使用反射机制来获取类中的所有成员方法并打印  
28.	        Method[] methods = c1.getMethods();  
29.	        for (Method mt : methods) {
    
      
30.	            System.out.println("成员方法的修饰符是:" + mt.getModifiers());  
31.	            System.out.println("成员方法的返回值类型是:" + mt.getReturnType());  
32.	            System.out.println("成员方法的名称是:" + mt.getName());  
33.	            System.out.println("成员方法形参列表的类型是:");  
34.	            Class<?>[] parameterTypes = mt.getParameterTypes();  
35.	            for (Class ct : parameterTypes) {
    
      
36.	                System.out.print(ct + " ");  
37.	            }  
38.	            System.out.println();  
39.	            System.out.println("成员方法的异常类型列表是:");  
40.	            Class<?>[] exceptionTypes = mt.getExceptionTypes();  
41.	            for (Class ct: exceptionTypes) {
    
      
42.	                System.out.print(ct + " ");  
43.	            }  
44.	            System.out.println();  
45.	            System.out.println("---------------------------------------------------");  
46.	        }  
47.	    }  
48.	}  

5.6.获取其它结构信息

方法声明 功能介绍
Package getPackage() 获取所在的包信息
Class<? super T> getSuperclass() 获取继承的父类信息
Class<?>[] getInterfaces() 获取实现的所有接口
Annotation[] getAnnotations() 获取注解信息
Type[] getGenericInterfaces() 获取泛型信息

 
1.	import java.lang.annotation.ElementType;  
2.	import java.lang.annotation.Retention;  
3.	import java.lang.annotation.RetentionPolicy;  
4.	  
5.	@Retention(RetentionPolicy.RUNTIME)  
6.	public @interface MyAnnotation {
    
      
7.	}  

1.	import java.io.Serializable;  
2.	  
3.	@MyAnnotation  
4.	public class Student<T, E> extends Person implements Comparable, Serializable {
    
      
5.	  
6.	    @Override  
7.	    public int compareTo(Object o) {
    
      
8.	        return 0;  
9.	    }  
10.	}  

1.	import java.lang.annotation.Annotation;  
2.	import java.lang.reflect.Type;  
3.	  
4.	public class StudentTest {
    
      
5.	  
6.	    public static void main(String[] args) throws Exception {
    
      
7.	  
8.	        // 获取Student类型的Class对象  
9.	        Class c1 = Class.forName("com.lagou.task20.Student");  
10.	        System.out.println("获取到的包信息是:" + c1.getPackage());  
11.	        System.out.println("获取到的父类信息是:" + c1.getSuperclass());  
12.	  
13.	        System.out.println("-------------------------------------------------");  
14.	        System.out.println("获取到的接口信息是:");  
15.	        Class[] interfaces = c1.getInterfaces();  
16.	        for (Class ct : interfaces) {
    
      
17.	            System.out.print(ct + " ");  
18.	        }  
19.	        System.out.println();  
20.	  
21.	        System.out.println("-------------------------------------------------");  
22.	        System.out.println("获取到的注解信息是:");  
23.	        Annotation[] annotations = c1.getAnnotations();  
24.	        for (Annotation at : annotations) {
    
      
25.	            System.out.print(at + " ");  
26.	        }  
27.	        System.out.println();  
28.	  
29.	        System.out.println("-------------------------------------------------");  
30.	        System.out.println("获取到的泛型信息是:");  
31.	        Type[] genericInterfaces = c1.getGenericInterfaces();  
32.	        for (Type tt : genericInterfaces) {
    
      
33.	            System.out.print(tt + " ");  
34.	        }  
35.	        System.out.println();  
36.	    }  
37.	}  

猜你喜欢

转载自blog.csdn.net/BetterKunPeng/article/details/109529229
今日推荐