概述
Java.io 包几乎包含了所有操作输入、输出需要的类。所有这些流类代表了输入源和输出目标。
Java.io 包中的流支持很多种格式,比如:基本类型、对象、本地化字符集等等。
一个流可以理解为一个数据的序列。输入流表示从一个源读取数据,输出流表示向一个目标写数据。
Java 为 I/O 提供了强大的而灵活的支持,使其更广泛地应用到文件传输和网络编程中。
层次图
一个流被定义为一个数据序列。
输入流用于从源读取数据,输出流用于向目标写数据。
下图是一个描述输入流和输出流的类层次图:
FileInputStream
该流用于从文件读取数据,它的对象可以用关键字 new 来创建。
有多种构造方法可用来创建该对象。
- 可以使用字符串类型的文件名来创建一个输入流对象来读取文件:
InputStream ins = new FileInputStream("C:/java/hello");
- 也可以使用一个文件对象来创建一个输入流对象来读取文件。我们首先得使用 File() 方法来创建一个文件对象
File file = new File("C:/java/hello");
InputStream is = new FileInputStream(file);
创建了InputStream对象,就可以使用下面的方法来读取流或者进行其他的流操作:
FileOutputStream
该类用来创建一个文件并向文件中写数据。
如果该流在打开文件进行输出前,目标文件不存在,那么该流会创建该文件。
有两个构造方法可以用来创建该对象。
- 使用字符串类型的文件名来创建一个输出流对象:
OutputStream os = new FileOutputStream("C:/java/hello")
- 也可以使用一个文件对象来创建一个输出流来写文件。我们首先得使用File()方法来创建一个文件对象:
File file = new File("C:/java/hello");
OutputStream os = new FileOutputStream(file);
创建OutputStream 对象完成后,就可以使用下面的方法来写入流或者进行其他的流操作。
实例:
import java.io.*;
public class fileStreamTest {
public static void main(String args[]) {
try {
byte bWrite[] = { 11, 21, 3, 40, 5 };
OutputStream os = new FileOutputStream("test.txt");
for (int x = 0; x < bWrite.length; x++) {
os.write(bWrite[x]); // writes the bytes
}
os.close();
InputStream is = new FileInputStream("test.txt");
int size = is.available();
for (int i = 0; i < size; i++) {
System.out.print((char) is.read() + " ");
}
is.close();
} catch (IOException e) {
System.out.print("Exception");
}
}
}
上面的程序首先创建文件test.txt,并把给定的数字以二进制形式写进该文件,同时输出到控制台上。
以上代码由于是二进制写入,可能存在乱码,你可以使用以下代码实例来解决乱码问题:
import java.io.*;
public class fileStreamTest2 {
public static void main(String[] args) throws IOException {
// 构建FileOutputStream对象,文件不存在会自动新建
File f = new File("a.txt");
FileOutputStream fop = new FileOutputStream(f);
// 构建OutputStreamWriter对象,参数可以指定编码,默认为操作系统默认编码,windows上是gbk
OutputStreamWriter writer = new OutputStreamWriter(fop, "UTF-8");
// 写入到缓冲区
writer.append("中文输入");
// 换行
writer.append("\r\n");
// 刷新缓存冲,写入到文件,如果下面已经没有写入的内容了,直接close也会写入
writer.append("English");
// 关闭写入流,同时会把缓冲区内容写入文件,所以上面的注释掉
writer.close();
// 关闭输出流,释放系统资源
fop.close();
// 构建FileInputStream对象
FileInputStream fip = new FileInputStream(f);
// 构建InputStreamReader对象,编码与写入相同
InputStreamReader reader = new InputStreamReader(fip, "UTF-8");
StringBuffer sb = new StringBuffer();
while (reader.ready()) {
// 转成char加到StringBuffer对象中
sb.append((char) reader.read());
}
System.out.println(sb.toString());
// 关闭读取流
reader.close();
// 关闭输入流,释放系统资源
fip.close();
}
}
FileReader
FileReader类从InputStreamReader类继承而来。该类按字符读取流中数据。可以通过以下几种构造方法创建需要的对象。
- 在给定从中读取数据的 File 的情况下创建一个新 FileReader:
FileReader(File file)
- 在给定从中读取数据的 FileDescriptor 的情况下创建一个新 FileReader:
FileReader(FileDescriptor fd)
- 在给定从中读取数据的文件名的情况下创建一个新 FileReader:
FileReader(String fileName)
创建FileReader对象成功后,可以参照以下列表里的方法操作文件:
实例如下:
import java.io.*;
public class FileRead {
public static void main(String args[]) throws IOException {
File file = new File("Hello1.txt");
// 创建文件
file.createNewFile();
// creates a FileWriter Object
FileWriter writer = new FileWriter(file);
// 向文件写入内容
writer.write("This\n is\n an\n example\n");
writer.flush();
writer.close();
// 创建 FileReader 对象
FileReader fr = new FileReader(file);
char[] a = new char[50];
fr.read(a); // 读取数组中的内容
for (char c : a)
System.out.print(c); // 一个一个打印字符
fr.close();
}
}
运行结果 :
This
is
an
example
FileWriter
FileWriter 类从 OutputStreamWriter 类继承而来。该类按字符向流中写入数据。可以通过以下几种构造方法创建需要的对象:
- 在给出 File 对象的情况下构造一个 FileWriter 对象:
FileWriter(File file)
- 在给出 File 对象的情况下构造一个 FileWriter 对象:
FileWriter(File file, boolean append)
参数:
file:要写入数据的 File 对象。
append:如果 append 参数为 true,则将字节写入文件末尾处,相当于追加信息。如果 append 参数为 false, 则写入文件开始处。
- 构造与某个文件描述符相关联的 FileWriter 对象:
FileWriter(FileDescriptor fd)
- 在给出文件名的情况下构造 FileWriter 对象,它具有指示是否挂起写入数据的 boolean 值:
FileWriter(String fileName, boolean append)
创建FileWriter对象成功后,可以参照以下列表里的方法操作文件:
实例:
import java.io.*;
public class FileRead {
public static void main(String args[]) throws IOException {
File file = new File("Hello1.txt");
// 创建文件
file.createNewFile();
// creates a FileWriter Object
FileWriter writer = new FileWriter(file);
// 向文件写入内容
writer.write("This\n is\n an\n example\n");
writer.flush();
writer.close();
// 创建 FileReader 对象
FileReader fr = new FileReader(file);
char[] a = new char[50];
fr.read(a); // 从数组中读取内容
for (char c : a)
System.out.print(c); // 一个个打印字符
fr.close();
}
}
运行结果:
This
is
an
example
缓冲流BufferedInputStream、BufferedOutputStream
先看一下继承关系图:
有了InputStream为什么还要有BufferedInputStream?
BufferedInputStream和BufferedOutputStream这两个类分别是FilterInputStream和FilterOutputStream的子类,作为装饰器子类,使用它们可以防止每次读取/发送数据时进行实际的写操作,代表着使用缓冲区。
不带缓冲的操作每读一个字节就要写入一个字节,由于涉及磁盘的IO操作相比内存的操作要慢很多,所以不带缓冲的流效率很低。带缓冲的流,可以一次读很多字节,但不向磁盘中写入,只是先放到内存里。等凑够了缓冲区大小的时候一次性写入磁盘,这种方式可以减少磁盘操作次数,速度就会提高很多!
同时正因为它们实现了缓冲功能,所以要注意在使用BufferedOutputStream写完数据后,要调用flush()方法或close()方法,强行将缓冲区中的数据写出。否则可能无法写出数据。与之相似还BufferedReader和BufferedWriter两个类。
BufferedInputStream和BufferedOutputStream类就是实现了缓冲功能的输入流/输出流。使用带缓冲的输入输出流,效率更高,速度更快。
BufferedInputStream 本质上是通过一个内部缓冲区数组实现的。例如,在新建某输入流对应的BufferedInputStream后,当我们通过read()读取输入流的数据时,BufferedInputStream会将该输入流的数据分批的填入到缓冲区中。每当缓冲区中的数据被读完之后,输入流会再次填充数据缓冲区;如此反复,直到我们读完输入流数据位置。
BufferedInputStream API
- 源码关键字段
//内置缓存字节数组的大小 8KB
private static int defaultBufferSize = 8192;
//内置缓存字节数组
protected volatile byte buf[];
//当前buf中的字节总数、注意不是底层字节输入流的源中字节总数
protected int count;
//当前buf中下一个被读取的字节下标
protected int pos;
//最后一次调用mark(int readLimit)方法记录的buf中下一个被读取的字节的位置
protected int markpos = -1;
//调用mark后、在后续调用reset()方法失败之前云寻的从in中读取的最大数据量、用于限制被标记后buffer的最大值
protected int marklimit;
- 构造函数
BufferedInputStream(InputStream in) //使用默认buf大小、底层字节输入流构建bis
BufferedInputStream(InputStream in, int size) //使用指定buf大小、底层字节输入流构建bis
- 常用方法
int available(); //返回底层流对应的源中有效可供读取的字节数
void close(); //关闭此流、释放与此流有关的所有资源
boolean markSupport(); //查看此流是否支持mark
void mark(int readLimit); //标记当前buf中读取下一个字节的下标
int read(); //读取buf中下一个字节
int read(byte[] b, int off, int len); //读取buf中下一个字节
void reset(); //重置最后一次调用mark标记的buf中的位置
long skip(long n); //跳过n个字节、 不仅仅是buf中的有效字节、也包括in的源中的字节
BufferedOutputStream API
- 源码关键字段
protected byte[] buf; //内置缓存字节数组、用于存放程序要写入out的字节
protected int count; //内置缓存字节数组中现有字节总数
- 构造函数
//使用默认大小、底层字节输出流构造bos。默认缓冲大小是 8192 字节( 8KB )
BufferedOutputStream(OutputStream out);
//使用指定大小、底层字节输出流构造bos
BufferedOutputStream(OutputStream out, int size);
- 常用方法
//在这里提一句,`BufferedOutputStream`没有自己的`close`方法
//当他调用父类`FilterOutputStrem`的方法关闭时,会间接调用自己实现的`flush`方法
//将buf中残存的字节flush到out中再`out.flush()`到目的地中,DataOutputStream也是如此。
//将写入bos中的数据flush到out指定的目的地中、注意这里不是flush到out中
//因为其内部又调用了out.flush()
void flush();
//将一个字节写入到buf中
write(byte b);
// 将b的一部分写入buf中
write(byte[] b, int off, int len);
那么什么时候flush()才有效呢?
答案是:当OutputStream是BufferedOutputStream时。
当写文件需要flush()的效果时,需要
FileOutputStream fos = new FileOutputStream(“c:\a.txt”);
BufferedOutputStream bos = new BufferedOutputStream(fos);
也就是说,需要将FileOutputStream作为BufferedOutputStream构造函数的参数传入,然后对BufferedOutputStream进行写入操作,才能利用缓冲及flush()。
查看BufferedOutputStream的源代码,发现所谓的buffer其实就是一个byte[]。
BufferedOutputStream的每一次write其实是将内容写入byte[],当buffer容量到达上限时,会触发真正的磁盘写入。
而另一种触发磁盘写入的办法就是直接调用flush()了。
需要注意的是:
1.BufferedOutputStream在close()时会自动flush()
2.BufferedOutputStream在不调用close()的情况下,缓冲区不满,又需要把缓冲区的内容写入到文件或通过网络发送到别的机器时,才需要调用flush()
应用实例:
使用缓存流将F盘根目录里面名字为:123.png 图片复制成 abc.png
package com.tp.pandora;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class IoTest {
public static void main(String[] args) throws IOException {
String filePath = "F:/123.png";
String filePath2 = "F:/abc.png";
File file = new File(filePath);
File file2 = new File(filePath2);
copyFile(file, file2);
}
/**
* 复制文件
*
* @param oldFile
* @param newFile
*/
public static void copyFile(File oldFile, File newFile) {
InputStream inputStream = null;
BufferedInputStream bufferedInputStream = null;
OutputStream outputStream = null;
BufferedOutputStream bufferedOutputStream = null;
try {
inputStream = new FileInputStream(oldFile);
bufferedInputStream = new BufferedInputStream(inputStream);
outputStream = new FileOutputStream(newFile);
bufferedOutputStream = new BufferedOutputStream(outputStream);
byte[] b = new byte[1024]; //代表一次最多读取1KB的内容
int length = 0; //代表实际读取的字节数
while ((length = bufferedInputStream.read(b)) != -1) {
//length 代表实际读取的字节数
bufferedOutputStream.write(b, 0, length);
}
//缓冲区的内容写入到文件
bufferedOutputStream.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bufferedOutputStream != null) {
try {
bufferedOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bufferedInputStream != null) {
try {
bufferedInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
如何正确的关闭流
在处理流关闭完成后,我们还需要关闭节点流吗?
close()方法的作用是关闭流,并且释放系统资源 ,BufferedInputStream装饰一个 InputStream 使之具有缓冲功能,is要关闭只需要调用最终被装饰出的对象的 close()方法即可,因为它最终会调用真正数据源对象的 close()方法。因此,可以只调用外层流的close方法关闭其装饰的内层流。
缓冲流BufferedReader、BufferedWriter
BufferedReader
- BufferedReader构造函数
BufferedReader(Reader in, int sz) //创建一个使用指定大小输入缓冲区的缓冲字符输入流。
BufferedReader(Reader in) //创建一个使用默认大小输入缓冲区的缓冲字符输入流。
- 常用方法
int read() //读取单个字符。
int read(char[] cbuf, int off, int len) //将字符读入数组的某一部分。
String readLine() //读取一个文本行。
boolean ready() //判断此流是否已准备好被读取。
void reset() //将流重置到最新的标记。
long skip(long n) //跳过字符。
void close() //关闭该流并释放与之关联的所有资源。
void mark(int readAheadLimit) //标记流中的当前位置。
boolean markSupported() //判断此流是否支持 mark() 操作(它一定支持)。
BufferedWriter
- BufferedWriter构造函数
BufferedWriter(Writer out, int sz) //创建一个使用给定大小输出缓冲区的新缓冲字符输出流。
BufferedWriter(Writer out) //建一个使用默认大小输出缓冲区的缓冲字符输出流。
- 常用方法
void close() // 关闭此流,但要先刷新它。
void flush() //刷新该流的缓冲。
void newLine() //写入一个行分隔符。
void write(char[] cbuf, int off, int len) //写入字符数组的某一部分。
void write(int c) //写入单个字符。
void write(String s, int off, int len) //写入字符串的某一部分。
实例:复制F盘里面的一个txt文本
package com.tp.pandora;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
public class IoTest {
public static void main(String[] args) {
String filePath = "F:/123.txt";
String filePath2 = "F:/abc.txt";
File file = new File(filePath);
File file2 = new File(filePath2);
copyFile(file, file2);
}
private static void copyFile(File oldFile, File newFile) {
Reader reader = null;
BufferedReader bufferedReader = null;
Writer writer = null;
BufferedWriter bufferedWriter = null;
try {
reader = new FileReader(oldFile);
bufferedReader = new BufferedReader(reader);
writer = new FileWriter(newFile);
bufferedWriter = new BufferedWriter(writer);
String result = null; //每次读取一行的内容
while ((result = bufferedReader.readLine()) != null) {
bufferedWriter.write(result); //把内容写入文件
bufferedWriter.newLine(); //换行,result 是一行数据,所以没写一行就要换行
}
bufferedWriter.flush(); //强制把数组内容写入文件
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
bufferedWriter.close(); //关闭输出流
} catch (IOException e) {
e.printStackTrace();
}
try {
bufferedReader.close(); //关闭输入流
} catch (IOException e) {
e.printStackTrace();
}
}
}
}