Java IO 流
一、IO 流
1.1 前言
流就是将数据无结构化的传递,强调的是数据的传输过程。
流分为 输入流 (Input) 和 输出流(Output),所以简称为 I/O 流。
输入流 表示从一个源读取数据,输出流表示向另一个目标写数据。
Java 中 I/O 操作主要是指 使用 java.io
包下的内容,进行输入、输出操作。
输入流:从磁盘读到内存
输出流:从内存写到磁盘中
配合使用:
1.2 Java 中 I/O 流分类
Java I/O 分为字节流 和字符流
1.3 Java 中 I/O 流体系图
1.4 Java 中 I/O 常用类体系图
1.4 字节输入流 输出流
1.4.1 前言
传输过程中,传输数据的最基本单位是字节
FileInputStream 和 FileOutputStream 是文件操作常用的两种流,借助这两个流实现文件读取、文件输出、文件拷贝等功能。
把硬盘上的文件读取到应用程序中使用 FileInputStream。
把应用程序中文件内容输出到硬盘上使用 FileOutputStream。
注意:
- 流使用完毕一定要关闭,释放资源
- 如果输出流内容来源于输入流,要先关闭输出流后关输入流。
1.4.2 FileInputStream API 介绍
1.4.3 FileOutputStream API 介绍
1.4.4 字节输入流的使用
代码示例一:
package com.bjsxt.test;
import java.io.File;
import java.io.FileInputStream;
public class Test01 {
public static void main(String[] args) throws Exception {
// 创建文件对象
File file = new File("test.txt");
// 使用字节输入流读取文件
FileInputStream fileInputStream = new FileInputStream(file);
while (true) {
// 读取文件中的内容,内容为 -1,表示已经全部读取
int read = fileInputStream.read();
if (read == -1) {
break;
}
System.out.println(read);
}
fileInputStream.close();
}
}
代码示例二:
package com.bjsxt.test;
import java.io.File;
import java.io.FileInputStream;
public class Test02 {
public static void main(String[] args) throws Exception {
// 创建文件对象
File file = new File("test.txt");
// 使用字节输入流读取文件
FileInputStream fileInputStream = new FileInputStream(file);
// 读取文件中的内容
int read;
while ((read = fileInputStream.read()) != -1) {
System.out.println(read);
}
fileInputStream.close();
}
}
1.4.5 字节输出流的使用
package com.bjsxt.test;
import java.io.File;
import java.io.FileOutputStream;
public class test03 {
public static void main(String[] args) throws Exception {
// 创建文件对象
File file = new File("test_01.txt");
// 创建字节输出流
FileOutputStream fileOutputStream = new FileOutputStream(file);
// 向文件中输出
fileOutputStream.write("abc".getBytes());
fileOutputStream.close();
}
}
1.5 使用字节流复制文件
1.5.1 实现思路
- 使用 FileInputStream 读取 文件
- 使用 FileOutputStream ,指定输出文件
- 遍历 FileInputStream 读取的数据,遍历的同时将读取的数据输出
- 先关闭输出流,再关闭输入流
1.5.2 代码示例
package com.bjsxt.test;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
public class Test09 {
/*
* 文件复制|拷贝
* 1.将文件读取到程序中
* 2.将读取到的文件内容输出到指定的位置
* */
public static void main(String[] args) throws Exception {
//创建字节输入流
FileInputStream fileInputStream = new FileInputStream("test.jpg");
// 创建字节输出流,自动创建指定文件的内容
FileOutputStream fileOutputStream = new FileOutputStream("test_copy.jpg");
long l = System.currentTimeMillis();
// 读取文件中的内容
int read; // 每次读取一个字节
while ((read = fileInputStream.read()) != -1) {
// System.out.println(read);
fileOutputStream.write(read);
}
long l1 = System.currentTimeMillis();
System.out.println(l1 - l);
// 先关闭输出流,在关闭输入流
fileOutputStream.close();
fileInputStream.close();
}
}
每次读取 1024 字节或者 n * 1024字节
package com.bjsxt.test;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class Test10 {
/*
* 文件复制|拷贝
* 1.将文件读取到程序中
* 2.将读取到的文件内容输出到指定的位置
* */
public static void main(String[] args) throws Exception {
//创建字节输入流
FileInputStream fileInputStream = new FileInputStream("test.jpg");
// 创建字节输出流,自动创建指定文件的内容
FileOutputStream fileOutputStream = new FileOutputStream("test_copy.jpg");
long l = System.currentTimeMillis();
// fileInputStream.read(); // 每次读取一个字节
byte[] bytes = new byte[1024];
int read; // 每次读取的长度,将读取到的内容存储到数组中
while ((read = fileInputStream.read(bytes)) != -1) {
// System.out.println(read);
fileOutputStream.write(bytes);
}
long l1 = System.currentTimeMillis();
System.out.println(l1 - l);
// 先关闭输出流,在关闭输入流
fileOutputStream.close();
fileInputStream.close();
}
}
1.5 字符输入输出流
1.5.1 前言
传输过程中,传输数据最基本的单位是字符流
由于字符编码方式不同,有时候一个字符使用的字节数也不一样,比如 ASCLL 方法编码的字符,占一个字节;而 UTF-8 方式编码的字符,一个英文字符需要一个字节,一个中文需要三个字节。
字节数据是二进制形式的,要转成我们能识别的正常字符,需要选择正确的编码方式。
因为数据编码的不同,因而有了对字符进行高效操作的流对象,字符流本质其实就是基于字节流读取时,去查了指定的码表,而字节流直接读取数据可能会有乱慢的问题。
1.5.2 BufferedReader API
1.5.3 BufferedWriter API
1.5.4 字符输入流
package com.bjsxt.test;
import java.io.FileOutputStream;
import java.io.FileWriter;
public class Test12 {
public static void main(String[] args) throws Exception {
/*
* 字符输出流:
* 向指定的位置创建一个文件
* 向改文件中输入内容
*
* */
//创建文件字符输出流
/*
* 参数2: 默认为false, 向文件中写数据时, 覆盖之前的数据
* true, 在之前数据后追加
* */
FileWriter fw = new FileWriter("d:/axx.txt", true);
//向文件中输入内容
// fw.write("你好吗. 小老弟!"); //将全部的内容写出
/*
* 参数1: 内容
* 参数2: 偏移量, 从哪个脚标开始写
* 参数3: 写几个
* */
fw.write("你好吗. 小老弟!", 0 ,2);
//资源释放, 关闭流
fw.close();
//创建字节输出流
/*
* 参数2: 默认为false, 向文件中写数据时, 覆盖之前的数据
* true, 在之前数据后追加
* */
FileOutputStream fos = new FileOutputStream("d:/aaa.txt", true);
fos.write("abc".getBytes(), 0, 2);
fos.close();
}
}
1.5.5 字符输出流
public static void main(String[] args) throws Exception {
/*
* 参数1: 输出文件对象
* 参数2: true: 追加
* false: 覆盖原有内容
* */
FileWriter fw = new FileWriter(new File("d:/io/aa.txt"),true);
//可以写字符串
fw.write("你好吗");
fw.close();
FileOutputStream fos = new FileOutputStream(new File("d:/io/abc.txt"));
//只能写字节
fos.write("你好吗".getBytes());
fos.close();
}
总结:
字符流的底层还是字节流,进行了封装转换。
字节流可以完成所有类型文件的复制(文本、音频、视频、图片等)
字符流只可以完成文本文件的复制(txt、Java),字符流一般用来处理中文的文本文件。
1.6 try - with - source
1.6.1 前言
try - with - source 是Java7 引入的。主要是为了解决因为忘记关闭资源而导致的性能问题和调用关闭方式而导致的代码结构乱的问题。
1.6.2 语法
try(需要在finally关闭的资源对象定义,可以写多条代码){
}catch(){
}
在 try 后面多了个 括号。定义到 括号里面的对象会自动关闭资源。
1.6.3 代码使用
package com.bjsxt.test;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class Test13 {
public static void main(String[] args) {
/*
* try () 中的对象,不需要手动释放资源,自动释放
* */
try (
// 字符输入流
FileReader fileReader = new FileReader("a.txt");
// 字符输出流
FileWriter fileWriter = new FileWriter("a_copy.txt");
) {
char[] chars = new char[1024]; // 存储每次读取的内容
int length; // 每次读取的长度
while ((length = fileReader.read(chars)) != -1) {
// 写入文件中, 从 0 开始,将读取到的长度内容写出
fileWriter.write(chars, 0, length);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
1.7 缓冲流
1.7.1 前言
Java I/O 中 BufferedXXX相关的流统称缓冲流。其本质就是在输入输出时添加缓冲区,减少裁判 I/O 的次数,这样可以提高读写效率,同时也可以反复读取。
缓冲流称为上层流(搞笑流),当使用完成后关闭缓冲流,字节流或字符流也会随之关闭。
可以使用 flush() 刷新/清空 缓冲区内容,把内容输出到目的地。
当缓冲区满了以后会自动刷新。在代码中当关闭流时,底层自动调用 flush() 方法。
1.7.2 字节缓冲流
package com.bjsxt.test;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class Test14 {
public static void main(String[] args) {
try (
// 创建文件字节输入流
FileInputStream fileInputStream = new FileInputStream("1.vip");
// 创建字节输入缓冲流,创建一个缓冲区
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
// 创建文件字节输出流,自动创建文件
FileOutputStream fileOutputStream = new FileOutputStream("1_copy_1.mp4");
// 创建字节输出缓冲流,创建一个输出流缓冲区
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
) {
// 使用输入缓冲流读取
byte[] bytes = new byte[1024];
long l = System.currentTimeMillis();
int length;
while ((length = bufferedInputStream.read(bytes)) != -1) {
// 将内容写到输出缓冲区中
bufferedOutputStream.write(bytes, 0, length);
}
long l1 = System.currentTimeMillis();
System.out.println("spent time:" + (l1 - l));
// 资源被释放,自动清空输出缓冲区
} catch (Exception e) {
e.printStackTrace();
}
}
}
1.7.3 原理图
只要关闭高层流即可,底层流不用手动关闭;因为高层的关闭方法就是把底层流关闭
1.7.4 字符缓冲流
package com.bjsxt.test;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
public class Test15 {
public static void main(String[] args) {
try (
// 创建字符输入流
FileReader fileReader = new FileReader("a.txt");
// 创建字节输入流缓冲
BufferedReader bufferedReader = new BufferedReader(fileReader);
// 创建字符输出流
FileWriter fileWriter = new FileWriter("a_copy_1.txt");
// 创建字符输出缓冲流
BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
) {
// 将文件中的内容读取到字符输入缓冲流中
char[] chars = new char[1024];
int length;
while ((length = bufferedReader.read(chars)) != -1) {
// 将读取的内容写到字符输出缓冲区
bufferedWriter.write(chars);
}
// bufferedWriter.flush(); 清楚输出缓冲流
// 关闭资源,自动清楚缓冲流
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.bjsxt.test;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
public class Test16 {
public static void main(String[] args) {
try (
// 创建字符输入流
FileReader fileReader = new FileReader("test.txt");
// 创建字节输入流缓冲
BufferedReader bufferedReader = new BufferedReader(fileReader);
// 创建字符输出流
FileWriter fileWriter = new FileWriter("c_copy_1.txt");
// 创建字符输出缓冲流
BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
) {
// 将文件中的内容读取到字符输入缓冲流中
String s;
while ((s = bufferedReader.readLine()) != null) {
//System.out.println(s);
// 将每次读取到的字符串,写到字符输出流缓冲区
bufferedWriter.write(s);
bufferedWriter.newLine(); // 换行
}
// String s = bufferedReader.readLine(); // 每次读取一行
// bufferedWriter.flush(); 清楚输出缓冲流
// 关闭资源,自动清楚缓冲流
} catch (Exception e) {
e.printStackTrace();
}
}
}
1.7.5 缓冲流文件复制
package com.bjsxt.test;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class Test17 {
public static void main(String[] args) {
try (
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("1.vip"));
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("1_copy.mp4"))
) {
// 将文件内容读取到输入缓冲流中
int length;
byte[] bytes = new byte[1024];
while ((length = bufferedInputStream.read(bytes)) != -1) {
bufferedOutputStream.write(bytes, 0, length);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
1.8 序列化与反序列化
1.8.1 前言
序列化:把对象转换为字节数组。在java中通过ObjectOutputStream序列化。序列化后须接收序列化的结果。所以在ObjectOutputSteam构造方法需要要一个OutputStream或其子类输出流对象,这个对象就是负责接收序列化结果的。
反序列化:把字节数组转换为对象。在java中通过ObjectInputStream反序列化,反序列化的结果在内存,需要通过输入流对象接收反序列化结果。所以在ObjectInputStream构造方法参数必须要一个InputStream对象,这个对象就负责接收反序列化结果的。
1.8.2 为什么要序列化
序列化后对象就是字节数组。变为字节数组后就可以把数组中内容输出到本地硬盘中,在后面学习的网络通信中,数据传输时也需要将对象转换为字节。
1.8.3 如何序列化
让需要序列化的类实现Serializable接口,实现了这个接口代表这个类允许被序列化。
通过Java中ObjectOutputStream把对象进行序列化, ObjectInputStream把对象反序列化。
1.8.4 属性值不参与序列化
如果类中包含一些私密属性,例如: 密码等。可以通过transient关键字,禁止该属性值被序列化。
public class Student implements Serializable {
private String name;
private transient int age;
//...
}
1.8.5 序列码
在实现了Serializable接口的类中。如果没有显示添加序列码会由JVM生成一个。
程序员也可以自己显示添加一个序列码,这个序列码和类中代码有关系,如果类中内容不变序列化和反序列化是没有影响的。
但是在企业级项目中,难免碰见需要修改类结构的情况。例如:添加一个新的属性。当我们修改了类结构后,类中自动生成的序列码就会改变。但是要求序列化和反序列化时序列码必须相同。而由于序列码的改变,所以在反序列化时会出错。为了防止这种问题,开发中只要实现了序列化都会添加序列码。
1.8.6 案例
package com.bjsxt.test;
import com.bjsxt.pojo.Phone;
import java.io.*;
public class Test18 {
public static void main(String[] args) throws Exception {
// 将对象存储到本身磁盘
Phone phone = new Phone("小米手机", 5799.00, "red");
/*
* 对象为什么进行序列化:
* 将对象存储到本地磁盘,通过网络传递,需要将对象变为哦字节数组,传输字节数组
* 序列化:将对象转化为字节数组 ObjectOutputStream()
* 反序列化:将字节数组转化为对象 ObjectInputStream()
* */
// 将 phone 对象序列,变为字节数组
/*ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("phone.txt"));
objectOutputStream.writeObject(phone); // 将对象序列化,将对象变为字节数组 */
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("phone.txt"));
Phone phone1 = (Phone) inputStream.readObject(); // 将字节数组转化为对象
System.out.println(phone1);
}
}