字符流的缓冲区
1. 缓冲区的出现提高了对数据的读写效率。
2. 对应类
BufferedWriter
BufferedReader
3. 缓冲区要结合流才可以使用
4. 在流的基础上对流的功能进行了增强
缓冲区的出现是为了提高流的操作效率而出现的。所以在创建缓冲区之前,必须要先有流对象。
该缓冲区中提供了一个跨平台的换行符,newLine()方法。
BufferedWriter
import java.io.*; class day19 { public static void main(String[] args) throws IOException { //创建一个字符写入流对象 FileWriter fw = new FileWriter("buf.txt"); //为了提高字符写入流效率,加入了缓冲技术 //只要将需要被提高效率的流对象作为参数传递给缓冲区的构造函数即可 BufferedWriter bufw = new BufferedWriter(fw); for(int x = 1; x <= 4; x ++) { bufw.write("abcde" + x); bufw.newLine(); bufw.flush(); } //记住,只要用到缓冲区,就要记得刷新 //bufw.flush(); //其实关闭缓冲区,就是在关闭缓冲区中的流对象,不用写fw.close() bufw.close(); } }
字符读取缓冲区BufferedReader
该缓冲区提供了一个一次读一行的方法readLine,方便于对文本数据的获取。当返回null时,表示读到文件末尾。readLine方法返回的时候只返回回车符之前的数据内容,并不返
回已回车符。
import java.io.*; class day19 { public static void main(String[] args) throws IOException { //创建一个读取流对象和文件相关联 FileReader fr = new FileReader("buf.txt"); //为了提高效率,加入缓冲技术,将字符读取流对象作为 //参数传递给缓冲对象的构造函数 BufferedReader bufr = new BufferedReader(fr); String line = null; while((line = bufr.readLine()) != null) { sop(line); } bufr.close(); } public static void sop(Object obj) { System.out.println(obj); } }
练习:通过缓冲区复制一个.java文件
import java.io.*; class day19 { public static void main(String[] args) { BufferedReader bufr = null; BufferedWriter bufw = null; try { bufr = new BufferedReader(new FileReader("buf.txt")); bufw = new BufferedWriter(new FileWriter("buf_copy.txt")); String line = null; while((line = bufr.readLine()) != null) { bufw.write(line); bufw.newLine(); bufw.flush(); } } catch (IOException e) { throw new RuntimeException("读写失败"); } finally { try { if(bufr != null) bufr.close(); } catch (IOException e) { throw new RuntimeException("关闭失败"); } try { if(bufw != null) bufw.close(); } catch (IOException e) { throw new RuntimeException("关闭失败"); } } } public static void sop(Object obj) { System.out.println(obj); } }
readLine方法的原理:无论是读一行,还是读取多个字符。其实最终都是在硬盘上一个一个读取。所以最终使用的还是read方法一次读一个的方法。
明白了BufferedReader类中特有方法readLine的原理后,可以自定义一个类中包含一个功能和readLine一致的方法来模拟一下BufferedReader
import java.io.*; class MyBufferedReader { private FileReader r; MyBufferedReader(FileReader r) { this.r = r; } //可以一次读一行的方法 public String myReadLine() throws IOException { //定义一个临时容器,原BufferedReader封装的是字符数组. //为了演示方便,定义一个StringBuilder容器,因为最终还是要讲 //数据变成字符串 StringBuilder sb = new StringBuilder(); int ch = 0; while((ch = r.read()) != -1) { if(ch == '\r') continue; if(ch == '\n') return sb.toString(); sb.append((char)ch); } if(sb.length() != 0)//如果最后一行没有回车换行 return sb.toString(); return null; } public void myClose() throws IOException { r.close(); } } class day19 { public static void main(String[] args) throws IOException { FileReader fr = new FileReader("buf.txt"); MyBufferedReader myBuf = new MyBufferedReader(fr); String line = null; while((line = myBuf.myReadLine()) != null) { sop(line); } myBuf.close(); } public static void sop(Object obj) { System.out.println(obj); } }
装饰设计模式:
当想要对已有的对象进行功能增强时,可以定义类,将已有对象传入,基于已有对象的功能,并提供加强功能。那么自定义的该类就称为装饰类。
装饰类通常会通过构造方法接收被装饰的对象。并基于被装饰的对象的功能,提供更强的功能。
BufferedReader 是 FileReader 的装饰类,提供比FileReader更强的功能,可以一次读一行。
比如
MyReader//专门用来读取数据的类
|--MyTextReader
|--MyBufferTextReader
|--MyMediaReader
|--MyBufferMediaReader
继承的话很臃肿
优化
class MyBufferReader
{
MyBufferReader(MyTextReadertext)
{}
MyBufferReader(MyMediaReadermdia)
{}
}
上面这个类扩展性很差,找到其参数的共同类型,通过多态的形式,可以提高扩展性。
class MyBufferReader extends MyReader
{
privateMyReader r;
MyBufferReader(MyReaderr)
{}
}
原来的体系变为:
MyReader//专门用来读取数据的类
|--MyTextReader
|--MyMediaReader
|--MyBufferReader
装饰模式比继承要灵活,避免了继承体系的臃肿。而且降低了类与类之间的关系。
装饰类因为增强已有对象,具备的功能和已有对象是形同的,只不过是提供了更强的功能,所以装饰类和被装饰类通常都属于一个体系中。
原来自定义写的MyBufferedReader 改为:
class MyBufferedReader extends Reader//改动1 { private Reader r;//改动2 MyBufferedReader(Reader r)//改动3 { this.r = r; } //可以一次读一行的方法 public String myReadLine() throws IOException { //定义一个临时容器,原BufferedReader封装的是字符数组. //为了演示方便,定义一个StringBuilder容器,因为最终还是要讲 //数据变成字符串 StringBuilder sb = new StringBuilder(); int ch = 0; while((ch = r.read()) != -1) { if(ch == '\r') continue; if(ch == '\n') return sb.toString(); sb.append((char)ch); } if(sb.length() != 0)//如果最后一行没有回车换行 return sb.toString(); return null; } public void myClose() throws IOException { r.close(); } //覆盖Reader类中的抽象方法//改动4 public void close() throws IOException { r.close(); } public int read(char[] cbuf, int off, int len)throws IOException { return r.read(cbuf, off, len); } }
另一个装饰类LineNumberReader 可以对行号进行操作
import java.io.*; class day19 { public static void main(String[] args) throws IOException { FileReader fr = new FileReader("buf.txt"); LineNumberReader lnr = new LineNumberReader(fr); String line = null; lnr.setLineNumber(100);//设置行号从100开始 while((line = lnr.readLine()) != null) { sop(lnr.getLineNumber()+":"+line); } } public static void sop(Object obj) { System.out.println(obj); } }
输出:
101:abcde1
102:abcde2
103:abcde3
104:abcde4
练习:自己写一个类实现LineNumberReader的功能
class MyLineNumberReader { private Reader r; private int lineNumber; MyLineNumberReader(Reader r) { this.r = r; } public String myReadLine()throws IOException { lineNumber ++; StringBuilder sb = new StringBuilder(); int ch = 0; while((ch = r.read()) != -1) { if(ch == '\r') continue; if(ch == '\n') return sb.toString(); else sb.append((char)ch); } if(sb.length() != 0) return sb.toString(); return null; } public void setLineNumber(int lineNumber) { this.lineNumber = lineNumber; } public int getLineNumber() { return lineNumber; } public void myClose() throws IOException { r.close(); } }
有些方法我们在前面的MyBufferedReaderl里面写过了,所以进行继承优化
class MyLineNumberReader extends MyBufferedReader { private int lineNumber; MyLineNumberReader(Reader r) { super(r); } public String myReadLine()throws IOException { lineNumber ++; return super.myReadLine(); } public void setLineNumber(int lineNumber) { this.lineNumber = lineNumber; } public int getLineNumber() { return lineNumber; } }
字符流:
FileReader
FileWriter
BufferedReader
BufferedWriter
字节流:
InputStream
OutputStream
BufferedInputStream
BufferedOutputStream
基本读写:
import java.io.*; class day19 { public static void main(String[] args) throws IOException { readFile_3(); } public static void readFile_3() throws IOException { FileInputStream fis = new FileInputStream("fos.txt"); //int num = fis.available(); byte[] buf = new byte[fis.available()];//定义一个刚刚好的缓冲区 fis.read(buf); //不用再循环了,但是这种方式慎用,超大文件有风险 sop(new String(buf)); fis.close(); } public static void readFile_2() throws IOException { FileInputStream fis = new FileInputStream("fos.txt"); byte[] buf = new byte[1024]; int len = 0; while((len = fis.read(buf)) != -1) { sop(new String(buf, 0, len)); } fis.close(); } public static void readFile_1() throws IOException { FileInputStream fis = new FileInputStream("fos.txt"); int ch = 0; while((ch = fis.read()) != -1) { sop((char)ch); } fis.close(); } public static void writeFile() throws IOException { FileOutputStream fos = new FileOutputStream("fos.txt"); fos.write("abcde".getBytes()); //不需要刷新 fos.close(); } public static void sop(Object obj) { System.out.println(obj); } }
需求:想要操作图片数据,这时就要用到字节流。
思路:
1. 用字节读取流对象和图片关联。
2. 用字节写入流对象创建一个图片文件,用于存储获取到的图片数据
3. 通过循环读写,完成数据的存储。
4. 关闭资源
import java.io.*; class day19 { public static void main(String[] args) { FileOutputStream fos = null; FileInputStream fis = null; try { fos = new FileOutputStream("d:\\2.jpg"); fis = new FileInputStream("d:\\1.jpg"); byte[] buf = new byte[1024]; int len = 0; while((len = fis.read(buf)) != -1) { fos.write(buf, 0, len); } } catch (IOException e) { throw new RuntimeException("复制文件失败"); } finally { try { if(fis != null) fis.close(); } catch (IOException e) { throw new RuntimeException("读取关闭失败"); } try { if(fos != null) fos.close(); } catch (IOException e) { throw new RuntimeException("写入关闭失败"); } } } public static void sop(Object obj) { System.out.println(obj); } }
通过缓冲区,进行mp3的复制。
两种方法。
import java.io.*; class MyBufferedInputStream { private InputStream in; private byte[] buf = new byte[1024]; private int pos = 0, count = 0; MyBufferedInputStream(InputStream in) { this.in = in; } //一次读一个字节,从缓冲区(字节数组)获取。 public int myRead() throws IOException { //通过in对象读取硬盘上数据,并存储buf中 if(count == 0)//只有获取完,才重新抓数据到buf中 { count = in.read(buf); if(count < 0) return -1; pos = 0; byte b = buf[pos]; count --; pos ++; return b; } else if(count > 0) { byte b = buf[pos]; count --; pos ++; return b & 0xff;//&255 为什么?看下面注解 } return -1; } public void myClose()throws IOException { in.close(); } } class day19 { public static void main(String[] args)throws IOException { long start = System.currentTimeMillis(); copy_2(); long end = System.currentTimeMillis(); sop((end - start)+"ms"); } //通过字节流的缓冲区完成复制 public static void copy_1() throws IOException { BufferedInputStream bufis = new BufferedInputStream(new FileInputStream("Wish.mp3")); BufferedOutputStream bufos = new BufferedOutputStream(new FileOutputStream("Wish2.mp3")); int by = 0; while((by = bufis.read()) != -1) { bufos.write(by);//强制转化,只留by的后8位,一个字节 } bufos.close(); bufis.close(); } public static void copy_2() throws IOException { MyBufferedInputStream bufis = new MyBufferedInputStream(new FileInputStream("Wish.mp3")); BufferedOutputStream bufos = new BufferedOutputStream(new FileOutputStream("Wish2.mp3")); int by = 0; //sop("第一个字节:"+bufis.myRead()); while((by = bufis.myRead()) != -1) { bufos.write(by); } bufos.close(); bufis.myClose(); } public static void sop(Object obj) { System.out.println(obj); } } /* 自定义类中 myRead()返回int 每次读一个字节,8位 如果读到的8位是11111111 byte : -1 --->提升成 int : -1 也是-1, 那么如果返回-1,就结束了,不会再向下读取 11111111提升了一个int类型,还是-1,是-1的原因是因为在8个1前面 补的是1导致的。那么只要在前面补0,既可以保留原字节数据不变,又可以避免-1的出现。 怎么补0? &255就可以了 */
需求:读取键盘录入
System.out: 对应的是标准输出设备,控制台。
System.in: 对应的是标准输入设备,键盘。
InputStream in = System.in; int by = in.read();
需求:
通过键盘录入数据。
当录入一行数据后,就将该行数据进行打印。
如果录入的数据是over,那么停止录入.
import java.io.*; class day19 { public static void main(String[] args) throws IOException { InputStream in = System.in; StringBuilder sb = new StringBuilder(); while(true) { int ch = in.read(); if(ch == '\r') continue; if(ch == '\n') { String s = sb.toString(); if("over".equals(s)) break; sop(s); sb.delete(0, sb.length());//清空缓冲区 } else sb.append((char)ch); } } public static void sop(Object obj) { System.out.println(obj); } }
通过刚才的键盘录入一行数据并打印,发现其实就是读一行数据的原理。也就是readLine方法。能不能直接使用readLine方法来完成键盘录入的一行数据的读取呢?
readLine方法是字符流BufferedReader类中的方法
而键盘录入的read方法是字节流InputStream的方法
那么能不能将字节流转成字符流,然后使用字符流缓冲区的readLine方法呢?
字节流转成字符流的桥梁 InputStreamReader
字符流转向字节流的桥梁OutputStreamWriter
import java.io.*; class day19 { public static void main(String[] args) throws IOException { //获取键盘录入对象 //InputStream in = System.in; //将字节流对象转成字符流对象,使用转换流InputStreamReader //InputStreamReader isr = new InputStreamReader(in); //为了提高效率,将字符流进行缓冲区技术高效操作,使用BufferedReader装饰一下 //BufferedReader bufr = new BufferedReader(isr); //以上三句话换成一句话 //键盘录入的最常见写法 BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in)); //OutputStream out = System.out; //OutputStreamWriter osw = new OutputStreamWriter(out); //BufferedWriter bufw = new BufferedWriter(osw); BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out)); String line = null; while((line = bufr.readLine()) != null) { if("over".equals(line)) break; bufw.write(line); bufw.newLine(); bufw.flush(); } bufr.close(); } public static void sop(Object obj) { System.out.println(obj); } }
上面的例子是
1. 源:键盘录入
2. 目的: 控制台
新需求:把键盘录入的数据存储到一个文件中。
1. 源: 键盘录入。
2. 目的: 文件。
public static void main(String[] args) throws IOException { //以上三句话换成一句话 //键盘录入的最常见写法 BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in)); BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("1.txt"))); String line = null; while((line = bufr.readLine()) != null) { if("over".equals(line)) break; bufw.write(line); bufw.newLine(); bufw.flush(); } bufr.close(); }
新需求:将一个文件的数据打印在控制台上。
源:文件
目的:控制台
import java.io.*; class day19 { public static void main(String[] args) throws IOException { //以上三句话换成一句话 //键盘录入的最常见写法 BufferedReader bufr = new BufferedReader(new InputStreamReader(new FileInputStream("1.txt"))); BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out)); String line = null; while((line = bufr.readLine()) != null) { if("over".equals(line)) break; bufw.write(line); bufw.newLine(); bufw.flush(); } bufr.close(); } public static void sop(Object obj) { System.out.println(obj); } }
就是改了两句话。
流操作的基本规律:
最痛苦的就是流对象有很多,不知道该用哪一个。
通过3个明确来完成。
1. 明确源和目的。
源:输入流。InputStreamReader
目的:输出流。OutputStreamWriter
2. 明确操作的数据是否是纯文本。
是:字符流。
不是:字节流。
3. 当体系明确后,再明确要使用哪个具体的对象。
通过设备来进行区分:
源设备:内存,硬盘(文件),键盘。
目的设备:内存,硬盘(文件),控制台
①. 将一个文本文件中数据存储到另一个文件,复制文件。
源:因为是源,所以使用读取流。InputStream Reader
是不是操作文本文件?是:这时可以选择Reader
这样体系明确了。
接下来明确要使用该体系中的哪个对象。
明确设备:硬盘,一个文件。
Reader体系中,可以操作文件的对象是 FileReader
是否需要提高效率?是:加入Reader体系中的缓冲区BufferedReader
FileReaderfr = new FileReader(“a.txt”);
BufferedReaderbufr = new BufferedReader(fr);
目的:OutputStreamWriter
是否是纯文本?是:这时可以选择Writer
明确设备:硬盘,一个文件。
Writer体系中可以操作文件的对象FileWriter
是否需要提高效率?是:加入Writer体系中的缓冲区BufferedWriter
FileWriterfw = new FileWriter(“b.txt”);
BufferedWriterbufw = new BufferedWriter(fw);
②. 将键盘录入的数据保存到一个文件中。
这个需求中有源和目的都存在。分别分析。
源: InputStreamReader
是不是纯文本?是:用Reader
设备:键盘(字节流)。对应的对象是System.in
不是选择Reader吗?System.in对应的不是字节流吗?为了操作键盘的文本数据方便,
转成字符流,按照字符串操作是最方便的。所以既然明确了Reader,那么久将System.in 转成字符流Reader,用到了Reader体系中的转换流InputStreamWriter
InputStreamReaderisr = new InputStreamReader(System.in);
需要提高效率吗?需要:BufferedReader
BufferedReaderbufr = new BufferedReader(isr);
目的:OutputStream Writer
是否是纯文本?是:用Writer
设备:硬盘,一个文件。使用FileWriter
FileWriterfw = new FileWriter(“c.txt”);
需要提高效率吗?需要:
BufferedWriterbufw = new BufferedWriter(fw);
****************
扩展:想要把录入的数据按照指定的编码表(比如utf-8)存到文件中。
目的:OutputStream Writer
是否是纯文本?是:用Writer
设备:硬盘,一个文件。使用FileWriter
但是FileWriter使用的是默认编码表GBK。
但是,存储时需要加入指定的编码表,而指定的编码表只有转换流可以指定。
所以要是用的对象是OutputStreamWriter.而该转换流对象要接收一个字节输出流。而且还可以操作文件的字节输出流。
OutputStreamWriterosw = new OutputStreamWriter(new FileOutputStream(“d.txt”, “UTF-8”));
需要高效吗:需要:
BufferedWriterbufw = new BufferedWriter(osw);
所以,记住。转换流什么时候使用。字符和字节之间的桥梁,通常,涉及到字符编码转换时,需要用到转换流。
改变源和目的:
System.setIn(newFileInputStream("1.txt")); System.setOut(newPrintStream("2.txt"));
小实例:将异常信息保存到文件中
import java.io.*; import java.util.*; import java.text.*; class day19 { public static void main(String[] args) throws IOException { try { int[] arr = new int[2]; sop(arr[3]); } catch (Exception e) { try { Date d = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String s = sdf.format(d); PrintStream ps = new PrintStream("exception.log"); ps.println(s); System.setOut(ps); } catch (IOException ex) { throw new RuntimeException("日志文件创建失败"); } e.printStackTrace(System.out); } } public static void sop(Object obj) { System.out.println(obj); } }
Properties prop = System.getProperties(); prop.list(System.out);//将信息输出到控制台 prop.list(new PrintStream("sys.txt"));//将信息输出到文件中