分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 |
抽象基类 | 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 |
后端实际业务中对文件操作无非两种情况。
1. 不关心文件内容,直接对文件进行迁移传输操作
2. 需要具体解析文件数据。
流的分类
流按照读写单位划分
- 字节流:按照字节方式读写,一次读一个字节,可以读写所有类型文件。一般结尾带有Stream的类都是字节流。
- 字符流:按照字符方式读写,一次读一个字符,只能读文本类型(音频、图片、视频不可读)。一般结尾带有Reader、Writer的类都是字符流。
流按照流的方向划分
- 输入流:外部文件流入到程序中就是输入流。一般带有Input、Reader的类都是输入流。
- 输出流:程序内部流出到外部就是输出流。一般带有Output、Writer的类都是输出流
简单分析
以下示例只是demo,没有那么全面,仅供参考
FileInputStream、FileOutputStream类
FileInputStream
//文件路径,相对/绝对路径都可
String filePath = "a.zip";
File file = new File(filePath);
//以下两种构造方法
InputStream is1 = new FileInputStream(file);
InputStream is2 = new FileInputStream(filePath);
InputStream is = new FileInputStream("A.txt");
//两种读取方式
int temp = 0;
//一次读一个字节,返回值temp代表读取字节的ASCII码值,若为-1表示读取完毕;不推荐使用,读取效率太慢了
while ((temp = is.read()) != -1) {
System.out.println((char) temp);
}
/**
* 代码中,上面已经读取完毕后,下面代码无法重新读取
* FileInputStream无法改变指针的位置,若是借助其它缓冲流到可以做到
*/
//一次最多读取1024个字节大小进入缓冲区,缓冲区大小可以自定义,会影响效率
byte[] buffer = new byte[1024];
int len = 0;
//len代表当前读取的字节长度
while ((len = is.read(buffer)) != -1) {
String str = new String(buffer, 0, len);
System.out.println(str);
}
is.close();
FileOutputStream
//文件路径,相对/绝对路径都可
String filePath = "safe/test/file.txt";
File file = new File(filePath);
/**
* 文件不存在,父目录存在时,FileOutputStream会自动创建文件
* 如该文件的父级目录也不存在则无法创建,会报异常
*/
File parentFile = file.getParentFile();
if(!parentFile.exists()) {
//创建多级目录
parentFile.mkdirs();
}
//多种构造方法
//默认直接覆盖原始文件
OutputStream os1 = new FileOutputStream(file);
OutputStream os2 = new FileOutputStream(filePath);
//在原文件基础上追加内容
OutputStream os3 = new FileOutputStream(file, true);
OutputStream os4 = new FileOutputStream(filePath, true);
OutputStream os = new FileOutputStream("file.txt");
//写入数据的三种方式
//字节数组写入流中
os.write("Hello World !!!".getBytes());
//一个字节的ASCII码值写入流中;不推荐使用,写入效率太慢了
os.write(97);
//从字节数组的下标1开始,写入3位至流中
byte[] bytes = "Hello World".getBytes();
os.write(bytes, 1, 3);
//强制将缓冲区的数据写入到文件中
os.flush();
//关闭流
os.close();
联合使用
//计时器
TimeInterval timer = DateUtil.timer();
InputStream is = new FileInputStream("a.zip");
OutputStream os = new FileOutputStream("b.zip");
/**
* 35MB大小文件进行测试
* 缓冲区为1kb,花费时间大约为250毫秒
* 缓冲区为8kb,花费时间大约为50毫秒
* 缓冲区为100kb,花费时间大约为25毫秒
* 缓冲区为1Mb,花费时间大约为45毫秒
* 由此可见,缓冲区并不是越大越好
*/
int len = 0;
byte[] buffer = new byte[1024 * 100];
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
os.flush();
os.close();
is.close();
//打印耗时
System.out.println("读写花费时间:" + timer.interval() + "毫秒");
FileReader、FileWriter类
字符流和字节流操作方法类似,这里不多做解释
FileReader
//文件路径,相对/绝对路径都可
String filePath = "A.txt";
File file = new File(filePath);
Reader reader1 = new FileReader(file);
Reader reader2 = new FileReader(filePath);
Reader reader = new FileReader("A.txt");
int temp = 0;
while ((temp = reader.read()) != -1) {
System.out.println((char) temp);
}
int len = 0;
char[] buffer = new char[1024];
while ((len = reader.read(buffer)) != -1) {
String str = new String(buffer, 0, len);
System.out.println(str);
}
reader.close();
FileWriter
String filePath = "file.txt";
File file = new File(filePath);
Writer writer1 = new FileWriter(file);
Writer writer2 = new FileWriter(filePath);
Writer writer3 = new FileWriter(file, true);
Writer writer4 = new FileWriter(filePath, true);
Writer writer = new FileWriter("file.txt");
//写入一个字符
writer.write('a');
//写入一个字符串
writer.write("abc");
//写入一个字符数组
writer.write("Hello".toCharArray());
//从下标1开始,长度为3,截取字符串写入
writer.write("hello world", 1, 3);
//和writer.write('a')一致
writer.append('a');
//和writer.write("abc")一致
writer.append("Hello");
//从下标1开始,长度为3,截取字符串写入
writer.append("Hello", 1, 3);
writer.flush();
writer.close();
联合使用
Reader reader = new FileReader("A.txt");
Writer writer = new FileWriter("B.txt");
int len = 0;
char[] buffer = new char[1024];
while ((len = reader.read(buffer)) != -1) {
writer.write(buffer, 0, len);
}
writer.flush();
writer.close();
reader.close();
ByteArrayInputStream、ByteArrayOutputStream类
简单来说看作是一个字节数组对象,起到缓冲作用
ByteArrayInputStream
/**
* 字节数组缓冲区,可重复读
*/
String str = "Hello world!!!";
ByteArrayInputStream bis = new ByteArrayInputStream(str.getBytes());
int temp = 0;
while ((temp = bis.read()) != -1) {
System.out.println((char) temp);
}
//参数无意义,主要是标记当前读取的位置,一般用不到。
//bis.mark(0);
/**
* 将光标移至标记位
* 此处就可以连续读两次
*/
bis.reset();
int len = 0;
byte[] buffer = new byte[1024];
while ((len = bis.read(buffer)) != -1) {
String result = new String(buffer, 0, len);
System.out.println(result);
}
ByteArrayOutputStream
OutputStream os = new FileOutputStream("file.txt");
/**
* 当你导出一个excel文件或者压缩文件时,前端需要你在响应头中返回文件大小应该怎么做
* 就可以用到ByteArrayOutputStream作为临时缓冲区,可以计算大小
*/
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bos.write("Hello world!!!".getBytes());
System.out.println(bos.size());
//将字节数组缓冲区写入输出流中
bos.writeTo(os);
os.close();
PipedInputStream、PipedOutputStream类
它们的作用是让多线程可以通过管道流实现线程间的通信。使用管道通信时,两者必须配套使用。
大致流程是:线程A向PipedOutputStream中写入数据,数据会自动发送至配套的PipedInputStream中,线程B就可以读取PipedInputStream中的数据,这样就实现了线程间的通信
public class Sender implements Runnable {
private PipedOutputStream os = new PipedOutputStream();
public PipedOutputStream getOutputStream() {
return os;
}
@Override
public void run() {
String str = "我给你发消息了";
byte[] bytes = str.getBytes();
try {
//写入数据
os.write(bytes, 0, bytes.length);
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class Receiver implements Runnable {
private PipedInputStream is = new PipedInputStream();
public PipedInputStream getInputStream() {
return is;
}
@Override
public void run() {
// 读取数据的方式和FileInputStream一致
byte[] buffer = new byte[1024];
int len = 0;
try {
if ((len = is.read(buffer)) != -1) {
String str = new String(buffer, 0, len);
System.out.println(str);
}
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void test1() {
//创建对象,不能写成Runnable sender = new Sender(),因为上转型对象不能调用子类独有方法
Sender sender = new Sender();
Receiver receiver = new Receiver();
PipedOutputStream os = sender.getOutputStream();
PipedInputStream is = receiver.getInputStream();
try {
//将输出和输入管道连接起来
os.connect(is);
} catch (IOException e) {
e.printStackTrace();
}
//启动线程
new Thread(sender).start();
try {
//睡一秒,确保先写后读
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(receiver).start();
}
BufferedInputStream、BufferedOutputStream类
BufferedInputStream
TimeInterval timer = DateUtil.timer();
InputStream is = new FileInputStream("a.zip");
/**
* 高级流,和低级流配合使用
* 缓冲区的作用,提高读取效率,可重复读
* 35MB文件
* 不使用缓冲流,一次读一个字节,花费25秒左右
* 使用缓冲流,一次读一个字节,花费700毫秒左右。可能是一次读一个字节原因,改变缓冲区大小并不会提升效率
* 缓冲区大小默认是8kb,可以自定义设置
*/
BufferedInputStream bis = new BufferedInputStream(is, 1024 * 100);
int temp = 0;
while ((temp = bis.read()) != -1) {
System.out.println((char) temp);
}
bis.close();
System.out.println(timer.interval());
TimeInterval timer = DateUtil.timer();
InputStream is = new FileInputStream("a.zip");
/**
* 高级流,和低级流配合使用
* 缓冲区的作用,提高读取效率
* 35MB文件,可能文件太小,最低10毫秒左右,差距不明显
* 不使用缓冲流;一次最多读字节数组1kb,花费时间40毫秒左右
* 不使用缓冲流;一次最多读字节数组100kb,花费时间10毫秒左右
* 使用缓冲流,默认大小8kb;一次最多读字节数组1kb,花费时间15毫秒左右
* 使用缓冲流,默认大小8kb;一次最多读字节数组100kb,花费时间10毫秒左右
* 使用缓冲流,100kb;一次最多读字节数组1kb,花费时间10毫秒左右
* 使用缓冲流,100kb;一次最多读字节数组100kb,花费时间10毫秒左右
* 缓冲区大小默认是8kb,可以自定义设置
*/
BufferedInputStream bis = new BufferedInputStream(is, 1024 * 100);
int len = 0;
byte[] buffer = new byte[1024 * 100];
while ((len = bis.read(buffer)) != -1) {
String str = new String(buffer, 0, len);
System.out.println(str);
}
System.out.println(timer.interval());
联合使用测试效率问题
/**
* 35MB文件,文件有点小,效率达到阈值,没法有很大差别
* 输入输出都不用缓冲流,一次最多读取字节1kb,花费时间250毫秒左右
* 输入输出都不用缓冲流,一次最多读取字节100kb,花费时间25毫秒左右
* 输入用缓冲流默认8kb,输出不用缓冲流,一次最多读取字节1kb,花费时间230毫秒左右
* 输入用缓冲流默认8kb,输出不用缓冲流,一次最多读取字节100kb,花费时间25毫秒左右
* 输入用缓冲流100kb,输出不用缓冲流,一次最多读取字节1kb,花费时间230毫秒左右
* 输入用缓冲流100kb,输出不用缓冲流,一次最多读取字节100kb,花费时间25毫秒左右
* 输入不用缓冲流,输出用缓冲流默认8kb,一次最多读取字节1kb,花费时间90毫秒左右
* 输入不用缓冲流,输出用缓冲流默认8kb,一次最多读取字节100kb,花费时间25毫秒左右
* 输入不用缓冲流,输出用缓冲流100kb,一次最多读取字节1kb,花费时间60毫秒左右
* 输入不用缓冲流,输出用缓冲流100kb,一次最多读取字节100kb,花费时间25毫秒左右
* 输入缓冲流8kb,输出缓冲流8kb,一次最多读取字节1kb,花费时间60毫秒左右
* 输入缓冲流8kb,输出缓冲流8kb,一次最多读取字节100kb,花费时间25毫秒左右
* 输入缓冲流100kb,输出缓冲流100kb,一次最多读取字节1kb,花费时间25毫秒左右
* 输入缓冲流100kb,输出缓冲流100kb,一次最多读取字节100kb,花费时间25毫秒左右
*
* 以上可见,无论用不用缓冲流,一次最多读取字节大小对效率有很大影响。也不是越大越好
*/
TimeInterval timer = DateUtil.timer();
InputStream is = new FileInputStream("a.zip");
BufferedInputStream bis = new BufferedInputStream(is, 1024 * 100);
OutputStream os = new FileOutputStream("b.zip");
BufferedOutputStream bos = new BufferedOutputStream(os, 1024 * 100);
int len = 0;
byte[] buffer = new byte[1024 * 100];
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
bos.close();
os.close();
bis.close();
System.out.println(timer.interval());
InputStreamReader类
/**
* 将字节流转成字符流
*/
InputStream is = new FileInputStream("A.txt");
InputStreamReader reader = new InputStreamReader(is);
int temp = 0;
while ((temp = reader.read()) != -1) {
System.out.println((char) temp);
}
reader.close();
is.close();