一般来说,java I/O总共有16个流需要我们熟练掌握。接下来从I/O继承关系、流的分类、选择不同流应遵循的原则三个方面对 java I/O进行梳理。
1、流的分类
按照对数据的读写方式不同可以将流分为字节流和字符流,字节流每次读写都是以字节为单位,而字符流则是按编码字符为单位对多个字节进行字符编码然后返回字符。
按照流的指向可将流分为输入流和输出流,输入流是指将数据读到内存,输出流则是指将内存中的数据写到磁盘。
除了分为字节流/字符流、输入流/输出流,还可以按实现的功能分为节点流和处理流,
节点流:直接对数据源进行读写
处理流:对已经存在的流进行连接和封装,其构造方法必须要带一个其他流作为参数。处理流又包含5个类型:缓冲流、转换流、数据流、打印流和对象流。
2、流的继承体系:
3、选择流应遵循的规则
(1)目的:写入or读取?
写入:OutputStream/Writer
读取:InputStream/Reader
(2)是否操纵存文本?
是:优先选择字符流,Reader/Writer
否:只能选择字节流,Stream
(3)是否需要使用缓冲?
BufferedInputStream/BufferedOutputStream、BufferedReader/BufferedWriter
4、常用流的使用举例
通常使用流有四个步骤:
创建文件对象——>创建流——>使用流进行读写——>关闭流
(1)文件输入/输出流:
字节流:FileInputStream/FileOutputStream
BufferedInputStream/BufferedOutputStream
字符流:FileReader/FileWriter
BufferedReader/BufferedWriter
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
/**
* @author liwenkai
*
* 2016年11月28日
*/
public class BufferReaderAndWriterExample {
/**
* 1、FileInputStream类:
* 常用方法:int read():读取单个字节,返回该符号的ASCⅡ码
* int read(byte []byt):将此输入流中最多byt.length个字节读取到数组byt中,返回读入的总字节数
* int read(byte []byt,int off,int len):将此输入流中从偏移量off开始,最多len个字节读取到数组byt中,
* 返回读入的总字节数
* @param inFileName 要读取的文件位置
*/
public void fileInputStream(String inFileName){
//首先生成文件对象
File file = new File(inFileName);
try {
//创建输入流对象
FileInputStream in = new FileInputStream(file);
//创建byte数组。将文件中读取的信息放到数组中
byte []byt = new byte[100];
int len = in.read(byt);//返回写入到数组中的总字节数
// int x = in.read(); //无参的read方法返回的是该字符所对应ASCⅡ码,例如,若读到'a',则返回97
System.out.println(len);
//System.out.println("文件中的信息是:" + new String(byt,0,len));
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 2、FileOutputStream类:
* 常用方法:write(byte []byt):将指定数组的数据写入到磁盘
* write(byt []byt, int off, int len):将指定数组的指定位置起的指定长度字节数据写入到磁盘
* write(int b):将指定字节写入到磁盘;exp:write(97)则是在磁盘文件中写入'a'。
* @param byt 要进行写入的信息数组,类型为byte
* @param outFileName 写入的文件位置
*/
public void fileOutputStream(byte []byt,String outFileName){
File file = new File(outFileName);
try {
FileOutputStream out = new FileOutputStream(file);
out.write(97);
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
*3、FileReader类:
*常用方法:int read(char []chr):读取字符存入到字符数组chr,并返回读取的字符数
* int read(char []char,int off,int len):将此输入流中从偏移量off开始,最多len个字符读取到数组chr中,并
* 返回读取的字符数。
* @param inFileName 要读取文件的位置
* 可以看出,字节流和字符流工作方式几乎一致,只是对数据的操作格式不一样罢了
*/
public void fileReader(String inFileName){
File file = new File(inFileName);
try {
FileReader in = new FileReader(file);
char []chr = new char[1024];
int len = in.read(chr);
System.out.println("文件中的信息是:" + new String(chr,0,len));
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
*4、FileWriter类:
*常用方法:write(String str):将该字符串写入到磁盘
* write(char []chr):将该字符数组写入到磁盘
* write(char []chr, int off, int len):将该字符数组从指定位置起的指定长度写入到磁盘
* @param s 要进行写入的字符串数据
* @param outFileName 要入的文件位置
*/
public void fileWriter(String s,String outFileName){
File file = new File(outFileName);
try {
FileWriter out = new FileWriter(file);
out.write(s);
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 5、BufferedInputStream类:InputStream流每次只能读一个字节,且每次操作都是针对磁盘的。而使用BufferedInputStream后,
* 流每一次从磁盘都读取尽可能多的数据添加到缓冲区(数组,默认大小为8K),然后在从缓冲区分批次将数据读取出来返回给用户,由于从内存读取远比从磁盘
* 读取快,因此使用缓冲流大大提高了读写速度。
* 常用方法:int read():读取单个字节,返回该数据的ASCⅡ码
* int read(byte []byt):读取最多byt.length长度的字节数据放入到byt数组中,返回读取的字节总数
* int read(byte []byt,int off, int len):将此输入流中从偏移量off开始,最多len个字节数据读取到数组byt中,并
* 返回读取的字符数。
*
* @param inFileName 要读取的文件
*/
public void bufferedInputStream(String inFileName){
File file = new File(inFileName);
try {
InputStream is = new FileInputStream(file); //多态,父类型引用指向子类型对象
BufferedInputStream in = new BufferedInputStream(is);
byte []byt = new byte[1000];
int len = in.read(byt);
StringBuffer sbuf = new StringBuffer();
while (len != -1){
String s = new String(byt,0,len);
sbuf.append(s);
}
System.out.println("文件中的信息:" + sbuf);
in.close();
is.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
*6、BufferedOutputStream类:OutputStream的每次写入都是针对磁盘,而BufferedOutputStream则先将缓冲区堆满,然后
* 才会向磁盘中写入缓冲区的数据,为了防止最后的数据量不能将缓冲区填满,所以要在最后调用flush()方法将缓冲区中的所有数据全部写入到磁盘。
* 注意:close()和flush有交集,流在关闭前会先将缓冲区的数据强制写出,然后关闭,所以一般写了close()方法后就不再写flush方法。
* 常用方法:write(byte []byt):将指定数组的数据写入到磁盘
* write(byt []byt, int off, int len):将指定数组的指定位置起的指定长度字节数据写入到磁盘
* write(int b):将指定字节写入到磁盘;exp:write(97)则是在磁盘文件中写入'a'。
*
* @param byt 要写入磁盘的数据
* @param outFileName 要写入的磁盘位置
*/
public void bufferedOutputStream(byte []byt,String outFileName){
File file = new File(outFileName);
try {
OutputStream os = new FileOutputStream(file);
BufferedOutputStream out = new BufferedOutputStream(os);
out.write(byt);
//out.flush();
out.close();
os.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 7、BufferedReader类:
* 常用方法:read():读取单个字符,返回读取字符的unicode编码
* readLine():读取一个文本行,返回该文本行的字符串。
* @param inFileName 要读取的文件位置
*/
public void bufferedReader(String inFileName){
File file = new File(inFileName);
try {
Reader read = new FileReader(file);
BufferedReader bufr = new BufferedReader(read);
String s = null;
while((s = bufr.readLine()) != null){
System.out.println(s);
}
bufr.close();
read.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 8、BufferedWriter类:
* 常用方法:write(String str, int off, int len):写入字符串的一部分
* write(String str):写入该字符串
* flush():刷新该流的缓存
* newLine():另起一行
* @param data
* @param outFileName
*/
public void bufferedWriter(String []content,String outFileName){
//1、创建文件对象
File file = new File(outFileName);
try {
Writer fw = new FileWriter(file);
BufferedWriter bufw = new BufferedWriter(fw);
for (int i = 0; i < content.length; i++){
bufw.write(content[i]);
bufw.newLine();//写入一个行分隔符,即另起一行
}
bufw.close();
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
总结:由上面代码可以看出,进行文件输入输出的普通流和缓冲流的很多常用方法都是一样的,只是缓冲流在用户和磁盘之间多了一个缓冲区而已,流目的没变,只是提升了效率。
(2)转换流:InputStreamReader/OutputStreamWriter
这两个类有这样的构造方法:
InputStreamReader(InputStream in, Charset cs):创建指定编码格式的InputStreamReader
OutputStreamWriter(OutputStream out, Charset cs):创建指定编码格式的OutputStreamWriter
所以在需要使用指定的编码格式进行读写文件时,则不需要使用到这两个类。
/**
* InputStreamReader类:将字节流转换成字符流,例如,将键盘输入按照默认编码转换成字符存储在磁盘中,
* 何时使用:需要使用指定编码读取文件时
*/
public void inputStreamReader(){
String FileName = "123.txt";
File file = new File(FileName);
String s = null;
try {
//创建转换流,并使用utf-8编码对读取到的字节进行编码
InputStreamReader insr = new InputStreamReader(new FileInputStream(file),"utf-8");
//使用转换流创建文件输入流
BufferedReader bufr = new BufferedReader(insr);
while ((s = bufr.readLine()) != null){
System.out.println(s);
}
bufr.close();
insr.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* OutputStreamWwriter类:将字符流转换成字节流
* 何时使用:需要使用指定编码输出文件时。
*/
public void outputStreamWriter(){
File file = new File("123.txt");
String s = "hello world";
try {
//创建转换流,并使用utf-8编码进行输出
OutputStreamWriter oups = new OutputStreamWriter(new FileOutputStream(file),"utf-8");
BufferedWriter bufw = new BufferedWriter(oups);
bufw.write(s);
bufw.close();
oups.close();
} catch (Exception e) {
e.printStackTrace();
}
}
(3)数据流:DataInputStream/DateOutputStream
数据流允许应用程序以与机器无关的方式从底层读取基本java数据类型,也就是说,当读取一个数据时,不必再关心这个数值应当是哪种字节
当要求输入输出流必须遵循平台无关时,可以使用这两个类
/**
* DataInputStream/DataOutputStream:数据输入/输出流,一般来说都是搭配使用,在要求输入输出都与平台无关时,使用数据流。
*/
public void dataStream(){
File file = new File("C:\\Users\\lwk\\Desktop\\outtest1.txt");
try {
DataOutputStream dops = new DataOutputStream(new FileOutputStream(file));
//dops.writeBytes("abc");
//dops.writeChars("李文凯");
dops.writeUTF("李文凯");
dops.close();
DataInputStream fis = new DataInputStream(new FileInputStream(file));
System.out.println(fis.readUTF());
fis.close();
} catch (Exception e) {
e.printStackTrace();
}
}
(4)打印流:PrintStream/PrintWriter
我们在使用OutputStream时,发现他的write方法接受的参数类型只能是byte类型。所以当我们要写入其他数据类型的时候,还要将其转换成byte类型才能进行写入,所以,这里构造了一种新的字节输出流PrintStream,该类重载了许多print和println方法,可以接受任何形式的参数,保证用户输入的是什么,输出的就是什么。
举个例子:我们要把“hello world”写入到文件中,如果使用OutputStream流,首先要把字符串转换成byte数组,然后才能传入到write方法中,而使用打印流,则直接使用print(“hello world”)即可。
PrintWriter跟PrintStream功能类似,区别只在于PrintStream 打印的所有字符都使用平台的默认字符编码转换为字节。在需要写入字符而不是写入字节的情况下,应该使用 PrintWriter 类。关于该类的构造方法请参考帮助文档
(5)对象流:ObjectInputStream/ObjectOutputStream
对象流实现的就是对象的序列化与反序列化。
ObjectInputStream:对象的反序列化,使用readObject()方法。
ObjectOutputStream:对象的序列化,使用writeObject()方法。
其中,要实现序列化的对象必须要实现序列化接口“Serializable”接口。
example:
这里实体类忽略没写。
/**
* ObjectInputStream/ObjectOutputStream
* 实现对象的序列化和反序列化
*/
public void objectStream() {
File file = new File("123.txt");
Student stu = new Student("kaiwen",23);
try {
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(stu);
oos.close();
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
ObjectInputStream ois = new ObjectInputStream(bis);
Student student = (Student)ois.readObject();
System.out.println("学生姓名:" + student.getName() + " " + "年龄:" + student.getAge());
} catch (Exception e) {
e.printStackTrace();
}
}
总结:虽然这里总共罗列了16种I/O流,但是归根结底所有的流都是最基本的四种流的衍生,所以最重要的还是要着重掌握InputStream/OutputStream和Reader/Writer这四种基类的原理与本质。