终于结束了集合的学习,今天咱们就开始学习 I/O的操做了。 I/O 系列的内容分为 I/O概述、字符流、字节流。今天要学的是 I/O和字符流的操做设计模式
因为概述篇幅较短,因此就把概述压缩到这里来了。bash
I/O:即 Input Output,输入输出的意思。app
对数据的操做,其实就是对 File 文件。我偷了一张祖师爷传下来的图来描述 IO 流类结构关系。函数
从图中能够看出,都是从这如下四个类中派生出来的子类,子类的类型也好区分,后缀都是抽象基类名。性能
IO 异常大体分为三种,一是 IO 异常、二是找不到文件异常、三是没有对象异常。学习
所以,咱们在异常处理的时候,比较严峻的写法应该这样ui
FileWriter fileWriter = null;
try {
fileWriter = new FileWriter("demo.txt");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
if (fileWriter != null) {
fileWriter.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}复制代码
Reader: 用于读取字符流的抽象类。子类必须实现的方法只有 read(char[], int, int) 和 close()。可是,多数子类将重写此处定义的一些方法,以提供更高的效率和/或其余功能。this
Writer: 写入字符流的抽象类。子类必须实现的方法仅有 write(char[], int, int)、flush() 和 close()。可是,多数子类将重写此处定义的一些方法,以提供更高的效率和/或其余功能。编码
先看看基本运用吧~spa
try {
// 建立读取流和写入流
FileReader fileReader = new FileReader("raw.txt");
FileWriter fileWriter = new FileWriter("target.txt");
// 读取单个字符,自动往下读
int ch;
while ((ch = fileReader.read()) != -1) {
fileWriter.write(ch);
}
fileReader.close();
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}复制代码
每次读取一个字符再执行写入操做,效率比较慢,咱们能够尝试一次读取更多的数据再一次性写入。
try {
// 建立读取流和写入流
FileReader fileReader = new FileReader("raw.txt");
FileWriter fileWriter = new FileWriter("target.txt");
// 读取1024个字符,自动往下读
char[] buf = new char[1024];
while (fileReader.read(buf) != -1) {
fileWriter.write(buf);
}
fileReader.close();
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}复制代码
以上两种方法实现了文件的读写操做,将 raw.txt 读取写入 targer.txt 文件里面。
同时,这步操做也能够当成是文件的拷贝。
须要注意的是:
若是要实现追加写入文件的操做,好比将 raw1.txt 和 raw2.txt 共同写入 target.txt 里面,只须要在建立 FileWriter 的时候使用两个参数的构造方法便可,如:FileWriter fileWriter = new FileWriter("target.txt",true);
至于源码读写的实现,我简单说一下吧。其实 FileReader/FileWriter 的构造方法里面都有 new FileInputStream/FileOutputStream 的对象,而后继承的是InputStreamReader/OutputStreamWriter.这说明啥,我想你们内心应该有数了吧,其实仍是调用了字节流的读取,而后使用StreamEncoder/StreamDecoder进行编码解码操做。
具体流程是这样的,这里每次读/写
太长了,我只说读取操做了,反正都是相对的。
字符流的缓冲区,提升了对数据的读写效率,他有两个子类
缓冲区要结合流才可使用
在流的基础上对流的功能进行了加强
源码就不带着你们一块儿读了,我给你们分析一下 BufferedWriter 的思想。如下内容划重点,期末考试要考!
要想理解 BufferReader,就要先理解它的思想。BufferReader 的做用是为其它Reader 提供缓冲功能。建立BufferReader 时,咱们会经过它的构造函数指定某个Reader 为参数。BufferReader 会将该Reader 中的数据分批读取,每次读取一部分到缓冲中;操做完缓冲中的这部分数据以后,再从Reader 中读取下一部分的数据。
为何须要缓冲呢?缘由很简单,效率问题!缓冲中的数据其实是保存在内存中,而原始数据多是保存在硬盘中;而咱们知道,从内存中读取数据的速度比从硬盘读取数据的速度至少快10倍以上。
那干吗不干脆一次性将所有数据都读取到缓冲中呢?第一,读取所有的数据所须要的时间可能会很长。第二,内存价格很贵,容量不想硬盘那么大。
还用上面那个案例
BufferedReader bufferedReader;
BufferedWriter bufferedWriter;
try {
// 建立读取流和写入流
bufferedReader = new BufferedReader(new FileReader("raw.txt"));
bufferedWriter = new BufferedWriter(new FileWriter("target.txt", true));
// 读取一行字符串
String line;
while ((line = bufferedReader.readLine()) != null) {
bufferedWriter.write(line);
}
bufferedReader.close();
bufferedWriter.close();
} catch (IOException e) {
e.printStackTrace();
}复制代码
没什么特别的,很简单
上文中,出现了一个 readLine 方法,能够一次读取一行字符串。
其实这个一次读取一行字符串仍是蛮有用的,好比说读取一些Key-value 形式的配置文件。
咱们来看看 JDK 中关于 readLine 的描述
读取一个文本行。经过下列字符之一便可认为某行已终止:换行 ('\n')、回车 ('\r') 或回车后直接跟着换行。
返回:包含该行内容的字符串,不包含任何行终止符,若是已到达流末尾,则返回 null
抛出:IOException - 若是发生 I/O 错误
因此,咱们本身要封装一个 readLine,只须要判断读取到的字符是否为'\n'、'\r',再一次性返回就好了。
class MyBufferReaderLine {
private Reader fr;
public MyBufferReaderLine(Reader fr) {
this.fr = fr;
}
// 一次读取一行的方法
public String readLine() throws IOException {
// 定义临时容器
StringBuilder sb = new StringBuilder();
int ch = 0;
while ((ch = fr.read()) != -1) {
if (ch == '\r' || ch == '\n') {
return sb.toString();
} else {
sb.append((char) ch);
}
}
if(sb.length() != 0){
return sb.toString();
}
return null;
}
public void close() throws IOException {
fr.close();
}
}复制代码
代码实现很简单,就是参考 BufferedReader 写的一个包装类。
大家先感觉一下这个类的用法
FileReader fr;
try {
fr = new FileReader("test.txt");
LineNumberReader lnr = new LineNumberReader(fr);
String line;
while ((line = lnr.readLine()) != null) {
System.out.println(lnr.getLineNumber() + ":" + line);
}
lnr.close();
} catch (IOException e) {
e.printStackTrace();
}复制代码
这货和咱们刚刚手写的MyBufferReaderLine 基本没啥区别,继承自BufferedReader ,而后多了一个lineNumber 属性,lineNumber用来记录当前行数。
实现没有什么意义,咱们在MyBufferReaderLine 上添加一个字段lineNumber,每次 readLine 成功以后 lineNumber++ 便可。
可是,为何要讲他呢,由于他和 BufferedReader 同样,也是一个包装类啊。
包装类就是装饰设计模式啊~
好了,大家都知道了,就是提一下装饰模式。
当想要对已有的对象进行功能加强时,能够定义一个类,将已有对象传入,而且提供增强功能,那么自定义的该类就称为装饰类。
装饰模式又名包装(Wrapper)模式。装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。
可能有的同窗会问,那么为何不用继承呢?我写一个新的类,继承、再添加扩展方法或者重写方法也能够实现呀。
假如咖啡厅卖咖啡,运营可一段时间,发现客户对咖啡的甜度有不一样的需求,有以下三种需求加少许糖、通常糖、多糖。代码实现能够给 Coffee 添加一个糖量的属性,可是一开始设计 Coffee 这个类的时候没有加这个属性,根据开发守则,咱们是不该该去修改原 Coffee 类,此时能够选择新增三个子类,LowSurgeCoffee、MidSurgeCoffee、HightSurgeCoffee,或者使用装饰模式,添加3个不一样糖量的 SurgeDecorator。此时,使用装饰模式和继承没什么区别。可是运行了一段时间以后,需求又加了,咖啡须要新增口味卡布奇诺和摩卡。此时再组合以前的三种糖量,一共须要9个咖啡类。可是若是使用装饰模式,只须要新增摩卡和卡布奇诺装饰器就好了。一共6个装饰类。以后再扩展新的口味须要的子类是乘算,可是若是是装饰类,就只是加算。
以上这个例子没有代码实现,由于我懒。。。。。。
动态地给一个对象添加一些额外的职责。就增长功能来讲,Decorator模式相比生成子类更为灵活。不改变接口的前提下,加强所考虑的类的性能。