@[toc]
Java IO流原理
I/O 是 Input/Output 的缩写,I/O技术非常实用,用于处理设备之间的数据传输。如:读/写文件,网络通讯等。
Java程序中,对于数据的输入/输出操作以 流
的方式进行。
java.io包下提供了各种 “流” 类和接口,用以获取不同种类的数据,并通过标准的方法输入或输出数据。
输入input :读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中。
输出output :将程序(内存)中数据输出到磁盘、光盘等存储设备中,能实现永久存储。
流的分类
按操作数据单位
不同分为:字节流(8 bit),字符流(16 bit)
按数据流的流向
不同分为:输入流,输出流
按流的角色
不同分为:节点流,处理流
抽象基类 | 字节流 | 字符流 |
---|---|---|
输入流 | InputStream | Reader |
输出流 | OutputStream | Writer |
Java的IO流共涉及40多个类,看着多但实际上非常规则,都是从上面4个抽象基类派生出来的。
由这4个类派生出来的子类名称都是以其父类名作为子类名后缀。
如何选择:
对于文本
文件(.txt,.java,.c,.cpp),使用字符流
处理
对于非文本
文件(.jpg,.mp3,.avi,.doc,.ppt,...),使用字节流
处理
IO流体系
只写常用的几个
分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 |
---|---|---|---|---|
抽象基类 | InputStream | OutputStream | Reader | Writer |
文件流 (节点流) | FileInputStream | FileOutputStream | FileReader | FileWriter |
缓冲流 (处理流) | BufferedInputStream | BufferedOutputStream | BufferedReader | BufferedWriter |
转换流 (处理流) | InputStreamReader | OutputStreamWriter |
处理流是包裹在节点流外层的一种流,作用于已有流的基础之上。
节点流
先明确IO流的操作步骤,这就是模板,下面所有都是这样写的:
- 实例化File类的对象,指明要操作的文件
- 提供具体的流
- 数据的具体操作(读或写)
- 流的关闭
FileReader
分布来看:
// 1.实例化File类的对象,指明要操作的文件
File file = new File("hello.txt");
// 2.FileReader流的实例化
FileReader fr = new FileReader(file);
// 3.数据的读入
// read(char[] cbuf):返回每次读入cbuf数组中的字符个数。如果达到文件末尾,返回-1
char[] cbuf = new char[5];
int len;
while((len = fr.read(cbuf)) != -1){
// 方式一:循环遍历每次读到的cbuf
// for (int i = 0; i < len; i++) {
// System.out.print(cbuf[i]);
// }
// 方式二:把每次读到的cbuf变成字符串输出
String str = new String(cbuf,0,len);
System.out.print(str);
}
// 4.资源关闭
fr.close();
但此时并不完整,有编译时异常需要异常处理,加上 try-catch-finally,完整代码:
package IO.FileReaderWriterTest;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
public class FileReaderTest2 {
public static void main(String[] args) {
FileReader fr = null;
try {
// 1.File类的实例化,指明要操作的文件
File file = new File("hello.txt");
// 2.FileReader流的实例化
fr = new FileReader(file);
// 3.数据的读入
// read(char[] cbuf):返回每次读入cbuf数组中的字符个数。如果达到文件末尾,返回-1
char[] cbuf = new char[5];
int len;
while((len = fr.read(cbuf)) != -1){
// 方式一:循环遍历每次读到的cbuf
// for (int i = 0; i < len; i++) {
// System.out.print(cbuf[i]);
// }
// 方式二:把每次读到的cbuf变成字符串输出
String str = new String(cbuf,0,len);
System.out.print(str);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
// 4.资源关闭
assert fr != null;
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
补充一点:
try-catch-finally 快捷键:Ctrl+Alt+T
Ctrl+Alt+T 不仅仅能用于生成 try-catch-finally 结构,还能一键生成各种结构体,非常好用!
FileWriter
// 1.实例化File类的对象,指明写到的文件
File file = new File("hello1.txt");
// 2.提供FileWriter流的对象
FileWriter fw = new FileWriter(file);
注意:
这里的 FileWriter构造器 new FileWriter(file,append) 可以选参数
- 不写默认为false或写上false:意思是在原文件上直接覆盖新的数据。
- 写true:在文件后加新的数据,就不覆盖原有的了。
// 3.数据写入
fw.write("我有一个梦想\n");
fw.write("你也得有一个梦想\n");
// 4.资源关闭
fw.close();
最后别忘了 try-catch-finally!
完整代码:
package IO.FileReaderWriterTest;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
public class FileWriterTest {
public static void main(String[] args) {
FileWriter fw = null; // 追加模式写入
try {
// 1.实例化File类的对象,指明写到的文件
File file = new File("hello1.txt");
// 2.提供FileWriter流的对象
// FileWriter fw = new FileWriter(file); // append默认为false,直接覆盖
fw = new FileWriter(file,true);
// 3.数据写入
fw.write("我有一个梦想\n");
fw.write("你也得有一个梦想\n");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
// 4.资源关闭
assert fw != null;
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
最后补充一点 read() 及其重载方法的使用:
读取单个字符。
返回值:读取的字符,如果已到达流的结尾,则为-1
抛出:IOException–如果发生I/O错误
将字符读入数组。此方法将一直阻塞,直到有一些输入可用、发生I/O错误或到达流的结尾。
参数:cbuf–目标缓冲区
返回值:读取的字符数,如果已到达流的结尾,则为-1
抛出:IOException–如果发生I/O错误
FileReader和FileWriter一起使用实现文件的复制
package IO.FileReaderWriterTest;
import java.io.*;
public class TestAll {
public static void main(String[] args) {
FileReader fr = null;
FileWriter fw = null;
try {
// 1.造文件
File file1 = new File("hello.txt");
File file2 = new File("hello2.txt");
// 2.造流
fr = new FileReader(file1);
fw = new FileWriter(file2,true);
// 3.数据操作
char[] cbuf = new char[5];
int len;
while((len = fr.read(cbuf)) != -1){
// 方法一:
for (int i = 0; i < len; i++) {
fw.write(cbuf[i]);
}
//方法二: 每次写出0到len的字符
// fw.write(cbuf,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
// 4.关闭流
assert fr != null;
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
assert fw != null;
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
注意⭐细节:
在第3步数据操作时,字符流和字节流有所区别:
- 字符流:用 char[](char型数组)
- 字节流:用 byte[](byte型数组)
FileInputStream和FileOutputStream一起使用实现图片的复制
由于字节流和字符流使用方式几乎一模一样,所以直接写个总的了
package IO.FileInputOutputStreamTest;
import java.io.*;
public class TestAll {
public static void main(String[] args) {
FileInputStream fws = null;
FileOutputStream fos = null;
try {
File file1 = new File("a3141ac538443882b3aca9f5e462b31.jpg");
File file2 = new File("picture.jpg");
fws = new FileInputStream(file1);
fos = new FileOutputStream(file2);
byte[] b = new byte[10];
int len;
while((len = fws.read(b)) != -1){
fos.write(b,0,len);
}
System.out.println("复制成功!");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
assert fws != null;
fws.close();
assert fos != null;
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
处理流
就是“套接”在已有流的基础上,进一步加工,实现更多功能。
缓冲流
缓冲流是处理流,是包裹在节点流上的流。
具体做法:
造流的时候多加一步。造完节点流后,造缓冲流即可。
之后就是按部就班的操作数据和关闭资源。
用的类有:
- BufferedInputStream:字节缓冲输入流
- BufferedOutputStream:字节缓冲输出流
- BufferedReader:字符缓冲输入流
- BufferedWriter:字符缓冲输出流
作用: 提高流的读取、写入速度。
能提高读写速度的原因: 内部提供了一个缓冲区。
缓冲流(字节型)实现非文本文件的复制(具体步骤)
1、造文件
File srcFile = new File("D:\\Java\\StudyPlus\\test.txt");
File destFile = new File("D:\\Java\\StudyPlus\\test2.txt");
2、造流 —— 先造里层的流,后造外层的流。就像穿衣服:先穿里层,后穿外层。
2.1、造节点流
FileInputStream fis = new FileInputStream(srcFile);
FileOutputStream fos = new FileOutputStream(destFile);
2.2、造缓冲流
BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos);
3、数据操作:读取,写入
byte[] b = new byte[10];
int len;
while((len = bis.read(b)) != -1){
bos.write(b,0,len);
}
4、关资源
要求:先关闭外层的流,再关闭内层的流。即,先关外面后关里面。就像脱衣服:先脱外层,后脱里层。
偷懒: 关闭外层流的同时,内层流也会自动关闭。所以我们只需关外层即可,内层流的关闭可以省略。
bis.close();
bos.close();
总的代码:
package IO.Buffered;
import java.io.*;
public class BufferedInputOutputStream {
public static void main(String[] args) {
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
// 1.造文件
File srcFile = new File("D:\\Java\\StudyPlus\\a3141ac538443882b3aca9f5e462b31.jpg");
File destFile = new File("D:\\Java\\StudyPlus\\picture1.jpg");
// 2.造流 先造里层的流,后造外层的流。 穿衣服先穿里层,后穿外层。
// 2.1造节点流
FileInputStream fis = new FileInputStream(srcFile);
FileOutputStream fos = new FileOutputStream(destFile);
// 2.2造缓冲流(处理流)
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);
// 3.复制的细节:读取、写入
byte[] b = new byte[10];
int len;
while((len = bis.read(b)) != -1){
bos.write(b,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
// 4.关资源
// 要求:先关闭外层的流,再关闭内层的流。 先关外面后关里面。 脱衣服先脱外层,后脱里层。
assert bis != null;
bis.close();
assert bos != null;
bos.close();
// 说明:关闭外层流的同时,内层流也会自动关闭。所以我们只需关外层即可,内层流的关闭可以省略。
// fis.close();
// fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
缓冲流(字符型)实现文本文件的复制
package IO.Buffered;
import java.io.*;
public class BufferedReaderWriter {
public static void main(String[] args) {
BufferedReader br = null;
BufferedWriter bw = null;
try {
File srcFile = new File("D:\\Java\\StudyPlus\\test.txt");
File destFile = new File("D:\\Java\\StudyPlus\\test2.txt");
FileReader fr = new FileReader(srcFile);
FileWriter fw = new FileWriter(destFile);
br = new BufferedReader(fr);
bw = new BufferedWriter(fw);
// 方法一:使用char型数组
// char[] cbuf = new char[1024];
// int len;
// while((len = br.read(cbuf)) != -1){
// bw.write(cbuf,0,len);
// }
// 方法二:使用String
String data;
while((data = br.readLine()) != null){
// 换行的方式一: 手动添加 \n 换行
// bw.write(data + "\n"); // data中不包含换行符
// 换行的方式二: 调用方法
bw.write(data);
bw.newLine(); // 提供换行的操作
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
assert br != null;
br.close();
assert bw != null;
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
转换流
转换流: 属于字符流
InputStreamReader
:将一个字节的输入流
转换为字符的输入流
OutputStreamWriter
:将一个字符的输出流
转换为字节的输出流
作用: 提供字节流与字符流之间的转换
应用: 能改变文件的编码方式。如:utf-8 与 gbk间的相互转换
解码:字节、字节数组 ——→ 字符串、字符数组
编码:字符串、字符数组 ——→ 字节、字节数组
好记的口诀:
1、转化顺序和方向:
- 先是
InputStreamReader
:从左到右看 InputStream ——→ Reader 字节转字符 - 然后
OutputStreamWriter
:从右到左看 Writer ——→ OutputStream 字符转字节
2、用的流:
InputStreamReader
包FileInputStream
OutputStreamWriter
包FileOutputStream
注意:
因为要实现编码格式的转换,所以在 new转换流时,在构造器里写转换前后的参数。
InputStreamReader isr = new InputStreamReader(fis, StandardCharsets.UTF_8);
OutputStreamWriter osw = new OutputStreamWriter(fos,"gbk");
转换前是 utf-8,转换后为 gbk
还有一点:
InputStreamReader 构造器中默认
编码方式为 utf-8
转换流实现文件的读入和写出
package IO.转换流;
import java.io.*;
import java.nio.charset.StandardCharsets;
public class Test {
public static void main(String[] args) {
InputStreamReader isr = null;
OutputStreamWriter osw = null;
try {
FileInputStream fis = new FileInputStream("test.txt");
FileOutputStream fos = new FileOutputStream("test_gbk.txt");
isr = new InputStreamReader(fis, StandardCharsets.UTF_8);
osw = new OutputStreamWriter(fos,"gbk");
char[] cbuf = new char[20];
int len;
while((len = isr.read(cbuf)) != -1){
osw.write(cbuf,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
assert isr != null;
isr.close();
assert osw != null;
osw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
此时,新文件的编码方式就变了,变成 gbk了。