第十六章 异常机制和file类
16.1 异常机制(重点)
16.1.1 基本概念
所谓的异常就是Java程序在运行过程中发生了错误,出现了不正常的行为,中断正在执行程序的正常指令流;
Java.lang.Throwable类是Java中Error和Exception的基类;
Error称为错误,主要是一些JVM层面的错误,程序开发人员是无法通过程序去处理的,只能重启JVM。例如堆栈溢出。
Exception称为异常,主要是一些程序设计不严谨所抛出一些异常情况,开发人员可以事先通过编码去捕获处理。例如典型的空指针异常(NullPointException)。
16.1.2 异常的分类
- Throwable类URL图
上图所示
异常的分类主要分类两种:
RuntimeException 非检测性异常
IOException和其他异常 检测性异常
判断检测性异常和非检测性异常的区别:非检测性异常编译器无法识别,只有在程序运行的过程中才会发现;检测性
异常是编译器能够发现的
- 代码demo
System.out.println("-----------非检测性异常----------");
/**
* 此时编译器并没有提示报错,运行时会抛出Exception in thread "main" java.lang.ArithmeticException: / by zero
*/
System.out.println(4 / 0);
System.out.println("-----------检测性异常------------");
/**
* 会有红色波浪线提示unhandledException,此时需要捕获处理
*/
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
- 注意
一旦程序抛出异常,抛出异常地方下面的程序都不会再执行。
16.1.3 异常避免
通常程序开发使用if判断语句进行避免异常的发生
缺点:代码可读性差,臃肿
代码demo
System.out.println("-------------算术异常------------");
int a = 8;
int b = 0;
if (0 != b) {
System.out.println(a / b);
}
System.out.println("------------下标越界异常--------");
int[] arr = new int[5];
int index = 5;
if (arr.length > 0 && arr.length < 5) {
System.out.println(arr);
}
System.out.println("-------------空指针异常----------");
String str = null;
if (null != str) {
System.out.println(str.length());
}
System.out.println("------------类型转换异常---------");
Object it = new Object();
if(it instanceof String) {
String c = (String) it;
}
System.out.println("------------数据格式异常---------");
String numStr = "123r";
if (numStr.matches("\\d+")) {
int i = Integer.parseInt(numStr);
System.out.println(i);
}
16.1.6 自定义异常
- 自定义异常类
public class AgeException extends Exception{
/**
* 自定义异常AgeException,用于判断用户年龄是否符合要求
*/
public AgeException() {
super();
}
public AgeException(String message) {
super(message);
}
}
- 学生类
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
/**
* 使用throw进行声明异常
*
* @param age
* @throws AgeException
*/
public void setAge(int age) throws AgeException {
if (age > 0 && age < 120) {
this.age = age;
} else {
throw new AgeException("年龄不合理");
}
}
public Student(String name, int age) throws AgeException {
setName(name);
setAge(age);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
- 测试结果类
/**
* 1. 当使用throw的方式进行将一场进行跑出去的时候,由调用者进行try-catch进行捕获进行处理的时候,一旦发生异常就不会生成对象
*
* 打印结果:
*
* student1 = Student{name='fuyi', age=10}
* com.ryan.stage1.model4.task16.AgeException: 年龄不合理
* at com.ryan.stage1.model4.task16.Student.setAge(Student.java:32)
* at com.ryan.stage1.model4.task16.Student.<init>(Student.java:39)
* at com.ryan.stage1.model4.task16.StudentTest.main(StudentTest.java:13)
*/
try {
Student student1 = new Student("fuyi", 10);
System.out.println("student1 = " + student1);
Student student2 = new Student("Sam", 130);
System.out.println("student2 =" + student2);
} catch (AgeException e) {
e.printStackTrace();
}
- 使用try-catch方式
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
/**
* 使用throw进行声明异常
*
* @param age
* @throws AgeException
*/
public void setAge(int age) throws AgeException {
if (age > 0 && age < 120) {
this.age = age;
} else {
throw new AgeException("年龄不合理");
}
}
public Student(String name, int age) {
setName(name);
try {
setAge(age);
} catch (AgeException e) {
e.printStackTrace();
}
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
- 测试结果
/**
* 2. 如果在方法本身内部使用try-catch进行捕获处理,对象还是会生成
*
* 打印结果:
* com.ryan.stage1.model4.task16.AgeException: 年龄不合理
* at com.ryan.stage1.model4.task16.Student.setAge(Student.java:32)
* at com.ryan.stage1.model4.task16.Student.<init>(Student.java:45)
* at com.ryan.stage1.model4.task16.StudentTest.main(StudentTest.java:22)
* student3 = Student{name='Lucy', age=0}
*/
Student student3 = new Student("Lucy", 123);
System.out.println("student3 = " + student3);
- 总结
使用throw和try-catch的区别: 在于try-catch还是可以进行对象的生成,而使用throw进行抛出异常的时候,此时对象不会生成而为null。
16.2 File类
16.2.1 基本概念
java.io.File类主要用于描述文件或目录路径的抽象表示信息,可以获取文件或目录的特征信息,
如:大小等。
16.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) | 获取目录下满足筛选器的所有内容 |
第十七章 IO流
17.1 IO流的概念
IO就是Input和Output的简写。
IO流就是是Java程序读写数据的一种方式。流是一种有序的数据序列,将数据从一个地方带到另一个地方。
17.2 基本分类
-
按流的方向
输出流,从程序到文件、网络、数据库和其他数据源
输入流,从文件、网络、数据库和其他数据源到程序
如下图所示
-
按流的数据单位
字节流,以字节(8位)为单位进行数据的读写,可以读写任意类型的文件。
字符流,以字符(2个字节)为单位进行数据的读写,只能读取文本文件。
-
按流的功能
节点流,就是指直接和输入输出源对接的流。
处理流,指建立在在节点流的基础上的流。
17.3 体系结构
17.4 相关流的详解
- 流体系框架图
17.4.1 FileWriter类(重点)
- 作用
java.lang.FileWriter类主要用于将文本内容写入到文本文件
- 常用构造方法
方法声明 | 功能 |
---|---|
FileWriter(File file) | 使用平台的默认字符集构造给定要写入文件的FileWriter |
FileWriter(File file, boolean append) | 使用平台的默认字符集,构造一个FileWriter,给定要写入的File和一个布尔值,该布尔值指示是否追加写入的数据。 |
FileWriter(File file, Charset charset) | 给定File来构造FileWriter进行写和字符集设置。 |
代码demo
FileWriter fileWriter1 = null;
FileWriter fileWriter2 = null;
try {
//1. 构造方法一
fileWriter1 = new FileWriter("U:\\Ryan\\lagou-basic\\stage1\\src\\main\\java\\com\\ryan\\stage1\\model4\\task17\\file");
//2. 构造方法二(内容追加方式)
fileWriter2 = new FileWriter("U:\\Ryan\\lagou-basic\\stage1\\src\\main\\java\\com\\ryan\\stage1\\model4\\task17\\file", true);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileWriter1 != null || fileWriter2 != null) {
//关闭资源
try {
fileWriter1.close();
fileWriter2.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 常用方法
代码demo
System.out.println("--------------------------FileWriter类常用方法--------------------------------------");
/**
* 1. 写入单个字符,
*/
fileWriter1.write("B"); // B
/**
* 2. 将指定字符数组中从偏移量off开始的len个字符写入此文件输出流
*/
char[] cArr = new char[]{'a', 'b', 'c', 'd'};
fileWriter1.write(cArr, 1, 3); // bcd
/**
* 3. 将cbuf.length个字符从指定字符数组写入此文件输出流中
*/
fileWriter1.write(cArr); // bcdabcd
/**
* 4. 刷新流
*/
fileWriter1.flush();
/**
* 5. 关闭流对象释放有关的资源
*/
fileWriter1.close();
17.4.2 FileReader类(重点)
- 作用
java.lang.FileReader类主要用于从文本文件读取文本数据内容
- 常用构造方法
代码demo
System.out.println("-------------构造方法-----------------");
//1. 构造方法一
FileReader fileReader1 = null;
try {
fileReader1 = new FileReader("U:/Ryan/lagou-basic/stage1//src/main/java/com/ryan/stage1/model4/task17/file/myFile.txt");
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (fileReader1 != null) {
try {
fileReader1.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 常用类方法
代码demo
System.out.println("----------------常用方法-------------------------");
/**
* 1. 读取单个字符的数据并返回,返回-1表示读取到末尾
*/
int read = fileReader1.read();
System.out.println((char) read); // B
/**
* 2. 读取全部
*/
while (fileReader1.read() != -1) {
int read1 = fileReader1.read();
System.out.println("字符为:" + (char) read1);
}
/**
* 3. 从输入流中将最多len个字符的数据读入一个字符数组中,返回读取到的字符个数,返回-1表示读取到末尾
*/
char[] cArr = new char[5];
fileReader1.read(cArr, 0, 5);
System.out.println(cArr); // Bbcda
/** 4. 关闭流对象,释放资源
*/
fileReader1.close();
17.4.3 FileOutputStream类(重点)
上述的两个类主要对文件进行读写操作,但是对于音频和视频是不能进行读写,就算读写成功也是解析不出来,因此下面介绍能够读取任意文件类型的
文件流类
- 作用
java.io.FileOutputStream类主要用于将图像数据之类的原始字节流写入到输出流中。
- 构造方法
代码demo
System.out.println("-------------------构造方法-----------------------");
FileOutputStream fileOutputStream1 = null;
FileOutputStream fileOutputStream2 = null;
/**
* 创建文件输出流以写入具有指定名称的文件
*/
try {
//1. 构造一 (如果文件不存在会自动创建一个空白文件)
fileOutputStream1 = new FileOutputStream("e:/myFile.txt");
//2. 构造二 (进行追加内容)
fileOutputStream2 = new FileOutputStream("e:/myFile.txt",true);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (null != fileOutputStream1) {
try {
fileOutputStream1.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != fileOutputStream2) {
try {
fileOutputStream2.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 常用方法
System.out.println("-------------------构造方法-----------------------");
FileOutputStream fileOutputStream1 = null;
FileOutputStream fileOutputStream2 = null;
/**
* 创建文件输出流以写入具有指定名称的文件
*/
try {
//1. 构造一 (如果文件不存在会自动创建一个空白文件)
fileOutputStream1 = new FileOutputStream("e:/myFile.txt");
//2. 构造二 (进行追加内容)
fileOutputStream2 = new FileOutputStream("e:/myFile.txt",true);
System.out.println("--------------------常用方法----------------------");
/**
* 1. 将指定字节写入此文件输出流
*/
fileOutputStream1.write('G');
/**
* 2. 将指定字节数组中从偏移量off开始的len个字节写入此文件输出流
*/
byte[] arr = new byte[] {65, 66, 67, 68};
fileOutputStream1.write(arr);
/**
* 3. 刷新此输出流并强制写出任何缓冲的输出字节
*/
fileOutputStream1.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != fileOutputStream1) {
try {
/**
* 4. 关闭流对象并释放有关的资源
*/
fileOutputStream1.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != fileOutputStream2) {
try {
fileOutputStream2.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 总结
17.4.4 FileInputStream类(重点)
- 作用
java.io.FileInputStream类主要用于从输入流中以字节流的方式读取图像数据等。
- 构造方法
代码demo
System.out.println("-----------------构造方法------------------------");
FileInputStream fileInputStream1 = null;
try {
fileInputStream1= new FileInputStream("e:/myFile.txt");
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (null != fileInputStream1) {
try {
/**
* 4. 关闭流对象并释放有关的资源
*/
fileInputStream1.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 常用方法
System.out.println("-----------------构造方法------------------------");
FileInputStream fileInputStream1 = null;
try {
fileInputStream1= new FileInputStream("e:/myFile.txt");
System.out.println("----------------常用方法----------------------");
/**
* 1. 从输入流中读取单个字节的数据并返回,返回-1表示读取到末尾
* 注意:这里调用read()方法和迭代器itertor.next()方法是一样,每调用一次,游标向下移动一次
*/
int read = fileInputStream1.read();
System.out.println((char) read);
while ((read = fileInputStream1.read()) != -1) {
System.out.println((char) read); // A B C D
}
while (fileInputStream1.read() != -1) {
System.out.println((char) fileInputStream1.read()); // B D
}
/**
* 2. 从此输入流中将最多 b.length 个字节的数据读入字节数组中,返回读取到的字节个数,返回-1表示读取到末尾
*/
byte[] brr = new byte[5];
fileInputStream1.read(brr);
for (byte b : brr) {
System.out.print(b); // 71 65 66 67 68
}
/**
* 3. 获取输入流所关联文件的大小
*/
System.out.println("输入流大小为:" + fileInputStream1.available());// 5
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != fileInputStream1) {
try {
/**
* 4. 关闭流对象并释放有关的资源
*/
fileInputStream1.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 文件拷贝
//测试拷贝效率
long start = System.currentTimeMillis();
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream = null;
try {
//1. 创建文件输入流
fileInputStream = new FileInputStream("e:/myFile.txt");
//2. 创建文件输出流
fileOutputStream = new FileOutputStream("e:/myFileCopys.txt");
//3. 读取一个字节然后写入一个字节
int read = 0;
while ((read = fileInputStream.read()) != -1) {
fileOutputStream.write(read);
}
//4. 使用缓存分多少次进行读写
byte[] brr = new byte[1024];
int length = 0;
while ((length = fileInputStream.read(brr)) != -1) {
fileOutputStream.write(brr, 0, length);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭资源
if (null != fileInputStream) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != fileOutputStream) {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
long end = System.currentTimeMillis();
System.out.println("Copy消耗时间" + (end - start) + "ms"); //9ms
System.out.println("缓存方式Copy消耗时间" + (end - start) + "ms"); //1ms
-
总结
这里调用read()方法和迭代器itertor.next()方法是一样,每调用一次,游标向下移动一次;读取完后想要重新再读取一遍需要重新创建一个流对象
通过上面两个类可以实现文件的拷贝,拷贝的方式有三种:第一种是逐个字节读取并写入,缺点就是文件过大时候,花费时间很长;
第二种是通过将文件读入到一个文件大小的字节数组缓冲区里面,读取完毕再一并写入,缺点就是文件过大占用较大的物理内存空间;
第三种是在第二种的基础上通过分批多次读取并写入,相对前面两种来说效率得到提升和资源开销也减少了。
17.4.5 BufferedOutputStream类(重点)
上面章节结束的时候,我们使用自定义缓存的方式进行读写copy文件,Java官方也提供了这种缓存式的读写操作的类。本章节就继续讨论缓存式读写操作类
- 作用
java.io.BufferedOutputStream类主要用于描述缓冲输出流,此时不用为写入的每个字节调用底层系统。
- 构造方法
代码demo
BufferedOutputStream bos = null;
System.out.println("---------------------构造方法---------------------------");
try {
// 1.创建BufferedOutputStream类型的对象与e:/myFile.txt流的框架结构.txt文件关联
bos = new BufferedOutputStream(new FileOutputStream("e:/myFile.txt"));
} catch (IOException e) {
e.printStackTrace();
} finally {
// 4.关闭流对象并释放有关的资源
if (null != bos) {
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 常用方法
BufferedOutputStream bos = null;
System.out.println("---------------------构造方法---------------------------");
try {
// 1.创建BufferedOutputStream类型的对象与e:/myFile.txt流的框架结构.txt文件关联
bos = new BufferedOutputStream(new FileOutputStream("e:/myFile.txt"));
System.out.println("--------------------常用方法----------------------");
/**
* 1. 将指定字节写入此文件输出流
*/
bos.write('G');
/**
* 2. 将指定字节数组中从偏移量off开始的len个字节写入此文件输出流
*/
byte[] arr = new byte[] {65, 66, 67, 68};
bos.write(arr);
/**
* 3. 刷新此输出流并强制写出任何缓冲的输出字节
*/
bos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 4.关闭流对象并释放有关的资源
if (null != bos) {
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
-
总结
和上面的的流对象相比,只是构造方法的不同,其他的常用方法都是一样,后就不再加以赘述了。
17.4.6 BufferedInputStream类(重点)
- 作用
java.io.BufferedInputStream类主要用于描述缓冲输入流。
- 构造方法
代码demo
BufferedInputStream bis = null;
System.out.println("-------------------构造方法-------------------");
try {
new BufferedInputStream(new FileInputStream("e:/myFile.txt"));
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (null != bis) {
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 常用方法
参考FileInputStream
- 文件拷贝
// 获取当前系统时间距离1970年1月1日0时0分0秒的毫秒数
long g1 = System.currentTimeMillis();
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
// 1.创建BufferedInputStream类型的对象与e:/myFile.txt文件关联
bis = new BufferedInputStream(new FileInputStream("e:/myFile.txt"));
// 2.创建BufferedOuputStream类型的对象与e:/myFileCopys.txt文件关联
bos = new BufferedOutputStream(new FileOutputStream("e:/myFileCopys.txt"));
// 3.不断地从输入流中读取数据并写入到输出流中
byte[] bArr = new byte[1024];
int res = 0;
while ((res = bis.read(bArr)) != -1) {
bos.write(bArr, 0, res);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 4.关闭流对象并释放有关的资源
if (null != bos) {
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != bis) {
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
long g2 = System.currentTimeMillis();
System.out.println("使用缓冲区拷贝文件消耗的时间为:" + (g2-g1)); // 1ms
17.4.7 BufferedWrite类(重点)
- 作用
java.io.BufferedWriter类主要用于写入单个字符、字符数组以及字符串到输出流中。
- 构造方法
代码demo
System.out.println("-----------构造方法--------------");
BufferedWriter bufferedWriter1 = null;
try {
bufferedWriter1 = new BufferedWriter(new FileWriter("e:/myFile.txt"));
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != bufferedWriter1) {
try {
/**
* 6. 关闭资源
*/
bufferedWriter1.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 常用方法
System.out.println("-----------构造方法--------------");
BufferedWriter bufferedWriter1 = null;
try {
bufferedWriter1 = new BufferedWriter(new FileWriter("e:/myFile.txt"));
System.out.println("-------常用方法--------------");
/**
* 1. 写入单个字符到输出流中
*/
bufferedWriter1.write(70);
/**
* 2. 将字符串数组cbuf中所有内容写入输出流中
*/
char[] cArr = new char[] {'b', 'n', 'y'};
bufferedWriter1.write(cArr);
/**
* 3. 将参数指定的字符串内容写入输出流中
*/
bufferedWriter1.write("fuyi");
/**
* 4. 用于写入行分隔符到输出流中
*/
bufferedWriter1.newLine();
bufferedWriter1.write(cArr);
/**
* 5. 刷新流
*/
bufferedWriter1.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != bufferedWriter1) {
try {
/**
* 6. 关闭资源
*/
bufferedWriter1.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 总结
17.4.8 BufferedRead类(重点)
- 作用
java.io.BufferedReader类用于从输入流中读取单个字符、字符数组以及字符串。
- 构造方法
代码demo
System.out.println("--------------构造方法------------------");
BufferedReader bufferedReader1 = null;
try {
bufferedReader1 = new BufferedReader(new FileReader("e:/myFile.txt"));
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (null != bufferedReader1) {
try {
bufferedReader1.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 常用方法
BufferedReader bufferedReader1 = null;
try {
bufferedReader1 = new BufferedReader(new FileReader("e:/myFile.txt"));
System.out.println("-----------------常用方法---------------");
/**
* 1. 从输入流读取单个字符,读取到末尾则返回-1,否则返回实际读取到的字符内容
*/
int read = bufferedReader1.read();
System.out.println((char) read);
/**
* 2. 从输入流中读满整个数组cbuf
*/
char[] crr = new char[5];
bufferedReader1.read(crr);
System.out.println(crr);
/**
* 3. 读取一行字符串并返回,返回null表示读取到末尾
*/
String str = null;
while ((str = bufferedReader1.readLine()) != null) {
System.out.println(str);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != bufferedReader1) {
try {
/**
* 4. 关闭资源
*/
bufferedReader1.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 总结
17.4.9 PrintStream类(重点)
- 作用
java.io.PrintStream类主要用于更加方便地打印各种数据内容。
- 构造方法
代码demo
System.out.println("---------------构造方法------------------");
PrintStream printStream1 = null;
try {
printStream1 = new PrintStream(new FileOutputStream("e:/myFile.txt"));
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (null != printStream1) {
printStream1.close();
}
}
- 常用方法
PrintStream printStream1 = null;
try {
printStream1 = new PrintStream(new FileOutputStream("e:/myFile.txt"));
System.out.println("-----------------常用方法-------------------");
/**
* 1. 用于将参数指定的字符串内容打印出来
*/
printStream1.print("fuyinb");
/**
* 2. 用于打印字符串后并终止该行
*/
printStream1.println("cheshigailei");
printStream1.println("另起一行");
/**
* 3. 刷新流
*/
printStream1.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (null != printStream1) {
/**
* 4. 关闭流
*/
printStream1.close();
}
}
- 总结
17.4.10 PrintWriter类
- 作用
java.io.PrintWriter类主要用于将对象的格式化形式打印到文本输出流。
- 构造方法
代码demo
System.out.println("-------------------构造方法-------------------");
PrintWriter printWriter1 = null;
try {
printWriter1 = new PrintWriter(new FileWriter("e:/myChat.txt"));
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != printWriter1) {
printWriter1.close();
}
}
- 常用方法
参考PrintWriter类
- 总结
17.4.11 OutputStreamWriter类(重点)
- 作用
java.io.OutputStreamWriter类主要用于实现从字符流到字节流的转换。
- 构造方法
代码demo
OutputStreamWriter outputStreamWriter1 = null;
OutputStreamWriter outputStreamWriter2 = null;
try {
//构造方法一
outputStreamWriter1 = new OutputStreamWriter(new FileOutputStream("e:/myFile.txt"));
//构造方法二
outputStreamWriter2 = new OutputStreamWriter(new FileOutputStream("e:/myFile.txt"), "utf-8");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != outputStreamWriter1) {
try {
/**
* 3. 关闭流
*/
outputStreamWriter1.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != outputStreamWriter2) {
try {
outputStreamWriter2.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 常用方法
System.out.println("------------------常用方法-------------------");
/**
* 1. 将参数指定的字符串写入
*/
outputStreamWriter1.write(68);
outputStreamWriter1.write("jshgh");
outputStreamWriter1.write('j');
/**
* 2. 刷新流
*/
outputStreamWriter1.flush();
/**
* 3. 关闭流
*/
outputStreamWriter1.close();
- 总结
17.4.12 InputStreamReader类(重点)
- 作用
java.io.InputStreamReader类主要用于实现从字节流到字符流的转换。
- 常用方法
方法声明 | 功能介绍 |
---|---|
InputStreamReader(InputStream in) | 根据参数指定的引用来构造对象 |
InputStreamReader(InputStream in, String charsetName) | 根据参数指定的引用和编码来构造对象 |
int read(char[] cbuf) | 读取字符数据到参数指定的数组 |
void close() | 用于关闭输出流并释放有关的资源 |
17.4.13 字符编码
-
背景
- 计算机只能识别二进制数据,早期就是电信号。为了方便计算机可以识别各个国家的文字,就需要
将各个国家的文字采用数字编号的方式进行描述并建立对应的关系表,该表就叫做编码表。
- 计算机只能识别二进制数据,早期就是电信号。为了方便计算机可以识别各个国家的文字,就需要
-
常见编码
GBK
UTF-8
ASCII
发展
17.4.14 DataOutputStream类(了解)
- 作用
java.io.DataOutputStream类主要用于以适当的方式将基本数据类型写入输出流中。
- 常用方法
代码demo
DataOutputStream dataOutputStream = null;
try {
dataOutputStream = new DataOutputStream(new FileOutputStream("e:/myFile.txt"));
System.out.println("-------------------常用方法---------------------");
/**
* 1. 用于将参数指定的整数一次性写入输出流,优先写入高字节
*/
dataOutputStream.writeInt(67); // C
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != dataOutputStream) {
try {
dataOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
17.4.14 DataInputStream类(了解)
- 作用
java.io.DataInputStream类主要用于以适当的方式从输入流中读取基本数据类型的数据。
- 常用方法
DataInputStream dis = null;
try {
// 1.创建DataInputStream类型的对象与d:/a.txt文件关联
dis = new DataInputStream(new FileInputStream("e:/myFile.txt"));
// 2.从输入流中读取一个整数并打印
int res = dis.readInt(); // 读取4个字节
//int res = dis.read(); // 读取1个字节
System.out.println("读取到的整数数据是:" + res); // 67
} catch (IOException e) {
e.printStackTrace();
} finally {
// 3.关闭流对象并释放有关的资源
if (null != dis) {
try {
dis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
17.4.15 序列化与反序列化
-
基本概念
序列化 (对象 -> 字节流)
反序列化 (字节流 -> 对象)
-
序列化版本号
在 序列化存储/反序列化读取 或者是 序列化传输/反序列化接收 时,JVM 会把传来的字节流中的serialVersionUID与本地相应实体(类)的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。
-
transient关键字
在Java中,当一个类实现了java.io.Serializable接口,即表明了该类可以被序列化。我们可以把该类的属性序列化然后保存在外部,或者跟另外一个jvm进行数据传递。但是,我们是否想过,如果一个类包含隐私信息,如用户的密码等,那么这个属性就不能够被序列化到外部。当然,我们可以在序列化之前手动set该值为null,但是最优雅的做法就是使用transient关键字。
17.4.16 ObjectOutputStream类(重点)
-
作用
java.io.ObjectOutputStream类主要用于将一个对象的所有内容整体写入到输出流中。
只能将支持 java.io.Serializable 接口的对象写入流中。
类通过实现 java.io.Serializable 接口以启用其序列化功能。
所谓序列化主要指将一个对象需要存储的相关信息有效组织成字节序列的转化过程。
常用方法
ObjectOutputStream oos = null;
try {
// 1.创建ObjectOutputStream类型的对象与d:/a.txt文件关联
oos = new ObjectOutputStream(new FileOutputStream("e:/myFile.txt"));
// 2.准备一个Student类型的对象并初始化
Student student = new Student("fuyi", "123456");
// 3.将整个Student类型的对象写入输出流
oos.writeObject(student);
System.out.println("写入对象成功!");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 4.关闭流对象并释放有关的资源
if (null != oos) {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
17.4.17 ObjectInputStream类(重点)
-
作用
java.io.ObjectInputStream类主要用于从输入流中一次性将对象整体读取出来。
常用方法
ObjectInputStream ois = null;
try {
// 1.创建ObjectInputStream类型的对象与d:/a.txt文件关联
ois = new ObjectInputStream(new FileInputStream("e:/myFile.txt"));
// 2.从输入流中读取一个对象并打印
Object obj = ois.readObject();
System.out.println("读取到的对象是:" + obj); // fuyi 123456
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
// 3.关闭流对象并释放有关的资源
if (null != ois) {
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
-
编程开发经验
当希望将多个对象写入文件时,通常建议将多个对象放入一个集合中,然后将集合这个整体看做一
个对象写入输出流中,此时只需要调用一次readObject方法就可以将整个集合的数据读取出来,
从而避免了通过返回值进行是否达到文件末尾的判断。
17.4.18 RandomAccessFile类
-
作用
java.io.RandomAccessFile类主要支持对随机访问文件的读写操作。
常用方法
方法声明 | 功能介绍 |
---|---|
RandomAccessFile(String name, String mode) | 根据参数指定的名称和模式构造对象 r: 以只读方式打开 rw:打开以便读取和写入 rwd:打开以便读取和写入,同步文件内容的更新 rws:打开以便读取和写入,同步文件内容和元数据的更新 |
int read() | 读取单个字节的数据 |
void seek(long pos) | 用于设置从此文件的开头开始测量的文件指针偏移量 |
void write(int b) | 将参数指定的单个字节写入 |
void close() | 用于关闭流并释放有关的资源 |
- 代码demo
RandomAccessFile raf = null;
try {
// 1.创建RandomAccessFile类型的对象与d:/a.txt文件关联
raf = new RandomAccessFile("e:/myFile.txt", "rw");
// 2.对文件内容进行随机读写操作
// 设置距离文件开头位置的偏移量,从文件开头位置向后偏移3个字节 aellhello
raf.seek(3);
int res = raf.read();
System.out.println("读取到的单个字符是:" + (char)res); // a l
res = raf.read();
System.out.println("读取到的单个字符是:" + (char)res); // h 指向了e
raf.write('2'); // 执行该行代码后覆盖了字符'e'
System.out.println("写入数据成功!");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 3.关闭流对象并释放有关的资源
if (null != raf) {
try {
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
第十八章 多线程
18.1 基本概念
18.1.1 程序和进程
-
程序
数据结构 + 算法,主要指存放在硬盘上的可执行文件。
-
进程
主要指运行在内存中的可执行文件。
目前主流的操作系统都支持多进程,为了让操作系统同时可以执行多个任务,但进程是重量级的,
也就是新建一个进程会消耗CPU和内存空间等系统资源,因此进程的数量比较局限。
18.1.2 线程
为了解决上述问题就提出线程的概念,线程就是进程内部的程序流,也就是说操作系统内部支持多
进程的,而每个进程的内部又是支持多线程的,线程是轻量的,新建线程会共享所在进程的系统资
源,因此目前主流的开发都是采用多线程。多线程是采用时间片轮转法来保证多个线程的并发执行,所谓并发就是指宏观并行微观串行的机
制,本质上还是串行的,只不过是先CPU切换线程的速度很快,所以看起来是并行执行。
18.2 线程的创建(无比重要)
18.2.1 Thread类的概念
java.lang.Thread类代表线程,任何线程对象都是Thread类(子类)的实例。
Thread类是线程的模板,封装了复杂的线程开启等操作,封装了操作系统的差异性。
18.2.2 创建方式
- 自定义类继承Thread类并重写run方法,然后创建该类的对象调用start方法。
代码demo
class PrimeThread extends Thread {
long minPrime;
PrimeThread(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
}
}
启动线程
public class test {
public static void main(String[] args){
PrimeThread p = new PrimeThread(143);
p.start();
}
}
- 自定义类实现Runnable接口并重写run方法,创建该类的对象作为实参来构造Thread类型的对
象,然后使用Thread类型的对象调用start方法。
代码demo
class PrimeRun implements Runnable {
long minPrime;
PrimeRun(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
}
}
启动线程
public class test {
public static void main(String[] args){
PrimeRun p = new PrimeRun(143);
new Thread(p).start();
}
}
18.2.3 常用方法
方法声明 | 功能介绍 |
---|---|
Thread() | 使用无参的方式构造对象 |
Thread(String name) | 根据参数指定的名称来构造对象 |
Thread(Runnable target) | 根据参数指定的引用来构造对象,其中Runnable是个接口类型 |
Thread(Runnable target,String name) | 根据参数指定引用和名称来构造对象 |
void run() | 若使用Runnable引用构造了线程对象,调用该方法时最终调用接口中的版本若没有使用Runnable引用构造线程对象,调用该方法时则啥也不做 |
void start() | 用于启动线程,Java虚拟机会自动调用该线程的run方法 |
代码demo
/**
* 测试Thread的无参构造方法的所创建的对象调用run()方法是否是啥也没干
*
* Thread源码分析
* 首先new Thread()进入无参构造方法:
* -> 1. init(null, null, "Thread-" + nextThreadNum(), 0);
* -> 2. private void init(ThreadGroup g, Runnable target, String name,
* long stackSize) {
* init(g, target, name, stackSize, null, true);
* }
* -> 3. 在init方法中因为我们传入的target = null, 又因为在init的方法中this.target = target;所以此时的target == null
*/
Thread thread = new Thread();
/**
* run()方法源码:
* -> 4. if (target != null) {
* target.run();
* }
* -> 结论:没有执行run()方法
*/
thread.run();
System.out.println("测试结果!!");
测试run()和start()方法
public class SubThreadTest extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("SubThreadTeat子线程打印" + i);
}
}
}
代码测试
/**
* 测试线程构造方法一的启动
*/
//1. 创建一个Thread父类的引用指向子类SubThreadTest的对象
Thread thread1 = new SubThreadTest();
//2. run()
//注意:调用run()方法其实并没有启动一个子线程,只不过是执行SubThreadTest类中的run(),还是又主线程main进行执行指令
thread1.run();
//真正启动一条线程,本质是JVM会去启动一条线程,用来执行SubThreadTest类中的run()方法代码
thread1.start();
//3. 主线程main方法中打印
for (int i = 0; i < 10; i++) {
System.out.println("主线程main打印:" + i);
}
线程构造方法二
public class SubRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("SubRunnable子线程打印" + i);
}
}
}
测试运行
SubRunnable subRunnable = new SubRunnable();
/**
* 由Thread源码可得target = subRunnable
*/
Thread thread = new Thread(subRunnable);
/**
* if (target != null) {
* target.run();
* }
* 而此时的target = subRunnable, 所以调用的是SubRunnable.run()方法
*/
thread.start();
for (int i = 0; i < 10; i++) {
System.out.println("主线程main打印" + i);
}
18.2.4 执行流程
执行main方法的线程叫做主线程,执行run方法的线程叫做新线程/子线程。
main方法是程序的入口,对于start方法之前的代码来说,由主线程执行一次,当start方法调用成功后线程的个数由1个变成了2个,新启动的线程去执行run方法的代码,主线程继续向下执行,两个线程各自独立运行互不影响。
当run方法执行完毕后子线程结束,当main方法执行完毕后主线程结束。
两个线程执行没有明确的先后执行次序,由操作系统调度算法来决定。
18.2.5 方式比较
18.2.6 匿名内部类方式
实现两种线程创建的代码编写
/**
* 1. 原始语法
*/
// 继承Thread类的方式
Thread thread = new Thread() {
@Override
public void run() {
System.out.println("匿名方式创建");
}
};
thread.start();
// 实现Runnable接口的方式
Runnable ra = new Runnable() {
@Override
public void run() {
System.out.println("匿名方式创建");
}
};
Thread thread1 = new Thread(ra);
thread1.start();
/**
* 2. JDK8的lambda语法
*/
new Thread(() -> System.out.println("lambda匿名方式创建")).start();
18.3 线程的声明周期
18.4 线程的编号和名称
方法声明 | 功能介绍 |
---|---|
long getId() | 获取调用对象所表示线程的编号 |
String getName() | 获取调用对象所表示线程的名称 |
void setName(String name) | 设置/修改线程的名称为参数指定的数值 |
static Thread currentThread() | 获取当前正在执行线程的引用 |
代码示例
public class ThreadIdNameTest extends Thread{
public ThreadIdNameTest(String name) {
super(name);
}
public ThreadIdNameTest() {
}
/**
* 测试对线程id和name的管理
*/
@Override
public void run() {
System.out.println("子线程的信息:");
System.out.println("子线程的名字:" + getName());
System.out.println("子线程的Id:" + getId());
}
public static void main(String[] args) {
/**
* 结果打印
* 子线程的名字:Thread-0
* 子线程的Id:12
*/
Thread thread = new ThreadIdNameTest();
thread.start();
/**
* 修改线程名 fuyi
* 子线程的名字:fuyi
* 子线程的Id:13
*/
ThreadIdNameTest thread2 = new ThreadIdNameTest("fuyi");
thread2.start();
/**
* 获取当前主线程
* 主线程名字为:main
* 主线程ID为:1
*/
Thread thread1 = Thread.currentThread();
System.out.println("主线程名字为:" + thread1.getName());
System.out.println("主线程ID为:" + thread1.getId());
}
}
实现接口的方式
public class RunnableIdNameTest implements Runnable {
@Override
public void run() {
// 获取当前子线程名字
Thread thread = Thread.currentThread();
System.out.println("子线程的名字为:" + thread.getName());
System.out.println("子线程的ID为:" + thread.getId());
}
public static void main(String[] args) {
/**
* 无参方式创建启动线程
* 结果打印:
* 子线程的名字为:Thread-0
* 子线程的ID为:12
*/
new Thread(new RunnableIdNameTest()).start();
/**
* 设置线程名构造
* 结果打印
* 子线程的名字为:fuyi
* 子线程的ID为:13
*/
new Thread(new RunnableIdNameTest(),"fuyi").start();
System.out.println("主线程" + Thread.currentThread().getName());
}
}
18.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) | 用于设置线程为守护线程 |
18.6 线程同步机制(重点)
18.6.1 背景
- 当多个线程同时访问同一种共享资源时,可能会造成数据的覆盖等不一致性问题,此时就需要对线程之间进行通信和协调,该机制就叫做线程的同步机制。
- 多个线程并发读写同一个临界资源时会发生线程并发安全问题。
- 异步操作:多线程并发的操作,各自独立运行。
- 同步操作:多线程串行的操作,先后执行的顺序。
18.6.2 解决方案
- 由程序结果可知:当两个线程同时对同一个账户进行取款时,导致最终的账户余额不合理。
- 引发原因:线程一执行取款时还没来得及将取款后的余额写入后台,线程二就已经开始取款。
- 解决方案:让线程一执行完毕取款操作后,再让线程二执行即可,将线程的并发操作改为串行操作。
- 经验分享:在以后的开发尽量减少串行操作的范围,从而提高效率。
18.6.3 实现方式
- 在Java语言中使用synchronized关键字来实现同步/对象锁机制从而保证线程执行的原子性,具体
方式如下:
使用同步代码块的方式实现部分代码的锁定,格式如下:
synchronized(类类型的引用) {
编写所有需要锁定的代码;
} - 使用同步方法的方式实现所有代码的锁定。
直接使用synchronized关键字来修饰整个方法即可
该方式等价于:
synchronized(this) { 整个方法体的代码 }
18.6.4 静态方法锁定
- 当我们对一个静态方法加锁,如:
public synchronized static void xxx(){….} - 那么该方法锁的对象是类对象。每个类都有唯一的一个类对象。获取类对象的方式:类名.class。
- 静态方法与非静态方法同时使用了synchronized后它们之间是非互斥关系的。
- 原因在于:静态方法锁的是类对象而非静态方法锁的是当前方法所属对象。
18.6.5 注意事项
- 使用synchronized保证线程同步应当注意:
- 多个需要同步的线程在访问同步块时,看到的应该是同一个锁对象引用。
- 在使用同步块时应当尽量减少同步范围以提高并发的执行效率。
18.6.6 线程安全和不安全类
- StringBuffer类是线程安全的类,但StringBuilder类不是线程安全的类。
- Vector类和 Hashtable类是线程安全的类,但ArrayList类和HashMap类不是线程安全的类。
- Collections.synchronizedList() 和 Collections.synchronizedMap()等方法实现安全。
18.6.7 死锁
- 线程一执行的代码:
public void run(){
synchronized(a){ //持有对象锁a,等待对象锁b
synchronized(b){
编写锁定的代码;
}
}
} - 线程二执行的代码:
public void run(){
synchronized(b){ //持有对象锁b,等待对象锁a
synchronized(a){
编写锁定的代码;
}
}
} - 注意:
在以后的开发中尽量减少同步的资源,减少同步代码块的嵌套结构的使用!
18.6.8 使用锁实现线程同步
-
基本概念
- 从Java5开始提供了更强大的线程同步机制—使用显式定义的同步锁对象来实现。
- java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。
- 该接口的主要实现类是ReentrantLock类,该类拥有与synchronized相同的并发性,在以后的线程
安全控制中,经常使用ReentrantLock类显式加锁和释放锁。
常用方法
方法声明 | 功能介绍 |
---|---|
ReentrantLock() | 使用无参方式构造对象 |
void lock() | 获取锁 |
void unlock() | 释放锁 |
-
与synchronized方式的比较
Lock是显式锁,需要手动实现开启和关闭操作,而synchronized是隐式锁,执行锁定代码后自动
释放。Lock只有同步代码块方式的锁,而synchronized有同步代码块方式和同步方法两种锁。
使用Lock锁方式时,Java虚拟机将花费较少的时间来调度线程,因此性能更好。
18.6.9 Object类常用方法
方法声明 | 功能介绍 |
---|---|
void wait() | 用于使得线程进入等待状态,直到其它线程调用notify()或notifyAll()方法 |
void wait(long timeout) | 用于进入等待状态,直到其它线程调用方法或参数指定的毫秒数已经过去为止 |
void notify() | 用于唤醒等待的单个线程 |
void notifyAll() | 用于唤醒等待的所有线程 |
18.6.10 线程池
实现Callable接口
从Java5开始新增加创建线程的第三种方式为实现java.util.concurrent.Callable接口。
常用的方法如下:
方法声明 | 功能介绍 |
---|---|
V call() | 计算结果并返回 |
- FutureTask类
-java.util.concurrent.FutureTask类用于描述可取消的异步计算,该类提供了Future接口的基本实
现,包括启动和取消计算、查询计算是否完成以及检索计算结果的方法,也可以用于获取方法调用
后的返回结果。 - 常用的方法如下:
方法声明 | 功能介绍 |
---|---|
FutureTask(Callable callable) | 根据参数指定的引用来创建一个未来任务 |
V get() | 获取call方法计算的结果 |
线程池的由来
在服务器编程模型的原理,每一个客户端连接用一个单独的线程为之服务,当与客户端的会话结束
时,线程也就结束了,即每来一个客户端连接,服务器端就要创建一个新线程。
如果访问服务器的客户端很多,那么服务器要不断地创建和销毁线程,这将严重影响服务器的性
能。概念和原理
线程池的概念:首先创建一些线程,它们的集合称为线程池,当服务器接受到一个客户请求后,就
从线程池中取出一个空闲的线程为之服务,服务完后不关闭该线程,而是将该线程还回到线程池
中。
在线程池的编程模式下,任务是提交给整个线程池,而不是直接交给某个线程,线程池在拿到任务
后,它就在内部找有无空闲的线程,再把任务交给内部某个空闲的线程,任务是提交给整个线程
池,一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务。-
相关类和方法
从Java5开始提供了线程池的相关类和接口:java.util.concurrent.Executors类和
java.util.concurrent.ExecutorService接口。方法声明 功能介绍 static ExecutorService newCachedThreadPool() 创建一个可根据需要创建新线程的线程池 static ExecutorService newFixedThreadPool(int nThreads) 创建一个可重用固定线程数的线程池 static ExecutorService newSingleThreadExecutor() 创建一个只有一个线程的线程池 -
其中Executors是个工具类和线程池的工厂类,可以创建并返回不同类型的线程池,常用方法如下:
方法声明 功能介绍 void execute(Runnable command) 执行任务和命令,通常用于执行Runnable Future submit(Callable task) 执行任务和命令,通常用于执行Callable void shutdown() 启动有序关闭
第十九章 网络编程
19.1.1 七层网络模型
OSI(Open System Interconnect),即开放式系统互联,是ISO(国际标准化组织)组织在1985
年研究的网络互连模型。
OSI七层模型和TCP/IP五层模型的划分如下:
当发送数据时,需要对发送的内容按照上述七层模型进行层层加包后发送出去。
当接收数据时,需要对接收的内容按照上述七层模型相反的次序层层拆包并显示出来。
19.1.2 相关的协议(笔试题)
(1)协议的概念
- 计算机在网络中实现通信就必须有一些约定或者规则,这种约定和规则就叫做通信协议,通信协议
可以对速率、传输代码、代码结构、传输控制步骤、出错控制等制定统一的标准。
(2)TCP协议
- 传输控制协议(Transmission Control Protocol),是一种面向连接的协议,类似于打电话。
- 建立连接 => 进行通信 => 断开连接
- 在传输前采用"三次握手"方式。
- 在通信的整个过程中全程保持连接,形成数据传输通道。
- 保证了数据传输的可靠性和有序性。
- 是一种全双工的字节流通信方式,可以进行大数据量的传输。
- 传输完毕后需要释放已建立的连接,发送数据的效率比较低。
(3)UDP协议
- 用户数据报协议(User Datagram Protocol),是一种非面向连接的协议,类似于写信。
- 在通信的整个过程中不需要保持连接,其实是不需要建立连接。
- 不保证数据传输的可靠性和有序性。
- 是一种全双工的数据报通信方式,每个数据报的大小限制在64K内。
- 发送数据完毕后无需释放资源,开销小,发送数据的效率比较高,速度快。
19.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
19.1.4 端口号(重点)
IP地址 - 可以定位到具体某一台设备。
端口号 - 可以定位到该设备中具体某一个进程。
端口号本质上是16位二进制组成的整数,表示范围是:0 ~ 65535,其中0 ~ 1024之间的端口号通
常被系统占用,建议编程从1025开始使用。特殊的端口:
HTTP:80 FTP:21 Oracle:1521 MySQL:3306 Tomcat:8080网络编程需要提供:IP地址 + 端口号,组合在一起叫做网络套接字:Socket。
19.2 基于tcp协议的编程模型(重点)
19.2.1 C/S架构的简介
在C/S模式下客户向服务器发出服务请求,服务器接收请求后提供服务。
例如:在一个酒店中,顾客找服务员点菜,服务员把点菜单通知厨师,厨师按点菜单做好菜后让服
务员端给客户,这就是一种C/S工作方式。如果把酒店看作一个系统,服务员就是客户端,厨师就
是服务器。这种系统分工和协同工作的方式就是C/S的工作方式。客户端部分:为每个用户所专有的,负责执行前台功能。
服务器部分:由多个用户共享的信息与功能,招待后台服务。
19.2.2 编程模型
服务器:
(1)创建ServerSocket类型的对象并提供端口号;
(2)等待客户端的连接请求,调用accept()方法;
(3)使用输入输出流进行通信;
(4)关闭Socket;
客户端:
(1)创建Socket类型的对象并提供服务器的IP地址和端口号;
(2)使用输入输出流进行通信;
(3)关闭Socket;
19.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()
19.3 基于udp协议的编程模型(熟悉)
19.3.1 编程模型
接收方:
(1)创建DatagramSocket类型的对象并提供端口号;
(2)创建DatagramPacket类型的对象并提供缓冲区;
(3)通过Socket接收数据内容存放到Packet中,调用receive方法;
(4)关闭Socket;
发送方:
(1)创建DatagramSocket类型的对象;
(2)创建DatagramPacket类型的对象并提供接收方的通信地址;
(3)通过Socket将Packet中的数据内容发送出去,调用send方法;
(4)关闭Socket;
19.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) | 根据参数指定的主机名获取通信地址 |
19.4 URL类(熟悉)
19.4.1 基本概念
java.net.URL(Uniform Resource Identifier)类主要用于表示统一的资源定位器,也就是指向万
维网上“资源”的指针。这个资源可以是简单的文件或目录,也可以是对复杂对象的引用,例如对数
据库或搜索引擎的查询等。
通过URL可以访问万维网上的网络资源,最常见的就是www和ftp站点,浏览器通过解析给定的
URL可以在网络上查找相应的资源。
URL的基本结构如下:
<传输协议>://<主机名>:<端口号>/<资源地址>
方法声明 | 功能介绍 |
---|---|
URL(String spec) | 根据参数指定的字符串信息构造对象 |
String getProtocol() | 获取协议名称 |
String getHost() | 获取主机名称 |
int getPort() | 获取端口号 |
String getPath() | 获取路径信息 |
String getFile() | 获取文件名 |
URLConnection openConnection() | 获取URLConnection类的实例 |
方法声明 | 功能介绍 |
---|---|
InputStream getInputStream() | 获取输入流 |
void disconnect() | 断开连接 |
第二十章 反射机制
20.1 基本概念
通常情况下编写代码都是固定的,无论运行多少次执行的结果也是固定的,在某些特殊场合中编写
代码时不确定要创建什么类型的对象,也不确定要调用什么样的方法,这些都希望通过运行时传递
的参数来决定,该机制叫做动态编程技术,也就是反射机制。
通俗来说,反射机制就是用于动态创建对象并且动态调用方法的机制。
目前主流的框架底层都是采用反射机制实现的。如:
Person p = new Person(); - 表示声明Person类型的引用指向Person类型的对象
p.show(); - 表示调用Person类中的成员方法show
20.2 Class类
20.2.1 基本概念
java.lang.Class类的实例可以用于描述Java应用程序中的类和接口,也就是一种数据类型。
该类没有公共构造方法,该类的实例由Java虚拟机和类加载器自动构造完成,本质上就是加载到内
存中的运行时类。
20.2.2 获取Class对象的方式
使用数据类型.class的方式可以获取对应类型的Class对象(掌握)。
使用引用/对象.getClass()的方式可以获取对应类型的Class对象。
使用包装类.TYPE的方式可以获取对应基本数据类型的Class对象。
使用Class.forName()的方式来获取参数指定类型的Class对象(掌握)。
使用类加载器ClassLoader的方式获取指定类型的Class对象。
20.2.3 常用的方法(掌握)
方法声明 | 功能介绍 |
---|---|
static Class<?> forName(String className) | 用于获取参数指定类型对应的Class对象并返回 |
T newInstance() | 用于创建该Class对象所表示类的新实例 |
20.3 Constructor类
20.3.1 基本概念
java.lang.reflect.Constructor类主要用于描述获取到的构造方法信息
20.3.2 Class类的常用方法
方法声明 | 功能介绍 |
---|---|
Constructor getConstructor(Class<?>...parameterTypes) | 用于获取此Class对象所表示类型中参数指定的公共构造方法 |
Constructor<?>[] getConstructors() | 用于获取此Class对象所表示类型中所有的公共构造方法 |
20.3.3 Constructor类的常用方法
方法声明 | 功能介绍 |
---|---|
T newInstance(Object...initargs) | 使用此Constructor对象描述的构造方法来构造Class对象代表类型的新实例 |
int getModifiers() | 获取方法的访问修饰符 |
String getName() | 获取方法的名称 |
Class<?>[] getParameterTypes() | 获取方法所有参数的类型 |
20.4 Field类
20.4.1 基本概念
java.lang.reflect.Field类主要用于描述获取到的单个成员变量信息。
20.4.2 Class类的常用方法
方法声明 | 功能介绍 |
---|---|
Field getDeclaredField(String name) | 用于获取此Class对象所表示类中参数指定的单个成员变量信息 |
Field[] getDeclaredFields() | 用于获取此Class对象所表示类中所有成员变量信息 |
20.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() | 获取成员变量的名称 |
20.5 Method类
20.5.1 基本概念
java.lang.reflect.Method类主要用于描述获取到的单个成员方法信息。
20.5.2 Class类的常用方法
方法声明 | 功能介绍 |
---|---|
Method getMethod(String name,Class<?>... parameterTypes) | 用于获取该Class对象表示类中名字为name参数为parameterTypes的指定公共成员方法 |
Method[] getMethods() | 用于获取该Class对象表示类中所有公共成员方法 |
20.5.3 Method类的常用方法
方法声明 | 功能介绍 |
---|---|
Object invoke(Object obj,Object... args) | 使用对象obj来调用此Method对象所表示的成员方法,实参传递args |
int getModifiers() | 获取方法的访问修饰符 |
Class<?> getReturnType() | 获取方法的返回值类型 |
String getName() | 获取方法的名称 |
Class<?>[] getParameterTypes() | 获取方法所有参数的类型 |
Class<?>[] getExceptionTypes() | 获取方法的异常信息 |
20.6 获取其它结构信息
方法声明 | 功能介绍 |
---|---|
Package getPackage() | 获取所在的包信息 |
Class<? super T> getSuperclass() | 获取继承的父类信息 |
Class<?>[] getInterfaces() | 获取实现的所有接口 |
Annotation[] getAnnotations() | 获取注解信息 |
Type[] getGenericInterfaces() | 获取泛型信息 |