IO流(其一)
一.概述
IO流是用来处理不同设备之间的数据传输(硬盘,内存等),java语言对数据的操作是需要通过流的方式来进行的,按照流向可以将IO流分为输入流和输出流,按照操作的数据分为字节流(InputStream,OutputStream)和字符流(Reader,Writer)。其中字符流是基于GBK编码表的的流,简单来说,字符流=字节流+编码表。
一般关于IO流的基本操作都需要处理或者抛出异常。
二.字符流
如果要操作文字数据,优先考虑字符流;如果是要将数据从内存中写到硬盘上,要使用字符流中的输出流(Writer)。而硬盘数据的基本体现是文件(FileWriter)。
1.往文件中写入字符步骤:
① 创建一个可以往文件中写入字符的输出对象:
FileWriter fw = new FileWriter("文件位置") ;
//该文件位置是用于存储数据的目的地。如果文件不存在,则自动创建;如果文件存在,则将会覆盖文件。
② 调用Writer对象中的writer(String )方法,写入数据:
fw.writer("黑马程序员") ; // 将数据写入到临时缓冲区中
fw.flush() ; // 刷新缓冲区,将缓存中的数据直接写入到目的地
③调用完流之后,需要关闭流对象
fw.close() ;
//在调用close()时,会先自动调用flush()。且在关闭刘对象之后将不能对数据进行任何操作
注意:
(1)当流对象被初始化时,有可能会有异常产生,说一般都先在处理异常之前先定义
(2)在流对象关闭时,为了避免因为路径无效无法关闭流对象,应该在关闭之前先做个判断,防止出现空指针异常
2.从文件中读取字符步骤
① 创建读取流对象明确所要读取的文件:
FileReader fr = new FileReader("已存在的文件路径") ; //该文件必须存在
② 用Reader中的read()/read(char[])方法读取字符数据:
int ch = fr.read() ; //该方法会抛出异常,当读到文件末尾时,将返回-1
或者:
char[] buf = new char[3] ;
int num = fr.read(buf) ; //将读取到的字符串存储到数组之中进行缓存
通过以下一个文件复制的操作,来深入了解字符流对象:(注意:注释包含注意点以及知识点)
package cn.itzixue.writerandreader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
/*
* 范例:将一个D盘中的文本文件复制到E盘中
*
* 思路:
* 由于已经确定了是文本文件,所以可以直接使用字符流进行文件操作
* 首先需要创建一个流对象读取源的数据,然后在将数据写入到指定的目的地中
*/
public class CopyTextTest {
public static void main(String[] args) {
//创建一个FileReader流对象,读取一个已有的文本文件,此处为了处理异常,先将其定义为空,在try/catch中在实例化对象
FileReader fr = null ;
//创建一个FileWriter流对象,并指定目的名称,对数据进行存储
FileWriter fw = null ;
try {
//所指定的("D:/demo01.txt")文件必须是存在的,否则会抛出异常
fr = new FileReader("D:"+File.separator+"demo01.txt") ;
//所指定的("E:/demo01-copy.txt")文件可以存在也可以不存在。若不存在则进行创建;若存在,则对原文件进行覆盖。
/*
如果想在原有文件中进行续写,则可以利用FileWriter对象的构造方法
FileWriter(File file,boolean append),在传入文件名的同时,传入一个布尔值true,
这样就可以对文件进行续写。
*/
fw = new FileWriter("E:"+File.separator+"demo01-copy.txt") ;
//创建一个临时容器,用于缓存读取到的字符
char[] buf = new char[1024] ;
//定义一个变量记录读取到的字符数,(即是向数组中存入的字符个数)
int len = 0 ;
while((len=fr.read(buf))!=-1){
fw.write(buf, 0, len) ;
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
/*
此处将流对象的close()方法定义在finally中,
是为了避免流对象在运行过程中出现异常而导致流对象没有关闭,进而导致内存资源的浪费
(当流未关闭时,文件将无法删除)
*/
finally {
try {
//将流对象关闭时,会先自动调用flush()方法进行刷新。
fr.close() ;
fw.close() ;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
三.字符流缓冲区
引入字符流缓冲区是为了提高流对象传输数据的效率。
1.BufferedReader
初始化BufferedReader类的对象(关联流对象):
BufferedReader bufr = new ByfferedReader(new FileReader("已存在的文件路径")) ;
通过其特有的readLine()方法,可以一次性读取一行字符。其中包含行内容的字符串,但并不包括任何行结束符,如果已到达流的末尾,则返回 null。底层调用的依然是read,只不过每次将读到的数据储存的一个底层数组(缓冲区)中,返回的是一个字符串。如果读到末尾就返回null(注意此处的null不是字符串似的"null",因为"null"!=null,而是一个实实在在的对象)。该方法使用了读取缓冲区的read()方法,将读取到的字符进行缓冲并判断换行标志。最后将标志之前的缓存数据变成字符串返回。(其中,bufr.read()是从缓冲区中取出字符数据,所以覆盖了父类中的方法)
在关闭缓冲区的时候,流对象也同时被关闭了。
(但需要行号时,可以使用继承自BufferedReader的LineNumberReader)
2.BufferedWriter
初始化BufferedWriter类的对象(关联流对象):
BufferedWriter bufw = new BufferedWriter(new FileWriter("文件路径")) ;
其特有的方法是newLine(),当字符缓冲读取流调用readLine()方法的时候,返回的并不包括行结束标志。因此,在write()写入读取到的一行数据时候,就必须调用newLine()方法进行换行。
3.装饰设计模式
当需要对一组对象的功能进行增强时,可以使用装饰设计模式进行解决。虽然通过继承也可以实现对原有类功能的增强,但是因为继承会产生关系,所以对比装饰来说,没有那么灵活。而是用装饰设计模式进行功能增强,确是不需要产生关系的,所以较为灵活。但是有一点需要注意,装饰类和被装饰类都必须说属于同一个接口或父类。
总而言之,BufferedReader和BuffereWriter两个类是在原有类基础上的功能增强。通过这两个类与流对象进行关联并对数据进行操作,在很大程度上提高数据操作的效率。
四.字节流
当操作的数据不是单纯的文本数据时,我们就不能使用字符流了,而是应该使用字节流来进行数据的操作。
1.使用字节流进行数据的写操作
① 创建字节输出流对象:
FileOutputStream fos = new FileOutputStream("目的位置") ;
② 写数据:
fos.write("abc".getBytes()) ; //字节流写的数据是字节数据,并且直接写到目的地中,所以不需要进行刷新
③ 关闭流对象:
fos.close() ; //关闭资源动作一定要完成
2.使用字节流进行文件的读操作
① 创建一个读取流对象,和指定文件进行关联:
FileInputStream fis = new FileInputStream(" 已存在的文件位置") ;
② 使用read()/read(byte[])进行读数据:
int ch = fis.read() ; //一次读取一个字节。对于中文,字符流一次可读一个而字节流需要读两次
或者:
byte[] buf = new byte[1024] ; //字节流的缓冲区使用的是字节数组(byte[])
int len = 0 ; //对读取到的数据进行长度的记录
while((len=fis.read(buf))!=-1){ … 对读取到的数据进行操作 … }
③ 关闭流对象
fis.close() ;
通过字节流对象的available()方法,可以返回文件大小(字节数)。
通过以下一个复制MP3文件的操作,来深入了解字节流对象:(注意:注释包含注意点以及知识点)
package cn.itzixue.inputstreamandoutputstream;
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;
public class CopyMP3 {
public static void main(String[] args) throws IOException {
copy() ;
}
/*
* 由于MP3文件有其特有的编码方式,所以不能通过字符的编码方式进行读取与写入,
* 所以不能使用字符流,而应该使用字节流对象。
* 为了提高其传输效率,可以使用字节流对应的缓冲区对象(BufferInputStream/BufferOutputStream)
*/
public static void copy() throws IOException {
//创建一个文件的字节流输入对象
FileInputStream fis = new FileInputStream("D:"+File.separator+"1.mp3") ;
//创建缓冲区对象将字节流对象传入
BufferedInputStream bufis = new BufferedInputStream(fis) ;
//创建一个文件的字节流输出对象
FileOutputStream fos = new FileOutputStream("E:"+File.separator+"1-copy.mp3") ;
//创建缓冲区对象将字节流对象传入
BufferedOutputStream bufos = new BufferedOutputStream(fos) ;
//创建一个临时容器,用于缓存读取到的字节
byte[] buf = new byte[1024] ;
//定义一个变量记录读取到的字节数
int len = 0 ;
while((len=bufis.read(buf))!=-1){
bufos.write(buf,0,len) ;
//此处不应该进行刷新,如果没读一次刷新一次的话,会使得效率变得很慢
//bufos.flush() ;
}
//使用完流对象一定要将其关闭
bufis.close() ;
bufos.close() ;
}
}