Java IO流总结
介绍IO
流之前,首先介绍File
类
File
我们知道,java中一切皆对象。File
类就是对一个实体文件(例如磁盘上的某个文件或者文件夹)的抽象,通过File
的实例对象对实体文件进行引用,然后进行一系列的操作。
//file1只是对"d:/info.txt"文件的抽象引用,至于文件存在与否对这句话并没有什么影响
File file1 = new File("d:/info.txt");
//这句话是对文件夹的引用,同样,存在与否对这句话的执行并没有影响
File file2 = new File("d:/info");
接下来就可以利用File
实例进行一系列文件属性的操作,比如
- 文件是否存在
- 文件创建/删除/重命名
- 是目录还是普通文件
- 权限查询(是否可读/写/执行)
- 路径查询
File
类的基本操作API都很简单,根据IDE下的提示,和个人需求直接使用就行了,基本没什么问题,这里就不举例子了。
注:凡是涉及到文件内容的操作,File类是无能为力的,只能通过IO流完成。
因此File类的对象一般作为IO流对象的参数,指明IO操作的作用对象。
IO流
刚开始学习IO
流的时候对这个概念很是费解,为什么叫做IO
流呢?现在看来,主要是因为思维没有转变过来。
平日生活当中我们向其他人索取东西往往都是直来直往的,比如我买一瓶水,店家直接递给我“一瓶”水,而不是说给你个水管,让你自己抽一瓶带走。
但是如果提起南水北调,由丹江口调水入北京,这很自然就和“流”对应起来了,因为源头的水无法一下子全部传输到北方,只能通过“流”的形式一点点传输。
计算机中也是一样,无论是文件还是网络,进行IO
操作(输入/输出)只能采用渐进式的数据传输,现在看来IO
流的叫法实在是太贴切不过了!
IO流的分类
从三个角度对IO
流进行分类,大家先了解一下即可,后面会详细介绍很多常用的具体的流对象
按照操作数据单位不同
- 字节流
- 字符流
首先需要知道的是字节流是万能的,因为计算机中一切文件底层皆是字节。
对于文本文件这种,我们可以采用字符流进行传输,但是对于视频、图片这样的二进制文件只能采用字节流的方式传输。
按照流向不同
- 输入流
- 输出流
这里需要注意的是,我们是站在程序的角度对流向进行命名的。从网络或磁盘中读取数据到程序中,我们成为输入;从程序向网络或磁盘中写入数据,我们称之为输出。
按照流的角色
- 节点流
- 处理流
这两个概念我们平时接触到的不是特别多,但是搞懂了这两个概念对理清IO流的规律有很大的帮助。
节点流:程序与文件(或者网络)之间直接端对端的传输过程中使用的流。这么说有点抽象,举个例子,假如我们有一个空桶和一个装满水的水箱,我们装一根水管用来从水箱向空桶疏水,在不纠结这到底是输入流还是输出流的情况下(根本不重要),我们称这个水管就是节点流。
这个时候有好事者认为水流太慢了,于是在原来的水管的基础上又加上了一层其他水管(我们假设加上这层水管后真的使流速变快了,别杠。。。我说快了就是快了),那么我们称后来加上的这个水管为处理流。
在java.io中,我们常用的节点流一共有四个,分别是FileInputStream
,FileOutputStream
,FileReader
,FileWriter
其他的你都可以看作是处理流!怎么样,是不是特别简单!!
当然了,上面的四个类如果从其他角度分类的话又会属于不同的流类型,比如FileInputStream
属于字节流,同时又是输入流。IO流中的类其实规律性很强,仅仅根据名字你就能知道他们的类型
IO流的4个基本抽象类
- InputStream(字节输入流)
- OutputStream(字节输出流)
- Reader(字符输入流)
- Writer(字符输出流)
所有IO流对象都直接或者间接继承自这四个基本抽象类
重要的IO流
四个节点流
FileInputStream
首先准备一个文本文件info.txt
,里边的内容你随便写
我们尝试读取该文件,并将内容打印到控制台上
第一种方法
public static void testFileInputStream1() {
FileInputStream fis = null;
try {
//1.创建实体文件的File对象
File file = new File("info.txt");
//2.创建FileInputStream对象
fis = new FileInputStream(file);
int b = 0;
//3.调用read方法,不断从io流中读取数据
/** * 此处的while充分体现了“流”的概念,表示源源不断从IO中进行读取 * 注意read()方法是一个字节一个字节地进行读取。是效率最低的一种方法,不推荐使用 * * 由于文本内容都是ascii字符,因此将读取的字节b进行(char)强转可以正常打印 * 但是如果超出char的表示范围(如汉字),不能采用该种方式,否则会出现乱码的情况 */
while ((b = fis.read()) != -1) {
System.out.print((char) (b));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
使用read()
public static void testFileInputStream2() {
FileInputStream fis = null;
try {
//1.创建实体文件的File对象
File file = new File("info.txt");
//2.创建FileInputStream对象
fis = new FileInputStream(file);
int len = 0;
byte[] bytes = new byte[20];
String str;
//3.调用read方法,不断从io流中读取数据
/** * read(byte[] buf)函数同时从流中读取若干个字节,返回值为该次读取的字节个数,如果到达文件末尾则返回-1 */
while ((len = fis.read(bytes)) != -1) {
//采用如下的打印方式可以避免乱码问题
str = new String(bytes, 0, len);
System.out.print(str);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
FileOutputStream
我们使用FileOutputStream
和前文的FileInputStream
实现文件拷贝
public static void testFileOutputStream() {
File inputFIle = new File("input.txt");
File outputFIle = new File("output.txt");
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(inputFIle);
fos = new FileOutputStream(outputFIle);
int len = 0;
byte[] bytes = new byte[1024];
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
//先关闭输出流
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
FileReader/FileWriter
同样我们使用字节节点流来实现文件拷贝
/** * 利用FileReader/FileWriter实现文件复制 */
public static void testFileReaderAndFileWriter() {
File inputFile = new File("input.txt");
File outputFile = new File("output.txt");
FileReader fr = null;
FileWriter fw = null;
try {
fr = new FileReader(inputFile);
fw = new FileWriter(outputFile);
int len = 0;
//API和FileInputStream/FileOutputStream基本相同
//不同点在于处理的基本单位不同,本例中使用的是字符数组
char[] chars = new char[20];
while ((len = fr.read(chars)) != -1) {
fw.write(chars, 0, len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fw != null) {
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fr != null) {
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
处理流之四种缓冲流
- BufferedInputStream
- BufferedOutputStream
- BufferedReader
- BufferedWriter
BufferdInputStream/BufferedOutputStream
首先我们看BufferdInputStream
/BufferedOutputStream
,他是对InputStream
的实现类的包装流,即它的构造函数需要传递一个InputStream
的实现类,具体用法高度套路化,还是以文件拷贝为例,下面看具体代码。
public static void testBufferedStream() {
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream = null;
try {
//1.万年不变的,创建File对象
File inputFile = new File("input.txt");
File outputFile = new File("output.txt");
//2.万年不变的,包装一层FileInputStream/FilePutputStream
fileInputStream = new FileInputStream(inputFile);
fileOutputStream = new FileOutputStream(outputFile);
//重点来了!创建缓冲流对象,将fileInputStream/fileOutputStream作为参数传递进去
//3.高度套路化的缓冲流的创建
BufferedInputStream bis = new BufferedInputStream(fileInputStream);
BufferedOutputStream bos = new BufferedOutputStream(fileOutputStream);
//4.利用缓冲流进行读取,基本的API和之前的没啥区别
int len = 0;
byte[] bytes = new byte[1024];
while ((len = bis.read()) != -1) {
bos.write(bytes, 0, len);
//5.这是使用缓冲流需要注意的一个点,write()之后寄的flush()一下
bos.flush();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
//.....略
}
}
注:有了缓冲流之后我们一般不会再直接使用节点流来操作数据,使用缓冲流会极大地提高效率。此外,如果是纯文本文件的读取,尽量使用字符流,效率比字节流要高。
FileReader/FileWriter
最后介绍一下BufferedReader
和BufferedWriter
,从名字上就可以看出来了,和FileReader
/FileWriter
又是配套的一组,代码其实和上面一样的套路。唯一不同的一点,而且是比较重要的一点,便是BufferedReader
多了一个readLine()
的API,便于我们直接读取一行。
代码还是写一遍吧…都是高度套路
public static void testBufferedReaderAndWriter() {
FileReader fileReader = null;
FileWriter fileWriter = null;
try {
//1.万年不变的,创建File对象
File inputFile = new File("input.txt");
File outputFile = new File("output.txt");
//2.万年不变的,包装一层FileReader/FileWriter
fileReader = new FileReader(inputFile);
fileWriter = new FileWriter(outputFile);
//重点来了!创建缓冲流对象,将fileReader/fileWriter作为参数传递进去
//3.高度套路化的缓冲流的创建
BufferedReader br = new BufferedReader(fileReader);
BufferedWriter bw = new BufferedWriter(fileWriter);
//4.利用缓冲流进行读取,除了多一个readLine()之外,API区别不大
//用于接收读取一行的返回结果,不要不停判断是否为null,而不是之前的-1
String str = "";
while ((str = br.readLine()) != null) {
bw.write(str);
//换行操作,否则拷贝之后的文件都是一行
bw.newLine();
//5.这是使用缓冲流需要注意的一个点,write()之后寄的flush()一下
bw.flush();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
//.....略
}
}
处理流之两个转换流
InputStreamReader/OutputStreamWriter
这两种转换流比较有玩儿,InputStreamReader
是将字节流转换为字符流。
有的人可能会问,你一开始使用字符流不就完了?干嘛多此一举还得转换一次?原因有二:一是有人就闲得慌,先用字节流处理…毕竟因为字节流是万能的嘛。其二,我们可以在字节流转字符流的过程中设置自己想要的编码格式,获得我们想要的结果,这也就是所谓的解码过程。
举个例子来说,二进制流0xE68891
(这里表示为16进制,本质是一样的),如果用utf8
字符集来解码,得到的结果为’我’ ;如果用iso-8859-1
,也就是latin1
字符集去解释这串字节 ,解释后的字符串就是'我'
。各位看到这里或许就明白了吧。
接下来的理解就简单了,OutputStreamWriter
是将字符输出流转换为字节输出流。一图胜千言,上图!
demo如下,看一下注释大家就会很明白
/** * 测试转换流 */
public static void testStreamReaderWriter() {
File inputFile = new File("input.txt");
File outputFile = new File("outputFile.txt");
/** * 解码 */
FileInputStream fis = null;
InputStreamReader isr = null;
/** * 编码 */
FileOutputStream fos = null;
OutputStreamWriter osw = null;
try {
fis = new FileInputStream(inputFile);
//这里的第二个参数表示从字节到字符采用何种字符集来解码
//如果字符集不足以包含你期望的所有字符,则会出现乱码
// isr = new InputStreamReader(fis,"ISO8859-1");//中文情况下乱码
isr = new InputStreamReader(fis, "UTF8");//正常
//isr已经是字符流,自然我们就可以加一层处理流,BufferedReader
BufferedReader br = new BufferedReader(isr);
fos = new FileOutputStream(outputFile);
osw = new OutputStreamWriter(fos);
//osw同样是字符流,继续采用处理流提高速度
BufferedWriter bw = new BufferedWriter(osw);
String str = "";
while ((str = br.readLine()) != null) {
bw.write(str);
bw.newLine();
bw.flush();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//异常处理
}
}