Java—IO 流
- 🔍文件
- 创建文件
- 获取文件相关信息
- 目录相关操作
- 🔍IO 流
- 理解流与文件
- 流的分类
- FileInputStream
- FileOutputStream
- 文件拷贝
- FileReader
- FileWriter
- 节点流与处理流
- 类型
- BufferedReader
- BufferedWriter
- BufferedInputStream + BufferedOutputStream
- 对象处理流
- ObjectOutputStream
- ObjectInputStream
- 对象处理流注意事项
- 标准输入输出流
- 转换流
- InputStreamReader
- OutputStreamWriter
- PrintStream
- PrintWriter
🔍文件
文件, 计算机存储数据的一种方式
创建文件
下面列举了创建文件的 3 种方式, 包括
- File(String pathname), 指定路径名创建
- File(File parent, String child), 指定父抽象路径 + 子路径名创建
- File(String parent, String child), 指定父路径名 + 子路径名创建
public class FileCreate {public static void main(String[] args) {// create1();// create2();// create3();}// 创建方式 1, File(String pathname)public static void create1() {String pathName = "e:/test1.txt";File file = new File(pathName);try {boolean ret = file.createNewFile();if(ret) {System.out.println("文件创建成功");}} catch (IOException e) {e.printStackTrace();}}// 创建方式 2, File(File parent, String child)public static void create2() {String parentPath = "e:/";File parent = new File(parentPath);String child = "test2.txt";try {File file = new File(parent, child);boolean ret = file.createNewFile();if(ret) {System.out.println("文件创建成功");}} catch (IOException e) {e.printStackTrace();}}// 创建方式3, File(String parent, String child)public static void create3() {String parent = "e:/";String child = "test3.txt";try {File file = new File(parent, child);boolean ret = file.createNewFile();if(ret) {System.out.println("文件创建成功");}} catch (IOException e) {e.printStackTrace();}}}
获取文件相关信息
具体文章参考获取文件相关信息
file.getName()
, 获取文件名file.getAbsolutePath()
, 获取文件绝对路径file.getParent()
, 获取文件父级目录file.length()
, 获取文件大小(单位: 字节)file.exists()
, 判断文件是否存在file.isFile()
, 判断是否为一个文件file.isDirectory()
, 判断是否为一个目录
目录相关操作
具体文章参考目录相关操作
file.exists()
, 判断目录是否存在file.delete()
, 删除目录file.mkdir()
, 创建单级目录file.mkdirs()
, 创建多级目录
注意🍂
- 执行删除目录命令时, 如果目录中存在文件, 则无法直接删除目录
- 创建多级目录也可以应用于创建单级目录的情况
🔍IO 流
I
, Input
的缩写, 表示输入
O
, Output
的缩写, 表示输出
输入流, 数据从文件输入到程序(文件 → 程序)
输出流, 数据从程序输出到文件(程序 → 文件)
举个栗子🌰
你可以将程序看作是一个人, 将文件看作是水
将人喝水(水被输入到人的肚子中)的过程理解为输入流
人在喝水过程中被呛到了(水被重新洒到了杯子)的过程理解为输出流
理解流与文件
流与文件之间的关系类似于快递小哥与商品之间的关系
- 将用户理解为程序
- 将快递小哥送快递的过程理解为输入流 / 输出流
- 将快递驿站理解为文件, 驿站中的快递(物品)理解为文件中的数据
快递小哥将快递(文件中的数据)送至用户家(程序) → 输入流
快递小哥将用户需要寄送的快递送至快递驿站 → 输出流
流负责数据的输入与输出
流的分类
- 依据操作的对象划分为: (1) 字节流 (2) 字符流
- 依据数据的流向划分为: (1) 输入流 (2) 输出流
抽象基类 | 字节流 | 字符流 |
---|---|---|
输入流 | InputStream | Reader |
输出流 | OutputStream | Writer |
FileInputStream
使用 FileInputStream
中的 read()
方法, 一次只能读取一个字节的内容
使用 FileInputStream
中的 read(byte[] b)
方法, 一次可以读取指定字节大小的内容
public class ReadFile {public static void main(String[] args) {// readFile1();// readFile2();}// 一次读取一个字节public static void readFile1() {String filePath = "e:/hello.txt";int readSize = 0;InputStream inputStream = null;try {inputStream = new FileInputStream(filePath);// 从文件中一次读取一个字节的数据. 返回 -1, 表示读到文件末尾while((readSize = inputStream.read()) != -1) {System.out.print((char) readSize);}} catch (IOException e) {e.printStackTrace();} finally {try {inputStream.close();} catch (IOException e) {e.printStackTrace();}}}// 一次读取多个字节public static void readFile2() {String filePath = "e:/hello.txt";int readSize = 0;// 从文件中一次读取 8 个字节的数据. 返回 -1, 表示读到文件末尾byte[] buffer = new byte[8];InputStream inputStream = null;try {inputStream = new FileInputStream(filePath);while((readSize = inputStream.read(buffer)) != -1) {// 注意不能写成 System.out.print(new String(buffer, 0, buffer.length));// 这是因为后续的 byte[] buffer 中的值由于第一次读取后的内容已经被填充System.out.print(new String(buffer, 0, readSize));}} catch (IOException e) {e.printStackTrace();} finally {try {inputStream.close();} catch (IOException e) {e.printStackTrace();}}}}
FileOutputStream
使用 FileOutputStream
向指定的文件中写入内容时. 如果该文件不存在, 则会自动进行创建
使用 FileOutputStream
中的 write()
方法, 一次只能写入一个字节的内容
使用 FileOutputStream
中的 write(byte[] b)
方法, 一次可以写入指定字节大小的内容
FileOutputStream outputStream = new FileOutputStream(filePath)
再次执行程序时, 新写入的内容覆盖原有的内容
FileOutputStream outputStream = new FileOutputStream(filePath, true)
再次执行程序时, 新写入的内容会追加到原有内容的末尾
再次执行程序时, 指的是流被关闭后再次使用
public class WriteFile {public static void main(String[] args) {writeFile1();// writeFile2();}// 一次写入一个字节public static void writeFile1() {OutputStream outputStream = null;String filePath = "e:/test.txt";try {// 使用 FileOutputStream 向指定的文件中写入内容时// 如果该文件不存在, 则会自动进行创建outputStream = new FileOutputStream(filePath);// 当再次执行程序时, 新写入的内容覆盖原有的内容// outputStream = new FileOutputStream(filePath); // 当再次执行程序时, 新写入的内容会追加到原有内容的末尾 // outputStream = new FileOutputStream(filePath, true); outputStream.write('h');outputStream.write('e');outputStream.write('l');outputStream.write('l');outputStream.write('o');} catch (IOException e) {e.printStackTrace();} finally {try {outputStream.close();} catch (IOException e) {e.printStackTrace();}}}// 一次写入多个字节public static void writeFile2() {OutputStream outputStream = null;String filePath = "e:/test.txt";try {// 使用 FileOutputStream 向指定的文件中写入内容时// 如果该文件不存在, 则会自动进行创建outputStream = new FileOutputStream(filePath);String str = "hello world";outputStream.write(str.getBytes());} catch (IOException e) {e.printStackTrace();} finally {try {outputStream.close();} catch (IOException e) {e.printStackTrace();}}}}
文件拷贝
文件拷贝分为 2 步, 包括
- 读取源文件中的数据输入到程序(输入流)
- 从程序中输出数据到目标文件(输出流)
写入目标文件中的数据长度应该是读取到的长度, 不能是字节数组本身的长度
outputStream.write(buffer, 0, readSize)
注意🍂
拷贝文件时应每次读取部分数据时就写入目标文件, 不要一下子全部读取再写入目标文件
这是因为如果文件太大, 可能会导致执行过程崩溃
public class FileCopy {public static void main(String[] args) {copyFile();}// 文件拷贝// 1. 读取源文件的数据// 2. 将源文件的数据拷贝到目标文件路径public static void copyFile() {// srcFilePath 源文件路径(最好不要有中文)String srcFilePath = "D:/Csdn截图上传/Redis_01.png";// destFilePath 目标文件路径(最好不要有中文)String destFilePath = "E:/Redis2.png";InputStream inputStream = null;OutputStream outputStream = null;int readSize = 0;// 每次读取 1024 字节的数据byte[] buffer = new byte[1024];try {inputStream = new FileInputStream(srcFilePath);// 使用 FileOutputStream 向指定的文件中写入内容时// 如果该文件不存在, 则会自动进行创建outputStream = new FileOutputStream(destFilePath);while((readSize = inputStream.read(buffer)) != -1) {// 将读取到的内容写入到目标文件// outputStream.write(buffer);outputStream.write(buffer, 0, readSize);}} catch (IOException e) {e.printStackTrace();} finally {// 资源释放try {if(inputStream != null) {inputStream.close();}if(outputStream != null) {outputStream.close();}} catch (IOException e) {e.printStackTrace();}}}
}
FileReader
使用 FileReader
中的 read()
方法, 一次只能读取一个字符的内容
使用 FileReader
中的 read(char cbuf[])
方法, 一次可以读取指定字符大小的内容
public class ReadFile {public static void main(String[] args) {// readFile1();// readFile2();}// 一次读取一个字符public static void readFile1() {Reader reader = null;String filePath = "e:/test.txt";int readSize = 0;try {reader = new FileReader(filePath);// 从文件中一次读取一个字符的数据. 返回 -1, 表示读到文件末尾while((readSize = reader.read()) != -1) {System.out.print((char) readSize);}} catch (IOException e) {e.printStackTrace();} finally {try {if(reader != null) {reader.close();}} catch (IOException e) {e.printStackTrace();}}}// 一次读取多个字符public static void readFile2() {Reader reader = null;String filePath = "e:/test.txt";int readSize = 0;char[] buffer = new char[1024];try {reader = new FileReader(filePath);// 从文件中一次读取多个字符的数据. 返回 -1, 表示读到文件末尾while((readSize = reader.read(buffer)) != -1) {System.out.print(new String(buffer, 0, readSize));}} catch (IOException e) {e.printStackTrace();} finally {try {if(reader != null) {reader.close();}} catch (IOException e) {e.printStackTrace();}}}}
FileWriter
使用 FileWriter
向指定的文件中写入内容时. 如果该文件不存在, 则会自动进行创建
使用 FileWriter
中的 write()
方法, 一次只能写入一个字符的内容
使用 FileWriter
中的 write(char[] cbuf)
或 write(String str)
方法, 一次可以写入指定字符大小的内容
FileWriter writer= new FileWriter(filePath)
再次执行程序时, 新写入的内容覆盖原有的内容
FileWriter writer= new FileWriter(filePath, true)
再次执行程序时, 新写入的内容会追加到原有内容的末尾
再次执行程序时, 指的是流被关闭后再次使用
FileWriter 使用之后, 必须要关闭(close)或刷新(flush). 否则数据写入不到指定的文件中
public class WriteFile {public static void main(String[] args) {// writeFile1();// writeFile2();}public static void writeFile1() {Writer writer = null;String filePath = "e:/note.txt";try {writer = new FileWriter(filePath);writer.write('我');writer.write('亦');writer.write('无');writer.write('他');writer.write(',');writer.write('为');writer.write('手');writer.write('属');writer.write('尔');} catch (IOException e) {e.printStackTrace();} finally {try {// 表示使用过该字符流if(writer != null) {writer.close();}} catch (IOException e) {e.printStackTrace();}}}public static void writeFile2() {Writer writer = null;String filePath = "e:/note.txt";try {writer = new FileWriter(filePath);String str = "我亦无他, 为手熟尔";writer.write(str);} catch (IOException e) {e.printStackTrace();} finally {try {// 表示使用过该字符流if(writer != null) {writer.close();}} catch (IOException e) {e.printStackTrace();}}}}
节点流与处理流
节点流, 针对指定的数据源读写数据. 例如 FileReader
, FileWriter
针对的数据源就是文件
处理流, 也叫包装流. 是连接在已存在的流(节点流或处理流)之上, 为程序提供更为强大的读写功能. 例如 BufferedReader
, BufferedWriter
翻译一下就是节点流只能做指定的工作. 而处理流不仅能做指定的工作, 还能在指定的基础之上做额外的工作
类型
分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 | 流类型 |
---|---|---|---|---|---|
抽象基类 | InputStream | OutputStream | Reader | Writer | |
访问文件 | FileInputStream | FileOutputStream | FileReader | FileWriter | 节点流 |
访问数组 | ByteArrayInputStream | ByteArrayOutputStream | CharArrayReader | CharArrayWriter | 节点流 |
访问管道 | PipedInputStream | PipedOutputStream | PipedReader | PipedWriter | 节点流 |
访问字符串 | StringReader | StringWriter | 节点流 | ||
缓冲流 | BufferedInputStream | BufferedOutputStream | BufferedReader | BufferedWriter | 处理流 |
转换流 | InputStreamReader | OutputStreamWriter | 处理流 | ||
对象流 | ObjectInputStream | ObjectOutputStream | 处理流 | ||
抽象基类 | FilterInputStream | FilterOutputStream | FilterReader | FilterWriter | 处理流 |
打印流 | PrintStream | PrintWriter | 处理流 | ||
推回输入流 | PushbackInputStream | PushbackReader | 处理流 | ||
特殊流 | DataInputStream | DataOutputStream | 处理流 |
BufferedReader
public class BFReadFile {public static void main(String[] args) throws IOException {readFile();}public static void readFile() throws IOException {String filePath = "e:/test.txt";BufferedReader reader = new BufferedReader(new FileReader(filePath));;String line = "";while((line = reader.readLine()) != null) {System.out.println(line);}reader.close();}}
BufferedWriter
public class BFWriter {public static void main(String[] args) throws IOException {writeFile();}public static void writeFile() throws IOException {String filePath = "e:/write.txt";// 表示以追加方式写入// BufferedWriter writer = new BufferedWriter(new FileWriter(filePath, true));// 表示以覆盖方式写入BufferedWriter writer = new BufferedWriter(new FileWriter(filePath));writer.write("hello world");// writer.write('\n');writer.newLine(); // 插入一个和系统相关的换行符writer.write("hello world");// writer.write('\n');writer.newLine(); // 插入一个和系统相关的换行符writer.write("hello world");// writer.write('\n');writer.newLine(); // 插入一个和系统相关的换行符writer.close();}}
BufferedInputStream + BufferedOutputStream
利用 BufferedInputStream
+ BufferedOutputStream
实现文件拷贝
public class BufferedCopy {public static void main(String[] args) throws IOException {copyFile();}// 拷贝文件// 1. 读取源文件数据到程序(输入流)// 2. 将原文件数据从程序输入到目标文件(输出流)public static void copyFile() throws IOException {// 源文件路径String srcFilePath = "d:/活法.pdf";// 目标文件路径String descFilePath = "e:/稻盛和夫_活法.pdf";BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(srcFilePath));BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(descFilePath));int readSize = 0;byte[] buf = new byte[1024];// 1. 读取源文件数据到程序while((readSize = inputStream.read(buf)) != -1) {// 2. 将原文件数据从程序输入到目标文件(输出流)outputStream.write(buf, 0, readSize);// 刷新缓冲区outputStream.flush();}inputStream.close();outputStream.close();}}
对象处理流
对象处理流包括 ObjectInputStream
和 ObjectOutputStream
通常利用 ObjectInputStream
和 ObjectOutputStream
进行序列化与反序列化
对于序列化与反序列化的解释🍂
当我们保存数据时, 通常不会保存数据的类型
例如在 .txt
文件中保存写入 10 并保存, 此时并没有保存数据的类型. 此时我们无法确定 10 这个数据是整数还是字符串类型
在比如, 当我们保存一个浮点数 10.5 时, 虽然进行了保存, 但是无法确定这个浮点数的类型是 float
还是 double
类型
序列化的过程就是保存数据的类型 + 数据的值
反序列化的过程就是将保存数据的类型 + 数据的值
举个栗子🌰
// 示例代码
public class Student {private int id;private String name
}
定义一个学生类, 属性包括 id, name
如果只保存一个 id 的值 + name 的值. 例如 id = 10, name = “嘟嘟”. 此时我们并没有办法判断这些属性描述的具体对象. 可以是学生, 老师, 也可能是一只宠物
序列化的过程就是保存了数据的类型 + 数据的值, 也就是说将值所描述的对象一同进行保存
而反序列化的过程就是在恢复数据时, 恢复保存了的数据 + 数据的值
实现序列化的过程需要该类实现下列的任意一个接口
- Serializable
- Externalizable
通常选择实现 Serializable
接口, 因为这只是一个标记接口, 里面并不包含抽象方法
ObjectOutputStream
public class ObjOutput {public static void main(String[] args) throws IOException {writeFile();}public static void writeFile() throws IOException {String filePath = "e:/t1.txt";ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(filePath));outputStream.writeInt(100);outputStream.writeChar('a');outputStream.writeUTF("hello world");outputStream.writeObject(new Student(1, "Tom"));outputStream.close();System.out.println("序列化完成");}}
Student 类🍂
public class Student implements Serializable {private int id;private String name;public Student(int id, String name) {this.id = id;this.name = name;}@Overridepublic String toString() {return "Student{" +"id=" + id +", name='" + name + '\'' +'}';}
}
ObjectInputStream
public class ObjInput {public static void main(String[] args) throws IOException, ClassNotFoundException {readFile();}public static void readFile() throws IOException, ClassNotFoundException {String filePath = "e:/t1.txt";ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(filePath));System.out.println(inputStream.readInt());System.out.println(inputStream.readChar());System.out.println(inputStream.readUTF());System.out.println(inputStream.readObject());inputStream.close();System.out.println("反序列化完成");}}
对象处理流注意事项
- 读写顺序需要保持一致
- 序列化或反序列化的对象, 需要实现
Serializable
或Externalizable
接口 - 序列化的类中建议添加
SerialVersionUID
, 以便提高版本兼容性 - 序列化对象时, 默认将对象中的所有属性都进行序列化(被
static
或transient
修饰的成员不会序列化) - 序列化对象时, 要求对象中的属性也实现序列化的接口
- 序列化具备可继承性. 即某个类实现了序列化, 那么它的子类也默认实现了序列化
标准输入输出流
⭕System.in
标准输入 → 默认对应设备为键盘
System.in
编译类型为InputStream
System.in
运行类型为PrintStream
⭕System.out
标准输出 → 默认对应设备为显示器
System.out
编译类型为PrintStream
System.out
运行类型为PrintStream
转换流
转换流包括 InputStreamReader
和 OutputStreamReader
转换流通常用于解决乱码问题. 这是因为InputStreamReader
和 OutputStreamReader
的构造方法中都可以指定字符编码
InputStreamReader
是 Reader
的子类, 可以将 InputStream
(字节流) 包装为 Reader
(字符流)
OutputStreamReader
是 Writer
的子类, 可以将 OutputStream
(字节流) 包装为 Writer
(字符流)
InputStreamReader
public class InputReadFile {public static void main(String[] args) throws IOException {readFile();}public static void readFile() throws IOException {String filePath = "e:/copyFile.pdf";FileInputStream inputStream = new FileInputStream(filePath);// 将 FileInputStream 转换为 InputStreamReader// 指定编码格式为 UTF-8InputStreamReader reader = new InputStreamReader(inputStream, "gbk");// 将 InputStreamReader 转换为 BufferedReaderBufferedReader bfReader = new BufferedReader(reader);while(bfReader.readLine() != null) {System.out.println(bfReader.readLine());}bfReader.close();}}
OutputStreamWriter
public class OutputWriteFile {public static void main(String[] args) throws IOException {writeFile();}public static void writeFile() throws IOException {String filePath = "e:/test.txt";FileOutputStream outputStream = new FileOutputStream(filePath);// 设置编码格式// OutputStreamWriter writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8);OutputStreamWriter writer = new OutputStreamWriter(outputStream, "gbk");BufferedWriter bfWriter = new BufferedWriter(writer);bfWriter.write("hello, world");bfWriter.newLine();bfWriter.write("做到才能得到");bfWriter.close();}}
PrintStream
public class ExPrintStream {public static void main(String[] args) throws IOException {// print1();// print2();}// 未设置打印位置// 默认将打印结果输出到显示器public static void print1() {PrintStream stream = System.out;stream.println("hello world");stream.println("做到才能得到");stream.close();}// 设置打印位置public static void print2() throws IOException {String filePath = "e:/test.txt";System.setOut(new PrintStream(filePath));System.out.println("输出结果到指定位置");System.out.close();}}
PrintWriter
public class ExPrintWriter {public static void main(String[] args) throws IOException {// print1();print2();}// 为指定输出位置// 默认将打印结果输出到显示器public static void print1() {PrintWriter writer = new PrintWriter(System.out);writer.println("hello world");writer.println("hello myFriend");writer.close();}// 指定输出位置public static void print2() throws IOException {String filePath = "e:/test.txt";PrintWriter writer = new PrintWriter(new FileWriter(filePath));writer.println("hello world");writer.println("hello myFriend");writer.close();}}
🌸🌸🌸完结撒花🌸🌸🌸