2. IO 流原理及流的分类

2.1 Java IO 原理
• Java 程序中,对于数据的输入/输出操作以“流(stream)” 的方式进行,可以看做是一种数据的流动。
• I/O 流中的 I/O 是 Input/Output 的缩写, I/O 技术是非常实用的技术,用于处理设备之间的数据传输。如读/写文件,网络通讯等。
– 输入 input:读取外部数据(磁盘、光盘等存储设备的数据)到程序(内
存)中。
– 输出 output:将程序(内存)数据输出到磁盘、光盘等存储设备中。

2.2 流的分类
java.io 包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过标准的方法输入或输出数据。

• 按数据的流向不同分为:输入流和输出流。
– 输入流 :把数据从其他设备上读取到内存中的流。
• 以 InputStream、Reader 结尾
– 输出流 :把数据从内存 中写出到其他设备上的流。
• 以 OutputStream、Writer 结尾

• 按操作数据单位的不同分为:字节流(8bit)和字符流(16bit)。
– 字节流 :以字节为单位,读写数据的流。
• 以 InputStream、OutputStream 结尾
– 字符流 :以字符为单位,读写数据的流。
• 以 Reader、Writer 结尾

• 根据 IO 流的角色不同分为:节点流和处理流。
– 节点流:直接从数据源或目的地读写数据
– 处理流:不直接连接到数据源或目的地,而是“连接”在已存在的流(节点
流或处理流)之上,通过对数据的处理为程序提供更为强大的读写功能。

在这里插入图片描述
2.3 流的 API
• Java 的 IO 流共涉及 40 多个类,实际上非常规则,都是从如下 4 个抽象基类派生的。

抽象基类 输入流 输出流
字节流 InputStream OutputStream
字符流 Reader Writer

在这里插入图片描述
常用的节点流:
文件流: FileInputStream、FileOutputStrean、FileReader、FileWriter
字节/字符数组流: ByteArrayInputStream、ByteArrayOutputStream、
CharArrayReader、CharArrayWriter
– 对数组进行处理的节点流(对应的不再是文件,而是内存中的一个数组)。

常用处理流:
缓冲流:BufferedInputStream、BufferedOutputStream、BufferedReader、
BufferedWriter
– 作用:增加缓冲功能,避免频繁读写硬盘,进而提升读写效率。
转换流:InputStreamReader、OutputStreamReader
– 作用:实现字节流和字符流之间的转换。
对象流:ObjectInputStream、ObjectOutputStream
– 作用:提供直接读写 Java 对象功能

  1. 节点流之一:FileReader\FileWriter

3.1 Reader 与 Writer
Java 提供一些字符流类,以字符为单位读写数据,专门用于处理文本文件。不
能操作图片,视频等非文本文件。
常见的文本文件有如下的格式:.txt、.java、.c、.cpp、.py 等
注意:.doc、.xls、.ppt 这些都不是文本文件。

3.1.1 字符输入流:Reader
java.io.Reader 抽象类是表示用于读取字符流的所有类的父类,可以读取字符
信息到内存中。它定义了字符输入流的基本共性功能方法。
• public int read(): 从输入流读取一个字符。 虽然读取了一个字符,但是会自
动提升为 int 类型。返回该字符的 Unicode 编码值。如果已经到达流末尾了,则返回-
1。
• public int read(char[] cbuf): 从输入流中读取一些字符,并将它们存储到
字符数组 cbuf 中 。每次最多读取 cbuf.length 个字符。返回实际读取的字符个数。
如果已经到达流末尾,没有数据可读,则返回-1。
• public int read(char[] cbuf,int off,int len):从输入流中读取一些字
符,并将它们存储到字符数组 cbuf 中,从 cbuf[off]开始的位置存储。每次最多读取
len 个字符。返回实际读取的字符个数。如果已经到达流末尾,没有数据可读,则返
回-1。
• public void close() :关闭此流并释放与此流相关联的任何系统资源。
注意:当完成流的操作时,必须调用 close()方法,释放系统资源,否
则会造成内存泄漏。
3.1.2 字符输出流:Writer
java.io.Writer 抽象类是表示用于写出字符流的所有类的超类,将指定的字符
信息写出到目的地。它定义了字节输出流的基本共性功能方法。
• public void write(int c) :写出单个字符。
• public void write(char[] cbuf):写出字符数组。
• public void write(char[] cbuf, int off, int len):写出字符数组的某
一部分。off:数组的开始索引;len:写出的字符个数。
• public void write(String str):写出字符串。
• public void write(String str, int off, int len) :写出字符串的某一
部分。off:字符串的开始索引;len:写出的字符个数。
• public void flush():刷新该流的缓冲。
• public void close() :关闭此流。
注意:当完成流的操作时,必须调用 close()方法,释放系统资源,否
则会造成内存泄漏。

3.2 FileReader 与 FileWriter
3.2.1 FileReader
java.io.FileReader 类用于读取字符文件,构造时使用系统默认的字符编码和
默认字节缓冲区。
• FileReader(File file): 创建一个新的 FileReader ,给定要读取的 File 对象。
• FileReader(String fileName): 创建一个新的 FileReader ,给定要读取的文
件的名称。
举例:读取 hello.txt 文件中的字符数据,并显示在控制台上


public class FileReaderWriterTest {
    
    
 
 //实现方式 1
 @Test
 public void test1() throws IOException {
    
    
 //1. 创建 File 类的对象,对应着物理磁盘上的某个文件
 File file = new File("hello.txt");
 //2. 创建 FileReader 流对象,将 File 类的对象作为参数传递到 FileReader 的构造器中
 FileReader fr = new FileReader(file);
 //3. 通过相关流的方法,读取文件中的数据
// int data = fr.read(); //每调用一次读取一个字符
// while (data != -1) {
    
    
// System.out.print((char) data);
// data = fr.read();
// }
 int data;
 while ((data = fr.read()) != -1) {
    
    
 System.out.print((char) data);
 }
 //4. 关闭相关的流资源,避免出现内存泄漏
 fr.close();
 }
 //实现方式 2:在方式 1 的基础上改进,使用 try-catch-finally 处理异常。保证流是可以关闭的
 @Test
 public void test2() {
    
    
 FileReader fr = null;
 try {
    
    
 //1. 创建 File 类的对象,对应着物理磁盘上的某个文件
 File file = new File("hello.txt");
 //2. 创建 FileReader 流对象,将 File 类的对象作为参数传递到 FileReader 的构造器中
 fr = new FileReader(file);
 //3. 通过相关流的方法,读取文件中的数据
 /*
 * read():每次从对接的文件中读取一个字符。并将此字符返回。
 * 如果返回值为-1,则表示文件到了末尾,可以不再读取。
 * */
// int data = fr.read();
// while(data != -1){
    
    
// System.out.print((char)data);
// data = fr.read();
// }
 int data;
 while ((data = fr.read()) != -1) {
    
    
 System.out.println((char) data);
 }
 } catch (IOException e) {
    
    
 e.printStackTrace();
 } finally {
    
    
 //4. 关闭相关的流资源,避免出现内存泄漏
 try {
    
    
 if (fr != null)
 fr.close();
 } catch (IOException e) {
    
    
 e.printStackTrace();
 }
 }
 }
 //实现方式 3:调用 read(char[] cbuf),每次从文件中读取多个字符
 @Test
 public void test3() {
    
    
 FileReader fr = null;
 try {
    
    
 //1. 创建 File 类的对象,对应着物理磁盘上的某个文件
 File file = new File("hello.txt");
 //2. 创建 FileReader 流对象,将 File 类的对象作为参数传递到 Fil
eReader 的构造器中
 fr = new FileReader(file);
 //3. 通过相关流的方法,读取文件中的数据
 char[] cbuf = new char[5];
 /*
 * read(char[] cbuf) : 每次将文件中的数据读入到 cbuf 数组中,并返回读入到数组中的
 * 字符的个数。
 * */
 int len; //记录每次读入的字符的个数
 while ((len = fr.read(cbuf)) != -1) {
    
    
 //处理 char[]数组即可
 //错误:
// for(int i = 0;i < cbuf.length;i++){
    
    
// System.out.print(cbuf[i]);
// }
 //错误:
// String str = new String(cbuf);
// System.out.print(str);
 //正确:
// for(int i = 0;i < len;i++){
    
    
// System.out.print(cbuf[i]);
// }
 //正确:
 String str = new String(cbuf, 0, len);
 System.out.print(str);
 }
 } catch (IOException e) {
    
    
 e.printStackTrace();
 } finally {
    
    
 //4. 关闭相关的流资源,避免出现内存泄漏
 try {
    
    
 if (fr != null)
 fr.close();
 } catch (IOException e) {
    
    
 e.printStackTrace();
 }
 }
 }
}

3.2.2 FileWriter
java.io.FileWriter 类用于写出字符到文件,构造时使用系统默认的字符编码
和默认字节缓冲区。
• FileWriter(File file): 创建一个新的 FileWriter,给定要读取的 File 对象。
• FileWriter(String fileName): 创建一个新的 FileWriter,给定要读取的文件
的名称。
• FileWriter(File file,boolean append): 创建一个新的 FileWriter,指明是
否在现有文件末尾追加内容。
举例:

public class FWWrite {
    
    
 //注意:应该使用 try-catch-finally 处理异常。这里出于方便阅读代码,使
用了 throws 的方式
 @Test
 public void test01()throws IOException {
    
    
 // 使用文件名称创建流对象
 FileWriter fw = new FileWriter(new File("fw.txt"));
 // 写出数据
 fw.write(97); // 写出第 1 个字符
 fw.write('b'); // 写出第 2 个字符
 fw.write('C'); // 写出第 3 个字符
 fw.write(30000); // 写出第 4 个字符,中文编码表中 30000 对应一个汉
字。
 //关闭资源
 fw.close();
 }
//注意:应该使用 try-catch-finally 处理异常。这里出于方便阅读代码,使用了 throws 的方式
 @Test
 public void test02()throws IOException {
    
    
 // 使用文件名称创建流对象
 FileWriter fw = new FileWriter(new File("fw.txt"));
 // 字符串转换为字节数组
 char[] chars = "尚硅谷".toCharArray();
 // 写出字符数组
 fw.write(chars); // 尚硅谷
 // 写出从索引 1 开始,2 个字符。
 fw.write(chars,1,2); // 硅谷
 // 关闭资源
 fw.close();
 }
//注意:应该使用 try-catch-finally 处理异常。这里出于方便阅读代码,使用了 throws 的方式
 @Test
 public void test03()throws IOException {
    
    
 // 使用文件名称创建流对象
 FileWriter fw = new FileWriter("fw.txt");
 // 字符串
 String msg = "尚硅谷";
 // 写出字符数组
 fw.write(msg); //尚硅谷
 // 写出从索引 1 开始,2 个字符。
 fw.write(msg,1,2); // 硅谷
 // 关闭资源
 fw.close();
 }
 
 @Test
 public void test04(){
    
    
 FileWriter fw = null;
 try {
    
    
 //1. 创建 File 的对象
 File file = new File("personinfo.txt");
 //2. 创建 FileWriter 的对象,将 File 对象作为参数传递到 FileWriter 的构造器中
 //如果输出的文件已存在,则会对现有的文件进行覆盖
 fw = new FileWriter(file);
// fw = new FileWriter(file,false);
 //如果输出的文件已存在,则会在现有的文件末尾写入数据
// fw = new FileWriter(file,true);
 //3. 调用相关的方法,实现数据的写出操作
 //write(String str) / write(char[] cbuf)
 fw.write("I love you,");
 fw.write("you love him.");
 fw.write("so sad".toCharArray());
 } catch (IOException e) {
    
    
 e.printStackTrace();
 } finally {
    
    
 //4. 关闭资源,避免内存泄漏
 try {
    
    
 if (fw != null)
 fw.close();
 } catch (IOException e) {
    
    
 throw new RuntimeException(e);
 }
 }
 }
}

3.2.3 小结

因为出现流资源的调用,为了避免内存泄漏,需要使用 try-catch-finally 处理异


对于输入流来说,File 类的对象必须在物理磁盘上存在,否则执行就会报 FileNotFo
undException。如果传入的是一个目录,则会报 IOException 异常。
对于输出流来说,File 类的对象是可以不存在的。

如果 File 类的对象不存在,则可以在输出的过程中,自动创建 File 类的对象
如果 File 类的对象存在,
如果调用 FileWriter(File file)或 FileWriter(File file,false),
输出时会新建 File 文件覆盖已有的文件
如果调用 FileWriter(File file,true)构造器,则在现有的文件末尾追加
写出内容。

3.3 关于 flush(刷新)
因为内置缓冲区的原因,如果 FileWriter 不关闭输出流,无法写出字符到文件
中。但是关闭的流对象,是无法继续写出数据的。如果我们既想写出数据,又
想继续使用流,就需要 flush() 方法了。
• flush() :刷新缓冲区,流对象可以继续使用。
• close():先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。
注意:即便是 flush()方法写出了数据,操作的最后还是要调用 close 方法,释放
系统资源。
举例:

public class FWWriteFlush {
    
    
 //注意:应该使用 try-catch-finally 处理异常。这里出于方便阅读代码,使
用了 throws 的方式
 @Test
 public void test() throws IOException {
    
    
 // 使用文件名称创建流对象
 FileWriter fw = new FileWriter("fw.txt");
 // 写出数据,通过 flush
 fw.write('刷'); // 写出第 1 个字符
 fw.flush();
 fw.write('新'); // 继续写出第 2 个字符,写出成功
 fw.flush();
 // 写出数据,通过 close
 fw.write('关'); // 写出第 1 个字符
 fw.close();
 fw.write('闭'); // 继续写出第 2 个字符,【报错】java.io.IOException: Stream closed
 fw.close();
 }
}
  1. 节点流之二:FileInputStream\FileOutputStream
    如果我们读取或写出的数据是非文本文件,则 Reader、Writer 就无能为力了,
    必须使用字节流。
    4.1 InputStream 和 OutputStream
    4.1.1 字节输入流:InputStream
    java.io.InputStream 抽象类是表示字节输入流的所有类的超类,可以读取字
    节信息到内存中。它定义了字节输入流的基本共性功能方法。
    • public int read(): 从输入流读取一个字节。返回读取的字节值。虽然读取了
    一个字节,但是会自动提升为 int 类型。如果已经到达流末尾,没有数据可读,则返
    回-1。
    • public int read(byte[] b): 从输入流中读取一些字节数,并将它们存储到字
    节数组 b 中 。每次最多读取 b.length 个字节。返回实际读取的字节个数。如果已经
    到达流末尾,没有数据可读,则返回-1。
    • public int read(byte[] b,int off,int len):从输入流中读取一些字节
    数,并将它们存储到字节数组 b 中,从 b[off]开始存储,每次最多读取 len 个字节 。
    返回实际读取的字节个数。如果已经到达流末尾,没有数据可读,则返回-1。
    • public void close() :关闭此输入流并释放与此流相关联的任何系统资源。
    说明:close()方法,当完成流的操作时,必须调用此方法,释放系统
    资源。
    4.1.2 字节输出流:OutputStream
    java.io.OutputStream 抽象类是表示字节输出流的所有类的超类,将指定的字
    节信息写出到目的地。它定义了字节输出流的基本共性功能方法。
    • public void write(int b) :将指定的字节输出流。虽然参数为 int 类型四个字
    节,但是只会保留一个字节的信息写出。
    • public void write(byte[] b):将 b.length 字节从指定的字节数组写入此输出
    流。
    • public void write(byte[] b, int off, int len) :从指定的字节数组写
    入 len 字节,从偏移量 off 开始输出到此输出流。
    • public void flush() :刷新此输出流并强制任何缓冲的输出字节被写出。
    • public void close() :关闭此输出流并释放与此流相关联的任何系统资源。
    说明:close()方法,当完成流的操作时,必须调用此方法,释放系统
    资源。
    4.2 FileInputStream 与 FileOutputStream
    4.2.1 FileInputStream
    java.io.FileInputStream 类是文件输入流,从文件中读取字节。
    • FileInputStream(File file): 通过打开与实际文件的连接来创建一个
    FileInputStream ,该文件由文件系统中的 File 对象 file 命名。
    • FileInputStream(String name): 通过打开与实际文件的连接来创建一个
    FileInputStream ,该文件由文件系统中的路径名 name 命名。
    举例:
    //read.txt 文件中的内容如下:

abcde
读取操作
public class FISRead {
    
    
 //注意:应该使用 try-catch-finally 处理异常。这里出于方便阅读代码,使
用了 throws 的方式
 @Test
 public void test() throws IOException {
    
    
 // 使用文件名称创建流对象
 FileInputStream fis = new FileInputStream("read.txt");
 // 读取数据,返回一个字节
 int read = fis.read();
 System.out.println((char) read);
 read = fis.read();
 System.out.println((char) read);
 read = fis.read();
 System.out.println((char) read);
 read = fis.read();
 System.out.println((char) read);
 read = fis.read();
 System.out.println((char) read);
 // 读取到末尾,返回-1
 read = fis.read();
 System.out.println(read);
 // 关闭资源
 fis.close();
 /*
 文件内容:abcde
 输出结果:
 a
 b
 c
 d
 e
 -1
 */
 }
 @Test
 public void test02()throws IOException{
    
    
 // 使用文件名称创建流对象
 FileInputStream fis = new FileInputStream("read.txt");
 // 定义变量,保存数据
 int b;
 // 循环读取
 while ((b = fis.read())!=-1) {
    
    
 System.out.println((char)b);
 }
 // 关闭资源
 fis.close();
 }
 @Test
 public void test03()throws IOException{
    
    
 // 使用文件名称创建流对象.
 FileInputStream fis = new FileInputStream("read.txt"); // 文件中为 abcde
 // 定义变量,作为有效个数
 int len;
 // 定义字节数组,作为装字节数据的容器
 byte[] b = new byte[2];
 // 循环读取
 while (( len= fis.read(b))!=-1) {
    
    
 // 每次读取后,把数组变成字符串打印
 System.out.println(new String(b));
 }
 // 关闭资源
 fis.close();
 /*
 输出结果:
 ab
 cd
 ed
 最后错误数据`d`,是由于最后一次读取时,只读取一个字节`e`,数组中,
 上次读取的数据没有被完全替换,所以要通过`len` ,获取有效的字节
 */
 }
 @Test
 public void test04()throws IOException{
    
    
 // 使用文件名称创建流对象.
 FileInputStream fis = new FileInputStream("read.txt"); // 文件
中为 abcde
 // 定义变量,作为有效个数
 int len;
 // 定义字节数组,作为装字节数据的容器
 byte[] b = new byte[2];
 // 循环读取
 while (( len= fis.read(b))!=-1) {
    
    
 // 每次读取后,把数组的有效字节部分,变成字符串打印
 System.out.println(new String(b,0,len));// len 每次读取的
有效字节个数
 }
 // 关闭资源
 fis.close();
 /*
 输出结果:
 ab
 cd
 e
 */
 }
}
4.2.2 FileOutputStream 
java.io.FileOutputStream 类是文件输出流,用于将数据写出到文件。
• public FileOutputStream(File file):创建文件输出流,写出由指定的 File
对象表示的文件。
• public FileOutputStream(String name): 创建文件输出流,指定的名称为写
出文件。
• public FileOutputStream(File file, boolean append): 创建文件输出
流,指明是否在现有文件末尾追加内容。
举例:
package com.atguigu.fileio;
import org.junit.Test;
import java.io.FileOutputStream;
import java.io.IOException;
public class FOSWrite {
    
    
 //注意:应该使用 try-catch-finally 处理异常。这里出于方便阅读代码,使
用了 throws 的方式
 @Test
 public void test01() throws IOException {
    
    
 // 使用文件名称创建流对象
 FileOutputStream fos = new FileOutputStream("fos.txt");
 // 写出数据
 fos.write(97); // 写出第 1 个字节
 fos.write(98); // 写出第 2 个字节
 fos.write(99); // 写出第 3 个字节
 // 关闭资源
 fos.close();
 /* 输出结果:abc*/
 }
 @Test
 public void test02()throws IOException {
    
    
 // 使用文件名称创建流对象
 FileOutputStream fos = new FileOutputStream("fos.txt");
 // 字符串转换为字节数组
 byte[] b = "abcde".getBytes();
 // 写出从索引 2 开始,2 个字节。索引 2 是 c,两个字节,也就是 cd。
 fos.write(b,2,2);
 // 关闭资源
 fos.close();
 }
 //这段程序如果多运行几次,每次都会在原来文件末尾追加 abcde
 @Test
 public void test03()throws IOException {
    
    
 // 使用文件名称创建流对象
 FileOutputStream fos = new FileOutputStream("fos.txt",true);
 // 字符串转换为字节数组
 byte[] b = "abcde".getBytes();
 fos.write(b);
 // 关闭资源
 fos.close();
 }
 
 //使用 FileInputStream\FileOutputStream,实现对文件的复制
 @Test
 public void test05() {
    
    
 FileInputStream fis = null;
 FileOutputStream fos = null;
 try {
    
    
 //1. 造文件-造流
 //复制图片:成功
// fis = new FileInputStream(new File("pony.jpg"));
// fos = new FileOutputStream(new File("pony_copy1.jpg"));
 //复制文本文件:成功
 fis = new FileInputStream(new File("hello.txt"));
 fos = new FileOutputStream(new File("hello1.txt"));
 //2. 复制操作(读、写)
 byte[] buffer = new byte[1024];
 int len;//每次读入到 buffer 中字节的个数
 while ((len = fis.read(buffer)) != -1) {
    
    
 fos.write(buffer, 0, len);
// String str = new String(buffer,0,len);
// System.out.print(str);
 }
 System.out.println("复制成功");
 } catch (IOException e) {
    
    
 throw new RuntimeException(e);
 } finally {
    
    
 //3. 关闭资源
 try {
    
    
 if (fos != null)
 fos.close();
 } catch (IOException e) {
    
    
 throw new RuntimeException(e);
 }
 try {
    
    
 if (fis != null)
 fis.close();
 } catch (IOException e) {
    
    
 throw new RuntimeException(e);
 }
 }
 }
 }
  1. 处理流之一:缓冲流
    • 为了提高数据读写的速度,Java API 提供了带缓冲功能的流类:缓冲流。
    • 缓冲流要“套接”在相应的节点流之上,根据数据操作单位可以把缓冲流分为:
    – 字节缓冲流:BufferedInputStream,BufferedOutputStream
    – 字符缓冲流:BufferedReader,BufferedWriter

    • 缓冲流的基本原理:在创建流对象时,内部会创建一个缓冲区数组(缺省使用 8192
    个字节(8Kb)的缓冲区),通过缓冲区读写,减少系统 IO 次数,从而提高读写的效
    率。
    在这里插入图片描述
    5.1 构造器
    • public BufferedInputStream(InputStream in) :创建一个 新的字节型的缓
    冲输入流。
    • public BufferedOutputStream(OutputStream out): 创建一个新的字节型
    的缓冲输出流。
    代码举例:
// 创建字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream
("abc.jpg"));
// 创建字节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStr
eam("abc_copy.jpg"));public BufferedReader(Reader in) :创建一个 新的字符型的缓冲输入流。
• public BufferedWriter(Writer out): 创建一个新的字符型的缓冲输出流。
代码举例:
// 创建字符缓冲输入流
BufferedReader br = new BufferedReader(new FileReader("br.txt"));
// 创建字符缓冲输出流
BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt"));

5.2 效率测试
查询 API,缓冲流读写方法与基本的流是一致的,我们通过复制大文件
(375MB),测试它的效率。

//方法 1:使用 FileInputStream\FileOutputStream 实现非文本文件的复制
public void copyFileWithFileStream(String srcPath,String destPath){
    
    
 FileInputStream fis = null;
 FileOutputStream fos = null;
 try {
    
    
 //1. 造文件-造流
 fis = new FileInputStream(new File(srcPath));
 fos = new FileOutputStream(new File(destPath));
 //2. 复制操作(读、写)
 byte[] buffer = new byte[100];
 int len;//每次读入到 buffer 中字节的个数
 while ((len = fis.read(buffer)) != -1) {
    
    
 fos.write(buffer, 0, len);
 }
 System.out.println("复制成功");
 } catch (IOException e) {
    
    
 throw new RuntimeException(e);
 } finally {
    
    
 //3. 关闭资源
 try {
    
    
 if (fos != null)
 fos.close();
 } catch (IOException e) {
    
    
 throw new RuntimeException(e);
 }
 try {
    
    
 if (fis != null)
 fis.close();
 } catch (IOException e) {
    
    
 throw new RuntimeException(e);
 }
 }
}
@Test
public void test1(){
    
    
 String srcPath = "C:\\Users\\shkstart\\Desktop\\01-复习.mp4";
 String destPath = "C:\\Users\\shkstart\\Desktop\\01-复习 2.mp4";
 long start = System.currentTimeMillis();
 copyFileWithFileStream(srcPath,destPath);
 long end = System.currentTimeMillis();
 System.out.println("花费的时间为:" + (end - start));//7677 毫秒
}
//方法 2:使用 BufferedInputStream\BufferedOuputStream 实现非文本文件的复制
public void copyFileWithBufferedStream(String srcPath,String destPat
h){
    
    
 FileInputStream fis = null;
 FileOutputStream fos = null;
 BufferedInputStream bis = null;
 BufferedOutputStream bos = null;
 try {
    
    
 //1. 造文件
 File srcFile = new File(srcPath);
 File destFile = new File(destPath);
 //2. 造流
 fis = new FileInputStream(srcFile);
 fos = new FileOutputStream(destFile);
 bis = new BufferedInputStream(fis);
 bos = new BufferedOutputStream(fos);
 //3. 读写操作
 int len;
 byte[] buffer = new byte[100];
 while ((len = bis.read(buffer)) != -1) {
    
    
 bos.write(buffer, 0, len);
 }
 System.out.println("复制成功");
 } catch (IOException e) {
    
    
 e.printStackTrace();
 } finally {
    
    
 //4. 关闭资源(如果有多个流,我们需要先关闭外面的流,再关闭内部的)
 try {
    
    
 if (bos != null)
 bos.close();
 } catch (IOException e) {
    
    
 throw new RuntimeException(e);
 }
 try {
    
    
 if (bis != null)
 bis.close();
 } catch (IOException e) {
    
    
 throw new RuntimeException(e);
 }
 }
}
@Test
public void test2(){
    
    
 String srcPath = "C:\\Users\\shkstart\\Desktop\\01-复习.mp4";
 String destPath = "C:\\Users\\shkstart\\Desktop\\01-复习 2.mp4";
 long start = System.currentTimeMillis();
 copyFileWithBufferedStream(srcPath,destPath);
 long end = System.currentTimeMillis();
 System.out.println("花费的时间为:" + (end - start));//415 毫秒
}

5.3 字符缓冲流特有方法
字符缓冲流的基本方法与普通字符流调用方式一致,不再阐述,我们来看它们
具备的特有方法。
• BufferedReader:public String readLine(): 读一行文字。
• BufferedWriter:public void newLine(): 写一行行分隔符,由系统属性定义符
号。

public class BufferedIOLine {
    
    
 @Test
 public void testReadLine()throws IOException {
    
    
 // 创建流对象
 BufferedReader br = new BufferedReader(new FileReader("in.txt
"));
 // 定义字符串,保存读取的一行文字
 String line;
 // 循环读取,读取到最后返回 null
 while ((line = br.readLine())!=null) {
    
    
 System.out.println(line);
 }
 // 释放资源
 br.close();
 }
 @Test
 public void testNewLine()throws IOException{
    
    
 // 创建流对象
 BufferedWriter bw = new BufferedWriter(new FileWriter("out.tx
t"));
 // 写出数据
 bw.write("尚");
 // 写出换行
 bw.newLine();
 bw.write("硅");
 bw.newLine();
 bw.write("谷");
 bw.newLine();
 // 释放资源
 bw.close();
 }
}

说明:
11. 涉及到嵌套的多个流时,如果都显式关闭的话,需要先关闭外层的
流,再关闭内层的流
12. 其实在开发中,只需要关闭最外层的流即可,因为在关闭外层流
时,内层的流也会被关闭。

  1. 处理流之二:转换流
    6.1 问题引入
    引入情况 1:
    使用 FileReader 读取项目中的文本文件。由于 IDEA 设置中针对项目设置了
    UTF-8 编码,当读取 Windows 系统中创建的文本文件时,如果 Windows 系统
    默认的是 GBK 编码,则读入内存中会出现乱码。
package com.atguigu.transfer;
import java.io.FileReader;
import java.io.IOException;
public class Problem {
    
    
 public static void main(String[] args) throws IOException {
    
    
 FileReader fileReader = new FileReader("E:\\File_GBK.txt");
 int data;
 while ((data = fileReader.read()) != -1) {
    
    
 System.out.print((char)data);
 }
 fileReader.close();
 }
}

输出结果:
���
那么如何读取 GBK 编码的文件呢?
引入情况 2:
针对文本文件,现在使用一个字节流进行数据的读入,希望将数据显示在控制
台上。此时针对包含中文的文本数据,可能会出现乱码。

6.2 转换流的理解
作用:转换流是字节与字符间的桥梁!

在这里插入图片描述
具体来说:
在这里插入图片描述
6.3 InputStreamReader 与 OutputStreamWriter
• InputStreamReader
– 转换流 java.io.InputStreamReader,是 Reader 的子类,是从字节流
到字符流的桥梁。它读取字节,并使用指定的字符集将其解码为字符。它
的字符集可以由名称指定,也可以接受平台的默认字符集。
– 构造器
• InputStreamReader(InputStream in): 创建一个使用默认字
符集的字符流。
• InputStreamReader(InputStream in, String
charsetName): 创建一个指定字符集的字符流。
– 举例
//使用默认字符集
InputStreamReader isr1 = new InputStreamReader(new FileIn
putStream(“in.txt”));
//使用指定字符集
InputStreamReader isr2 = new InputStreamReader(new FileIn
putStream(“in.txt”) , “GBK”);
– 示例代码:

package com.atguigu.transfer;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
public class InputStreamReaderDemo {
    
    
 public static void main(String[] args) throws IOExcep
tion {
    
    
 // 定义文件路径,文件为 gbk 编码
 String fileName = "E:\\file_gbk.txt";
 //方式 1:
 // 创建流对象,默认 UTF8 编码
 InputStreamReader isr1 = new InputStreamReader(ne
w FileInputStream(fileName));
 // 定义变量,保存字符
 int charData;
 // 使用默认编码字符流读取,乱码
 while ((charData = isr1.read()) != -1) {
    
    
 System.out.print((char)charData); // ��Һ�
 }
 isr1.close();
 //方式 2:
 // 创建流对象,指定 GBK 编码
 InputStreamReader isr2 = new InputStreamReader(ne
w FileInputStream(fileName) , "GBK");
 // 使用指定编码字符流读取,正常解析
 while ((charData = isr2.read()) != -1) {
    
    
 System.out.print((char)charData);// 大家好
 }
 isr2.close();
 }
}

• OutputStreamWriter
– 转换流 java.io.OutputStreamWriter ,是 Writer 的子类,是从字符
流到字节流的桥梁。使用指定的字符集将字符编码为字节。它的字符集可
以由名称指定,也可以接受平台的默认字符集。
– 构造器
• OutputStreamWriter(OutputStream in): 创建一个使用默认
字符集的字符流。
• OutputStreamWriter(OutputStream in,String
charsetName): 创建一个指定字符集的字符流。
– 举例:
//使用默认字符集
OutputStreamWriter isr = new OutputStreamWriter(new FileO
utputStream(“out.txt”));
//使用指定的字符集
OutputStreamWriter isr2 = new OutputStreamWriter(new File
OutputStream(“out.txt”) , “GBK”);
– 示例代码:

package com.atguigu.transfer;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
public class OutputStreamWriterDemo {
    
    
 public static void main(String[] args) throws IOExcep
tion {
    
    
 // 定义文件路径
 String FileName = "E:\\out_utf8.txt";
 // 创建流对象,默认 UTF8 编码
 OutputStreamWriter osw = new OutputStreamWriter(n
ew FileOutputStream(FileName));
 // 写出数据
 osw.write("你好"); // 保存为 6 个字节
 osw.close();
 // 定义文件路径
 String FileName2 = "E:\\out_gbk.txt";
 // 创建流对象,指定 GBK 编码
 OutputStreamWriter osw2 = new OutputStreamWriter
(new 
 FileOutputStr
eam(FileName2),"GBK");
 // 写出数据
 osw2.write("你好");// 保存为 4 个字节
 osw2.close();
  1. 处理流之三/四:数据流、对象流
    7.1 数据流与对象流说明
    如果需要将内存中定义的变量(包括基本数据类型或引用数据类型)保存在文
    件中,那怎么办呢?
int age = 300;
char gender = '男';
int energy = 5000;
double price = 75.5;
boolean relive = true;
String name = "巫师";
Student stu = new Student("张三",23,89);

Java 提供了数据流和对象流来处理这些类型的数据:
• 数据流:DataOutputStream、DataInputStream
– DataOutputStream:允许应用程序将基本数据类型、String 类型的变量写
入输出流中
– DataInputStream:允许应用程序以与机器无关的方式从底层输入流中读取
基本数据类型、String 类型的变量。
• 对象流 DataInputStream 中的方法:

 byte readByte() short readShort()
 int readInt() long readLong()
 float readFloat() double readDouble()
 char readChar() boolean readBoolean()
 String readUTF() void readFully(byte[] b)

• 对象流 DataOutputStream 中的方法:将上述的方法的 read 改为相应的 write 即可。
• 数据流的弊端:只支持 Java 基本数据类型和字符串的读写,而不支持其它 Java 对象
的类型。而 ObjectOutputStream 和 ObjectInputStream 既支持 Java 基本数据类型的
数据读写,又支持 Java 对象的读写,所以重点介绍对象流 ObjectOutputStream 和
ObjectInputStream。
• 对象流:ObjectOutputStream、ObjectInputStream
– ObjectOutputStream:将 Java 基本数据类型和对象写入字节输出流中。
通过在流中使用文件可以实现 Java 各种基本数据类型的数据以及对象的持
久存储。
– ObjectInputStream:ObjectInputStream 对以前使用 ObjectOutputStream
写出的基本数据类型的数据和对象进行读入操作,保存在内存中。
说明:对象流的强大之处就是可以把 Java 中的对象写入到数据源中,
也能把对象从数据源中还原回来。
7.2 对象流 API
ObjectOutputStream 中的构造器:

public ObjectOutputStream(OutputStream out): 创建一个指定的
ObjectOutputStreamFileOutputStream fos = new FileOutputStream("game.dat");
ObjectOutputStream oos = new ObjectOutputStream(fos);
ObjectOutputStream 中的方法:
• public void writeBoolean(boolean val):写出一个 boolean 值。
• public void writeByte(int val):写出一个 8 位字节
• public void writeShort(int val):写出一个 16 位的 short 值
• public void writeChar(int val):写出一个 16 位的 char 值
• public void writeInt(int val):写出一个 32 位的 int 值
• public void writeLong(long val):写出一个 64 位的 long 值
• public void writeFloat(float val):写出一个 32 位的 float 值。
• public void writeDouble(double val):写出一个 64 位的 double 值
• public void writeUTF(String str):将表示长度信息的两个字节写入输出流,后跟字符串

s 中每个字符的 UTF-8 修改版表示形式。根据字符的值,将字符串 s 中每个字符转
换成一个字节、两个字节或三个字节的字节组。注意,将 String 作为基本数据写入
流中与将它作为 Object 写入流中明显不同。 如果 s 为 null,则抛出
NullPointerException。

public void writeObject(Object obj):写出一个 obj 对象
• public void close() :关闭此输出流并释放与此流相关联的任何系统资源
ObjectInputStream 中的构造器:
public ObjectInputStream(InputStream in): 创建一个指定的
ObjectInputStreamFileInputStream fis = new FileInputStream("game.dat");
ObjectInputStream ois = new ObjectInputStream(fis);
ObjectInputStream 中的方法:
• public boolean readBoolean():读取一个 boolean 值
• public byte readByte():读取一个 8 位的字节
• public short readShort():读取一个 16 位的 short 值
• public char readChar():读取一个 16 位的 char 值
• public int readInt():读取一个 32 位的 int 值
• public long readLong():读取一个 64 位的 long 值
• public float readFloat():读取一个 32 位的 float 值
• public double readDouble():读取一个 64 位的 double 值
• public String readUTF():读取 UTF-8 修改版格式的 Stringpublic void readObject(Object obj):读入一个 obj 对象
• public void close() :关闭此输入流并释放与此流相关联的任何系统资源

7.3 认识对象序列化机制
1、何为对象序列化机制?
对象序列化机制允许把内存中的 Java 对象转换成平台无关的二进制流,从而允
许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另
一个网络节点。//当其它程序获取了这种二进制流,就可以恢复成原来的 Java
对象。
• 序列化过程:用一个字节序列可以表示一个对象,该字节序列包含该对象的类型和
对象中存储的属性等信息。字节序列写出到文件之后,相当于文件中持久保存了一
个对象的信息。
• 反序列化过程:该字节序列还可以从文件中读取回来,重构对象,对它进行反序列
化。对象的数据、对象的类型和对象中存储的数据信息,都可以用来在内存中创建
对象。
在这里插入图片描述
2、序列化机制的重要性
序列化是 RMI(Remote Method Invoke、远程方法调用)过程的参数和返回值
都必须实现的机制,而 RMI 是 JavaEE 的基础。因此序列化机制是 JavaEE 平
台的基础。
序列化的好处,在于可将任何实现了 Serializable 接口的对象转化为字节数据,
使其在保存和传输时可被还原。
3、实现原理
• 序列化:用 ObjectOutputStream 类保存基本类型数据或对象的机制。方法为:
– public final void writeObject (Object obj) : 将指定的对象写
出。
• 反序列化:用 ObjectInputStream 类读取基本类型数据或对象的机制。方法为:
– public final Object readObject () : 读取一个对象

在这里插入图片描述
7.4 如何实现序列化机制
如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可序
列化的,为了让某个类是可序列化的,该类必须实现 java.io.Serializable
接口。Serializable 是一个标记接口,不实现此接口的类将不会使任何状态序
列化或反序列化,会抛出 NotSerializableException 。
• 如果对象的某个属性也是引用数据类型,那么如果该属性也要序列化的话,也要实现
Serializable 接口
• 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必
须注明是瞬态的,使用 transient 关键字修饰。
• 静态(static)变量的值不会序列化。因为静态变量的值不属于某个对象。


举例 1package com.atguigu.object;
import org.junit.Test;
import java.io.*;
public class ReadWriteDataOfAnyType {
    
    
 @Test
 public void save() throws IOException {
    
    
 String name = "巫师";
 int age = 300;
 char gender = '男';
 int energy = 5000;
 double price = 75.5;
 boolean relive = true;
 ObjectOutputStream oos = new ObjectOutputStream(new FileOutpu
tStream("game.dat"));
 oos.writeUTF(name);
 oos.writeInt(age);
 oos.writeChar(gender);
 oos.writeInt(energy);
 oos.writeDouble(price);
 oos.writeBoolean(relive);
 oos.close();
 }
 @Test
 public void reload()throws IOException{
    
    
 ObjectInputStream ois = new ObjectInputStream(new FileInputSt
ream("game.dat"));
 String name = ois.readUTF();
 int age = ois.readInt();
 char gender = ois.readChar();
 int energy = ois.readInt();
 double price = ois.readDouble();
 boolean relive = ois.readBoolean();
 System.out.println(name+"," + age + "," + gender + "," + energ
y + "," + price + "," + relive);
 ois.close();
 }
}
举例 2package com.atguigu.object;
import java.io.Serializable;
public class Employee implements Serializable {
    
    
 //static final long serialVersionUID = 23234234234L;
 public static String company; //static 修饰的类变量,不会被序列化
 public String name;
 public String address;
 public transient int age; // transient 瞬态修饰成员,不会被序列化
 public Employee(String name, String address, int age) {
    
    
 this.name = name;
 this.address = address;
 this.age = age;
 }
 public static String getCompany() {
    
    
 return company;
 }
 public static void setCompany(String company) {
    
    
 Employee.company = company;
 }
 public String getName() {
    
    
 return name;
 }
 public void setName(String name) {
    
    
 this.name = name;
 }
 public String getAddress() {
    
    
 return address;
 }
 public void setAddress(String address) {
    
    
 this.address = address;
 }
 public int getAge() {
    
    
 return age;
 }
 public void setAge(int age) {
    
    
 this.age = age;
 }
 @Override
 public String toString() {
    
    
 return "Employee{" +
 "name='" + name + '\'' +
 ", address='" + address + '\'' +
 ", age=" + age +
 ", company=" + company +
 '}';
 }
}
package com.atguigu.object;
import org.junit.Test;
import java.io.*;
public class ReadWriteObject {
    
    
 @Test
 public void save() throws IOException {
    
    
 Employee.setCompany("尚硅谷");
 Employee e = new Employee("小谷姐姐", "宏福苑", 23);
 // 创建序列化流对象
 ObjectOutputStream oos = new ObjectOutputStream(new FileOutpu
tStream("employee.dat"));
 // 写出对象
 oos.writeObject(e);
 // 释放资源
 oos.close();
 System.out.println("Serialized data is saved"); // 姓名,地址被序列化,年龄没有被序列化。
 }
 @Test
 public void reload() throws IOException, ClassNotFoundException {
    
    
 // 创建反序列化流
 FileInputStream fis = new FileInputStream("employee.dat");
 ObjectInputStream ois = new ObjectInputStream(fis);
 // 读取一个对象
 Employee e = (Employee) ois.readObject();
 // 释放资源
 ois.close();
 fis.close();
 System.out.println(e);
 }
}
举例 3:如果有多个对象需要序列化,则可以将对象放到集合中,再序列化集合对象即可。
package com.atguigu.object;
import org.junit.Test;
import java.io.*;
import java.util.ArrayList;
public class ReadWriteCollection {
    
    
 @Test
 public void save() throws IOException {
    
    
 ArrayList<Employee> list = new ArrayList<>();
 list.add(new Employee("张三", "宏福苑", 23));
 list.add(new Employee("李四", "白庙", 24));
 list.add(new Employee("王五", "平西府", 25));
 // 创建序列化流对象
 ObjectOutputStream oos = new ObjectOutputStream(new FileOutpu
tStream("employees.dat"));
 // 写出对象
 oos.writeObject(list);
 // 释放资源
 oos.close();
 }
 @Test
 public void reload() throws IOException, ClassNotFoundException {
    
    
 // 创建反序列化流
 FileInputStream fis = new FileInputStream("employees.dat");
 ObjectInputStream ois = new ObjectInputStream(fis);
 // 读取一个对象
 ArrayList<Employee> list = (ArrayList<Employee>) ois.readObje
ct();
 // 释放资源
 ois.close();
 fis.close();
 System.out.println(list);
 }
}
7.5 反序列化失败问题
问题 1:
对于 JVM 可以反序列化对象,它必须是能够找到 class 文件的类。如果找不到
该类的 class 文件,则抛出一个 ClassNotFoundException 异常。
问题 2:
当 JVM 反序列化对象时,能找到 class 文件,但是 class 文件在序列化对象之后
发生了修改,那么反序列化操作也会失败,抛出一个 InvalidClassException
异常。发生这个异常的原因如下:
• 该类的序列版本号与从流中读取的类描述符的版本号不匹配
• 该类包含未知数据类型
解决办法:
Serializable 接口给需要序列化的类,提供了一个序列版本号:
serialVersionUID 。凡是实现 Serializable 接口的类都应该有一个表示序列化
版本标识符的静态变量:
static final long serialVersionUID = 234242343243L; //它的值由程序员随
意指定即可。
• serialVersionUID 用来表明类的不同版本间的兼容性。简单来说,Java 的序列化机制
是通过在运行时判断类的 serialVersionUID 来验证版本一致性的。在进行反序列化
时,JVM 会把传来的字节流中的 serialVersionUID 与本地相应实体类的
serialVersionUID 进行比较,如果相同就认为是一致的,可以进行反序列化,否则就
会出现序列化版本不一致的异常(InvalidCastException)。
• 如果类没有显示定义这个静态常量,它的值是 Java 运行时环境根据类的内部细节自
动生成的。若类的实例变量做了修改,serialVersionUID 可能发生变化。因此,建议
显式声明。
• 如果声明了 serialVersionUID,即使在序列化完成之后修改了类导致类重新编译,则
原来的数据也能正常反序列化,只是新增的字段值是默认值而已





package com.atguigu.object;
import java.io.Serializable;
public class Employee implements Serializable {
    
    
 private static final long serialVersionUID = 1324234L; //增加 serialVersionUID
 
 //其它结构:略
}

7.6 面试题
面试题:谈谈你对 java.io.Serializable 接口的理解,我们知道它用于序列化,是
空方法接口,还有其它认识吗?
实现了 Serializable 接口的对象,可将它们转换成一系列字节,并可在以后完全恢
复回原来的样子。这一过程亦可通过网络进行。这意味着序列化机制能自动补偿操作
系统间的差异。换句话说,可以先在 Windows 机器上创建一个对象,对其序列化,然
后通过网络发给一台 Unix 机器,然后在那里准确无误地重新“装配”。不必关心数据
在不同机器上如何表示,也不必关心字节的顺序或者其他任何细节。

由于大部分作为参数的类如 String、Integer 等都实现了 java.io.Serializable
的接口,也可以利用多态的性质,作为参数使接口更灵活。

  1. 其他流的使用
    8.1 标准输入、输出流
    • System.in 和 System.out 分别代表了系统标准的输入和输出设备
    • 默认输入设备是:键盘,输出设备是:显示器
    • System.in 的类型是 InputStream
    • System.out 的类型是 PrintStream,其是 OutputStream 的子类 FilterOutputStream 的
    子类
    • 重定向:通过 System 类的 setIn,setOut 方法对默认设备进行改变。
    – public static void setIn(InputStream in)
    – public static void setOut(PrintStream out)
    举例:
从键盘输入字符串,要求将读取到的整行字符串转成大写输出。然后继续进行
输入操作,直至当输入“e”或者“exit”时,退出程序。
System.out.println("请输入信息(退出输入 e 或 exit):");
// 把"标准"输入流(键盘输入)这个字节流包装成字符流,再包装成缓冲流
BufferedReader br = new BufferedReader(new InputStreamReader(System.i
n));
String s = null;
try {
    
    
 while ((s = br.readLine()) != null) {
    
     // 读取用户输入的一行数据 -->阻塞程序
 if ("e".equalsIgnoreCase(s) || "exit".equalsIgnoreCase(s)) {
    
    
 System.out.println("安全退出!!");
 break;
 }
 // 将读取到的整行字符串转成大写输出
 System.out.println("-->:" + s.toUpperCase());
 System.out.println("继续输入信息");
 }
} catch (IOException e) {
    
    
 e.printStackTrace();
} finally {
    
    
 try {
    
    
 if (br != null) {
    
    
 br.close(); // 关闭过滤流时,会自动关闭它包装的底层节点流
 }
 } catch (IOException e) {
    
    
 e.printStackTrace();
 }
}
拓展:
System 类中有三个常量对象:System.out、System.in、System.err
查看 System 类中这三个常量对象的声明:
public final static InputStream in = null;
public final static PrintStream out = null;
public final static PrintStream err = null;
奇怪的是,
• 这三个常量对象有 final 声明,但是却初始化为 nullfinal 声明的常量一旦赋值就不能
修改,那么 null 不会空指针异常吗?
• 这三个常量对象为什么要小写?final 声明的常量按照命名规范不是应该大写吗?
• 这三个常量的对象有 set 方法?final 声明的常量不是不能修改值吗?set 方法是如何
修改它们的值的?
final 声明的常量,表示在 Java 的语法体系中它们的值是不能修改的,而这三个常量
对象的值是由 C/C++等系统函数进行初始化和修改值的,所以它们故意没有用大写,
也有 set 方法。
public static void setOut(PrintStream out) {
    
    
 checkIO();
 setOut0(out);
}
public static void setErr(PrintStream err) {
    
    
 checkIO();
 setErr0(err);
}
public static void setIn(InputStream in) {
    
    
 checkIO();
 setIn0(in);
}
private static void checkIO() {
    
    
 SecurityManager sm = getSecurityManager();
 if (sm != null) {
    
    
 sm.checkPermission(new RuntimePermission("setIO"));
 }
}
private static native void setIn0(InputStream in);
private static native void setOut0(PrintStream out);
private static native void setErr0(PrintStream err);

8.2 打印流
• 实现将基本数据类型的数据格式转化为字符串输出。
• 打印流:PrintStream 和 PrintWriter
– 提供了一系列重载的 print()和 println()方法,用于多种数据类型的输出

在这里插入图片描述
在这里插入图片描述
– PrintStream 和 PrintWriter 的输出不会抛出 IOException 异常
– PrintStream 和 PrintWriter 有自动 flush 功能
– PrintStream 打印的所有字符都使用平台的默认字符编码转换为字节。在需
要写入字符而不是写入字节的情况下,应该使用 PrintWriter 类。
– System.out 返回的是 PrintStream 的实例
• 构造器
– PrintStream(File file) :创建具有指定文件且不带自动行刷新的新打印流。
– PrintStream(File file, String csn):创建具有指定文件名称和字符集且不带自
动行刷新的新打印流。
– PrintStream(OutputStream out) :创建新的打印流。
– PrintStream(OutputStream out, boolean autoFlush):创建新的打印流。
autoFlush 如果为 true,则每当写入 byte 数组、调用其中一个 println 方
法或写入换行符或字节 (‘\n’) 时都会刷新输出缓冲区。
– PrintStream(OutputStream out, boolean autoFlush, String encoding) :创建
新的打印流。
– PrintStream(String fileName):创建具有指定文件名称且不带自动行刷新的
新打印流。
– PrintStream(String fileName, String csn) :创建具有指定文件名称和字符集
且不带自动行刷新的新打印流。
• 代码举例 1

package com.atguigu.systemio;
import java.io.FileNotFoundException;
import java.io.PrintStream;
public class TestPrintStream {
    
    
 public static void main(String[] args) throws FileNotFoundExcepti
on {
    
    
 PrintStream ps = new PrintStream("io.txt");
 ps.println("hello");
 ps.println(1);
 ps.println(1.5);
 ps.close();
 }
}
• 代码举例 2
PrintStream ps = null;
try {
    
    
 FileOutputStream fos = new FileOutputStream(new File("D:\\IO\\tex
t.txt"));
 // 创建打印输出流,设置为自动刷新模式(写入换行符或字节 '\n' 时都会刷新输出缓冲区)
 ps = new PrintStream(fos, true);
 if (ps != null) {
    
    // 把标准输出流(控制台输出)改成文件
 System.setOut(ps);
 }
 for (int i = 0; i <= 255; i++) {
    
     // 输出 ASCII 字符
 System.out.print((char) i);
 if (i % 50 == 0) {
    
     // 每 50 个数据一行
 System.out.println(); // 换行
 }
 }
} catch (FileNotFoundException e) {
    
    
 e.printStackTrace();
} finally {
    
    
 if (ps != null) {
    
    
 ps.close();
 }
}
• 代码举例 3:自定义一个日志工具
/*
日志工具
*/
public class Logger {
    
    
 /*
 记录日志的方法。
 */
 public static void log(String msg) {
    
    
 try {
    
    
 // 指向一个日志文件
 PrintStream out = new PrintStream(new FileOutputStream("lo
g.txt", true));
 // 改变输出方向
 System.setOut(out);
 // 日期当前时间
 Date nowTime = new Date();
 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd H
H:mm:ss SSS");
 String strTime = sdf.format(nowTime);
 System.out.println(strTime + ": " + msg);
 } catch (FileNotFoundException e) {
    
    
 e.printStackTrace();
 }
 }
}
public class LogTest {
    
    
 public static void main(String[] args) {
    
    
 //测试工具类是否好用
 Logger.log("调用了 System 类的 gc()方法,建议启动垃圾回收");
 Logger.log("调用了 TeamView 的 addMember()方法");
 Logger.log("用户尝试进行登录,验证失败");
 }
}

8.3 Scanner 类
构造方法
• Scanner(File source) :构造一个新的 Scanner,它生成的值是从指定文件扫描的。
• Scanner(File source, String charsetName) :构造一个新的 Scanner,它生成的值是从
指定文件扫描的。
• Scanner(InputStream source) :构造一个新的 Scanner,它生成的值是从指定的输入
流扫描的。
• Scanner(InputStream source, String charsetName) :构造一个新的 Scanner,它生成
的值是从指定的输入流扫描的。
常用方法:
• boolean hasNextXxx(): 如果通过使用 nextXxx()方法,此扫描器输入信息中的下一个
标记可以解释为默认基数中的一个 Xxx 值,则返回 true。
• Xxx nextXxx(): 将输入信息的下一个标记扫描为一个 Xxx

package com.atguigu.systemio;
import org.junit.Test;
import java.io.*;
import java.util.Scanner;
public class TestScanner {
    
    
 @Test
 public void test01() throws IOException {
    
    
 Scanner input = new Scanner(System.in);
 PrintStream ps = new PrintStream("1.txt");
 while(true){
    
    
 System.out.print("请输入一个单词:");
 String str = input.nextLine();
 if("stop".equals(str)){
    
    
 break;
 }
 ps.println(str);
 }
 input.close();
 ps.close();
 }
 
 @Test
 public void test2() throws IOException {
    
    
 Scanner input = new Scanner(new FileInputStream("1.txt"));
 while(input.hasNextLine()){
    
    
 String str = input.nextLine();
 System.out.println(str);
 }
 input.close();
 }
}
  1. apache-common 包的使用
    9.1 介绍
    IO 技术开发中,代码量很大,而且代码的重复率较高,为此 Apache 软件基金
    会,开发了 IO 技术的工具类 commonsIO,大大简化了 IO 开发。
    Apahce 软件基金会属于第三方,(Oracle 公司第一方,我们自己第二方,其他
    都是第三方)我们要使用第三方开发好的工具,需要添加 jar 包。

9.2 导包及举例
• 在导入 commons-io-2.5.jar 包之后,内部的 API 都可以使用。
在这里插入图片描述
• IOUtils 类的使用

  • 静态方法:IOUtils.copy(InputStream in,OutputStream out)传递字节流,实
    现文件复制。
  • 静态方法:IOUtils.closeQuietly(任意流对象)悄悄的释放资源,自动处理 clos
    e()方法抛出的异常
public class Test01 {
    
    
 public static void main(String[] args)throws Exception {
    
    
 //- 静态方法:IOUtils.copy(InputStream in,OutputStream out)传
递字节流,实现文件复制。
 IOUtils.copy(new FileInputStream("E:\\Idea\\io\\1.jpg"),new F
ileOutputStream("E:\\Idea\\io\\file\\柳岩.jpg"));
 //- 静态方法:IOUtils.closeQuietly(任意流对象)悄悄的释放资源,自动处理 close()方法抛出的异常。
 /* FileWriter fw = null;
 try {
 fw = new FileWriter("day21\\io\\writer.txt");
 fw.write("hahah");
 } catch (IOException e) {
 e.printStackTrace();
 }finally {
 IOUtils.closeQuietly(fw);
 }*/
 }
}

• FileUtils 类的使用

  • 静态方法:void copyDirectoryToDirectory(File src,File dest):整个目
    录的复制,自动进行递归遍历
    参数:
    src:要复制的文件夹路径
    dest:要将文件夹粘贴到哪里去

  • 静态方法:void writeStringToFile(File file,String content):将内容 co
    ntent 写入到 file 中

  • 静态方法:String readFileToString(File file):读取文件内容,并返回一个
    String

  • 静态方法:void copyFile(File srcFile,File destFile):文件复制

public class Test02 {
    
    
 public static void main(String[] args) {
    
    
 try {
    
    
 //- 静态方法:void copyDirectoryToDirectory(File src,File d
est);
 FileUtils.copyDirectoryToDirectory(new File("E:\\Idea\\io
\\aa"),new File("E:\\Idea\\io\\file"));
 //- 静态方法:writeStringToFile(File file,String str)
 FileUtils.writeStringToFile(new File("day21\\io\\commons.
txt"),"柳岩你好");
 //- 静态方法:String readFileToString(File file)
 String s = FileUtils.readFileToString(new File("day21\\io\
\commons.txt"));
 System.out.println(s);
 //- 静态方法:void copyFile(File srcFile,File destFile)
 FileUtils.copyFile(new File("io\\yangm.png"),new File("io
\\yangm2.png"));
 System.out.println("复制成功");
 } catch (IOException e) {
    
    
 e.printStackTrace();
 }
 }
}

猜你喜欢

转载自blog.csdn.net/weixin_45817985/article/details/131376558
今日推荐