day19(中)_IO流3(模拟缓冲区,装饰设计模式)
1.MyBufferedReader和MyBufferedInputStream
1.模拟字符读取流的缓冲区:
/* 根据readLine原理: 自定义一个类包含一个功能和readLine一致的方法 来模拟以下BufferedReader方法 */ package myreadline; import java.io.FileReader; import java.io.IOException; class MyBufferedReader{ private FileReader r;//需要用到FileReader的read方法,因此初始化时传入FileReader对象 private char[] chArr=new char[1024]; private int pointer=0; MyBufferedReader(FileReader r){ this.r=r; } //自定义readLine方法 public String readLine()throws IOException{//自定义功能不在内部处理,而是抛出去,让调用者处理 StringBuilder sb=new StringBuilder();//定义一个临时容器模拟缓冲区 int ch=0; while((ch=r.read())!=-1){ if(ch=='\r') continue;//读下一个进行判断 else if(ch=='\n') return sb.toString(); else sb.append((char)ch); } /*①以上代码也不可写成
if(ch==’\r’||ch==’\n’)
returnsb.toString()
因为当读到'\r’ 立即返回改行数据,
但是下次会读'\n’ 会再次返回,此时StringBuilder无数据
②如果当前平台的换行为'\r’以上方法并不适用
以上方法仅适用与\r\n或\n换行
此时直接
if(ch==’\r’)
return sb.toString();
else
return sb.append((char)ch);
③对于以上代码可能发生一种情况: 当读到文件最后一行: 例如:abcd后面没有回车,此时执行不到sb.toString(), 也就是说:虽然存入容器但是这一行不能返回.
那么加上一个判断(如下):
*/ if(sb.length()!=0) return sb.toString(); return null;//此时读到文件末尾 } //模拟自定义缓冲区,把关闭单独封装在一个方法中 public void myClose()throws IOException{ r.close(); } public static void main(String[] args){ MyBufferedReader mbr=null; try{ mbr=new MyBufferedReader(new FileReader("4_MyBufferedReader.java")); String line=null; while((line=mbr.readLine_2())!=null) System.out.println(line); } catch(IOException e){ throw new RuntimeException("读异常"); } finally{ try{ if(mbr!=null) mbr.myClose(); } catch(IOException e){ throw new RuntimeException("关闭异常"); } } } }
/* 如果不利用StringBuilder而利用 字符数组 */ public String readLine_2()throws IOException{ pointer=0; int ch=0; while((ch=r.read())!=-1){ if(ch=='\r') continue; else if(ch=='\n') return new String(chArr,0,pointer); else chArr[pointer++]=(char)ch; } if(pointer!=0) return new String(chArr,0,pointer); return null; }2.模拟字节读取流缓冲区:
/* 自定义缓冲区: 算法思想: 参照示意图 */ package mybuffered; import java.io.InputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io. BufferedOutputStream; class MyBufferedInputStream{ private byte[] buff=new byte[1024]; private InputStream is=null; private int pointer=0;//指针用于取出缓冲区(数组)中的元素 private int count=0;//计数器用于记录每次从文件中取出的字节数 MyBufferedInputStream(InputStream is){ this.is=is; } public int myBufferedRead()throws IOException{ if(count==0){//只有当缓冲区中的数据被读完时,再从文件中 //取一部分数据 count=is.read(buff); pointer=0;//当count==0,把指针重新移到0角标 } if(count!=-1){//是否到达文件尾 --count;//读取一个字节,count-1 return (int)buff[pointer++] & 255;//0x00ff } else return -1; } public void close()throws IOException{ is.close(); } } class TestBuffered{ public static void main(String[] args)throws IOException{ //用自定义缓冲区拷贝MP3 MyBufferedInputStream mbis=new MyBufferedInputStream(new FileInputStream("e:\\song\\Amy Diamond - Heartbeats.mp3")); BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream("c:\\Hertbeats.mp3")); long start=System.currentTimeMillis(); int aByte=0; System.out.println(aByte=(mbis.myBufferedRead()));while((aByte=(mbis.myBufferedRead()))!=-1){//从自定义缓冲区中读一个字节,写入到系统的缓冲区 bos.write(aByte); bos.flush(); } long end=System.currentTimeMillis(); System.out.println(end-start+"ms");//时间的长短和cpu性能以及其它因素有关 bos.close(); mbis.close(); } }
2.装饰设计模式:
①概述:
/* 装饰设计模式:(目前学的第三种设计模式) 当想要对已有的对象进行功能增强时 可以定义类,将已有对象(new FileReader)传入,基于已有的功能(read),并提供加强功能(myReadLine). 那么自定义的该类成为装饰类. 装饰类通常会通过构造方法接收被装饰的对象 并基于被装饰的对象的功能,提供更强的功能 */ //例如:增强人的吃饭功能 class Person{ public void eat(){ System.out.println("吃饭"); } } class SuperPerson{ private Person p; SuperPerson(Person p){ this.p=p; } public void superEat(){ System.out.println("喝小酒"); p.eat(); System.out.println("甜点"); System.out.println("整一根"); } } class PersonDemo{ public static void main(String[] args){ Person p=new Person(); p.eat(); new SuperPerson(p).superEat(); } } /* 到目前为止已经学到3中设计模式: 1.单例设计模式 2.模板方法设计模式 3.装饰设计模式 */
②※装饰相对继承优点:
/* 在人吃饭例子中,可以不用装饰类,我直接 让SuperPerson extends Person,然后复写eat方法 不也OK? 在举例说明为什么要有装饰设计模式? //MyTextReader类用来操作文件读取,为了提高读操作效率,使用缓冲区技术 //又定义一个类MyBufferedTextReader继承它,提供更高效的readLine方法 MyReader <--MyTextReader<--MyBufferedTextReader <--MyMediaReader<--MyBufferedMediaReader//读取多媒体文件,然后为了提高读效率,使用缓冲区 <--MyDataReader<--MyBufferedDataReader//同理 ...... 那么,我后期在来个类用于操作读取其它文件,为了高效还得定义缓冲区子类, 产生一些缺点: 1.使整个体系变得异常冗杂 2.后期的扩展性极差 那么干脆: 找到其参数的共同类型.通过多态形式.可以提高扩展性 MyBufferedReader extends MyReader{ private MyReader mr; pubic MyBufferedReader(MyReader mr){//MyReader的子类对象均能传入 this.mr=mr; //谁需要使用缓冲技术,把它传进来 } } 简化了原有体系结构: MyReader <--MyTextReader <--MyMediaReader <--MyDataReader <--MyBufferedReader */ /* 装饰设计模式对比继承: 1.装饰设计模式比继承更加灵活 提高扩展性,避免了继承的冗杂 而且降低了类与类之间关系 2.装饰类因为增强已有对象,具备的功能和已有的功能是相同的 只不过提供了更强的功能(这个功能是原有的增强,而不是从本质上改变原有功能:在Reader体系中, 例如:装饰类BufferedReader,其中readLine就是增强了FileReader中read功能) 那么装饰类和被装饰类通常是同属于一个体系(具有共同的父类/父接口) */
鉴于以上我们可以把自定义缓冲区的代码优化下: (改进自定义字符读取流缓冲区)
//把MyBufferedReader修改为装饰类: package myreadline; import java.io.FileReader; import java.io.IOException; import java.io.Reader; public class MyBufferedReader2 extends Reader{//继承Reader,那么必须复写Reader中抽象方法,不然无法创建对象private Reader r;
public MyBufferedReader2(Reader r){//多态提高扩展性 this.r=r;} public String readLine()throws IOException{ StringBuilder sb=new StringBuilder(); int ch=0; while((ch=r.read())!=-1){ if(ch=='\r') continue;//读下一个进行判断 else if(ch=='\n') return sb.toString(); else sb.append((char)ch); } if(sb.length()!=0) return sb.toString(); return null; } //复写close方法 public void close()throws IOException{ r.close(); } //复写abstract int read(char[] cbuf, int off, int len) public int read(char[] cbuf, int off, int len)throws IOException{ returnr.read(cbuf,off,len);//利用了 传入对象已复写该方法 } public static void main(String[] args){ MyBufferedReader mbr=null; try{ mbr=new MyBufferedReader(new FileReader("4_MyBufferedReader.java")); String line=null; while((line=mbr.readLine())!=null) System.out.println(line); } catch(IOException e){ throw new RuntimeException("读异常"); } finally{ try{ if(mbr!=null) mbr.close(); } catch(IOException e){ throw new RuntimeException("关闭异常"); } } } }
3.BufferedReader子类LineNumberReader(相对于BufferedReader提供更强功能)
①.LineNumberReader示例:
/* 在BufferedReader下有一个子类:LineNumberReader 跟踪行号的缓冲字符输入流,进一步加强功能. 打印每行的同时可以带上行号 */ package bufferedreader; import java.io.LineNumberReader; import java.io.FileReader; import java.io.IOException; class LineNumberReaderDemo{ public static void main(String[] args){ LineNumberReader lnr=null; try{ lnr=new LineNumberReader(new FileReader("7_LineNumberReader.java")); String line=null; lnr.setLineNumber(10);//那么行号将从11开始 while((line=lnr.readLine())!=null) System.out.println(lnr.getLineNumber()+":"+line); } catch(IOException e){ e.printStackTrace(); } finally{ try{ if(lnr!=null) lnr.close(); } catch(IOException e){ e.printStackTrace(); } } } }②自定义MyLineNumberReader方法:
package mylinenumber; import java.io.Reader; import java.io.FileReader; import java.io.IOException; import myreadline.MyBufferedReader2; class MyLineNumberReader extends MyBufferedReader2{//在MyBufferedReader2已有方法,不再重复定义 private int lineNumber=0;//行号 public MyLineNumberReader(Reader r){ super(r); } //设置行号 public void mySetLineNumber(int lineNumber){ this.lineNumber=lineNumber; } //获取行号 public int myGetLineNumber(){ return lineNumber; } public String myReadLine()throws IOException{ ++lineNumber;//读一行自增一次 return super.readLine();//提高代码重用性 } } class MyLineNumberDemo{ public static void main(String[] args){ MyLineNumberReader mlnr=null; try{ mlnr=new MyLineNumberReader(new FileReader("8_MyLineNumberReader.java")); String line=null; mlnr.mySetLineNumber(100); while((line=mlnr.myReadLine())!=null) System.out.println(mlnr.myGetLineNumber()+":"+line); } catch(IOException e){ e.printStackTrace(); } finally{ try{ if(mlnr!=null) mlnr.close(); } catch(IOException e){ e.printStackTrace(); } } } }