内存节点流
ByteArrayInputStream是从内存的字节数组中读取数据
public ByteArrayInputStream(byte buf[]) {}
注意:不需要close数据源和抛出IOException,因为不涉及底层的系统调用
ByteArrayOutputStream是向内存字节数组中写数据,内部维护了一个数组
public ByteArrayOutputStream() { // 内部维护了一个可变的字节数组 // protected byte buf[]; this(32); }
内存节点流代码示例
ByteArrayInputStream bis = new ByteArrayInputStream("data".getBytes()); ByteArrayOutputStream bos = new ByteArrayOutputStream(); int len = 0; while ((len = bis.read()) != -1){ bos.write(len); } // 输出data System.out.println(new String(bos.toByteArray()));
应用场景
- 内存操作流一般在一些生成临时信息时会被使用,如果临时信息保存着文件中,代码执行完还要删除文件比较麻烦
- 结合对象流,可以实现对象和字节数组的互转
字符流
字符流封装了更加适合操作文本字符的方法
Reader用于读取文本字符
public abstract class Reader implements Readable, Closeable {}
核心方法
方法名 | 方法说明 |
int read() throws IOException | 从输入流中读取一个字符,读到文件末尾时返回-1 |
int read(char cbuf[]) throws IOException | 从输入流中读取字符到char数组中 |
Writer用于写出文本字符
public abstract class Writer implements Appendable, Closeable, Flushable {}
核心方法
方法名 | 方法说明 |
void write(int c) throws IOException | 写入单个字符到输出流中 |
void write(char[] cbuf) throws IOException | 写入字符数组到输出流中 |
void write(char[] cbuf, int off, int len) throws IOException | 写入字符数组的一部分,偏移量off开始,长度为len到输出流中 |
void write(String str) throws IOException | 直接写入字符串到输出流中(常用) |
void write(String str, int off, int len) throws IOException | 写入字符串的一部分,偏移量off开始,长度为len |
Writer append(char c) throws IOException | 追加字符到输出流中 |
文件节点流
字符流操作纯文本字符的文件是最合适的,主要有FileReader和FileWriter
FileReader主要是向磁盘文件中写出数据,常用构造方法如下
public FileReader(String fileName) throws FileNotFoundException{} public FileReader(File file) throws FileNotFoundException {}
注意:当读取的文件不存在时,会抛出FileNotFoundException,这点和
FileInputStream一致
- read()循环读取文件
FileReader fileReader = new FileReader("D:/三国/赵云.txt"); int b; while ((b = fileReader.read()) != -1) { System.out.println((char) b); }
- read(char[]) 读取文件
FileReader fileReader = new FileReader("D:/三国/赵云.txt"); int len; char[] data = new char[2]; while ((len = fileReader.read(data)) != -1) { System.out.println(new String(data, 0, len)); } // 两个字符两个字符依次读取
FileWriter构造方法如下,和FileOutStream构造方法类似,和FileOutputStream类似。
public FileWriter(String fileName) throws IOException {} public FileWriter(String fileName, boolean append) throws IOException {} public FileWriter(File file) throws IOException{} public FileWriter(File file, boolean append) throws IOException {}
常用的写数据进文件的方法
FileWriter fileWriter = new FileWriter("D:/三国/孙权.txt"); fileWriter.write(97); fileWriter.write('b'); fileWriter.write('C'); fileWriter.write("权"); fileWriter.append("力");
注意:
- 如果不执行close()或者flush()方法,数据只是保存到缓冲区,不会保存到文件。这点和与FileOutputStream不同,原因见 字节流和字符流的共同点章节
应用场景
纯文本文件的io操作,配合处理流一起实现。
内存节点流
字符流也有对应的内存节点流,常用的有StringWriter和CharArrayWriter
StringWriter是向内部的StringBuffer对象写数据。
// 定义 public class StringWriter extends Writer { private StringBuffer buf; public StringWriter() { buf = new StringBuffer(); lock = buf; } } // 应用 StringWriter sw = new StringWriter(); sw.write("hello"); StringBuffer buffer = sw.getBuffer(); // 输出hello System.out.println(buffer.toString());
CharArrayWriter是向内部的char数组写数据
// 定义 public class CharArrayWriter extends Writer { protected char buf[]; } // 应用 CharArrayWriter caw = new CharArrayWriter(); caw.write("hello"); char[] chars = caw.toCharArray(); for (char c : chars) { // 输出了h e l l o System.out.println(c); }
四种常用节点流的使用总结
字节流和字符流的共同点
注意到,OutputStream、Reader、Writer都实现了Flushable接口,Flushable接口有flush()方法
flush():强制刷新缓冲区的数据到目的地,刷新后流对象还可以继续使用
close(): 强制刷新缓冲区后关闭资源,关闭后流对象不可以继续使用
缓冲区:可以理解为内存区域,程序频繁操作资源(如文件)时,性能较低,因为读写内存较快,利用内存缓冲一部分数据,不要频繁的访问系统资源,是提高效率的一种方式
具体的流只要内部有维护了缓冲区,必须要close()或者flush(),不然不会真正的输出到文件中
处理流
上面的章节介绍了字节流和字符流的常用节点流,但是真正开发中都是使用更为强大的处理流
处理流是对节点流在功能上、性能上的增强
字节流的处理流的基类是FilterInputStream和FilterOutputStream
缓冲流(重点)
前面说了节点流,都是直接使用操作系统底层方法读取硬盘中的数据,缓冲流是处理流的一种实现,增强了节点流的性能,为了提高效率,缓冲流类在初始化对象的时候,内部有一个缓冲数组,一次性从底层流中读取数据到数组中,程序中执行read()或者read(byte[])的时候,就直接从内存数组中读取数据。
分类
字节缓冲流:BufferedInputStream , BufferedOutputStream
字符缓冲流:BufferedReader , BufferedWriter
字节缓冲流
可见构造方法传入的是节点流,是对节点流的装饰
// 内部默认8192 =8*1024 即8M的缓冲区 public BufferedInputStream(InputStream in) { // 8192 // 内部维护了下面这样的字节数组 // protected volatile byte buf[]; this(in, DEFAULT_BUFFER_SIZE); } public BufferedOutputStream(OutputStream out) { this(out, 8192); }
这里使用复制一部1G的电影来感受缓冲流的强大
- 使用基本的流读取数据(一次传输一个字节)
long start = System.currentTimeMillis(); FileInputStream fis = new FileInputStream("D:/三国/视频.mp4"); FileOutputStream fos = new FileOutputStream("D:/三国/拷贝.mp4"); int data; while ((data = fis.read()) != -1) { fos.write(data); } log.info("拷贝电影耗时:{}ms", System.currentTimeMillis() - start); // 五分钟还没拷好,关闭程序了...
- 使用基本的流读取数据(一次传输一个8M的字节数组)
long start = System.currentTimeMillis(); FileInputStream fis = new FileInputStream("D:/三国/视频.mp4"); FileOutputStream fos = new FileOutputStream("D:/三国/拷贝.mp4"); int len; byte[] data = new byte[1024 * 1024 * 1024]; while ((len = fis.read(data)) != -1) { fos.write(data, 0, len); } log.info("拷贝电影耗时:{}ms", System.currentTimeMillis() - start); // 拷贝电影耗时:4651ms
- 使用缓冲流读取数据(一次传输一个字节)
long start = System.currentTimeMillis(); BufferedInputStream fis = new BufferedInputStream(new FileInputStream("D:/三国/视频.mp4")); BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream("D:/三国/拷贝.mp4")); int data; while ((data = fis.read()) != -1) { fos.write(data); } log.info("拷贝电影耗时:{}ms", System.currentTimeMillis() - start); // 拷贝电影耗时:39033ms
- 使用缓冲流读取数据(一次传输一个8M的字节数组)(最常使用)
long start = System.currentTimeMillis(); BufferedInputStream fis = new BufferedInputStream(new FileInputStream("D:/三国/视频.mp4")); BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream("D:/三国/拷贝.mp4")); int len; byte[] data = new byte[8 * 1024]; while ((len = fis.read(data)) != -1) { fos.write(data, 0, len); } log.info("拷贝电影耗时:{}ms", System.currentTimeMillis() - start); // 拷贝电影耗时:1946ms
由上述四个例子可以得出结论,缓冲流读取数据比普通流读取数据快很多!
注意:采用边读边写的方式,一次传输几兆的数据效率比较高,如果采用先把文件的数据都读入内存,在进行写出,这样读写的次数是较小,但是占用太大的内存空间,一次读太大的数据也严重影响效率!