黑马程序员 —— Java高级视频_IO输入与输出(第十九天)1

来源:互联网 发布:树莓派和单片机的区别 编辑:程序博客网 时间:2024/05/19 01:59

------- android培训、java培训、期待与您交流! ----------

一    BufferedWriter


1、初步认识BufferedWriter


API文档:public class BufferedWriter extends Writer  将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。


视频笔记:

  • 缓冲区的出现提高了对数据的读写效率。

  • 对应类:BufferedWriter、BufferedReader

  • 缓冲区要结合流才可以使用。

  • 在流的基础上对流的功能进行了加强。

因此,在创建缓冲区之前,必须要先有流对象(原理其实就是加入了数组)。

之前的操作就像一滴一滴地喝水,而使用缓冲区后,就像用杯子喝水。


方法摘要:

void close()  关闭此流,但要先刷新它。

void flush() 刷新该流的缓冲

void newLine() 写入一个行分隔符。 跨平台

newLine()是跨平台的换行方法,不管Linux系统还是Windows系统,都可以换行。如果写\r\n到linux就无效了。


2、示例代码

import java.io.*;public class BufferedWriterDemo {public static void main(String[] args) throws IOException {// 创建一个字符写入流对象。FileWriter fw = new FileWriter("demo.txt");// 为了提高字符写入流效率,加入缓冲技术。// 只要将需要被提高效率的流对象作为参数传递给缓冲区的构造函数即可。BufferedWriter bw = new BufferedWriter(fw);bw.write("sdfsdfsdf");// 记住,只要用到缓冲区,就要记得刷新。bw.flush();// 其实关闭缓冲区,就是在关闭缓冲区中的流对象,所以fw.close();不用写了。bw.close();}}

上面的程序,开始我认为FileWriter自己的wrtie()方法也能完成操作,完全需要用BufferedWriter啊?

但是,结合API文档和源码,就知道BufferedWriter在它的构造方法,帮传入的流做了不少事情。

通常 Writer 将其输出立即发送到底层字符或字节流。除非要求提示输出,否则建议用 BufferedWriter 包装所有其 write() 操作可能开销很高的 Writer(如 FileWriters 和 OutputStreamWriters)。例如, PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter("foo.out")));将缓冲 PrintWriter 对文件的输出。如果没有缓冲,则每次调用 print() 方法会导致将字符转换为字节,然后立即写入到文件,而这是极其低效的。 
提高了效率。


二    BufferedReader


API文档:

public class BufferedReader extends Reader

从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。


  • BufferedReader提供了一个一次读一个文本行的方法 readLine(),方便于对文本数据的获取。
  • readLine()的返回:包含该行内容的字符串,不包含任何行终止符。如果已到达流末尾则返回 null 。
  • 注意readLine()方法返回的时候,只返回回车符之前的数据内容,并不返回回车符。
import java.io.*;public class BufferedReaderDemo {/* * 字符读取流缓冲区: 该缓冲区提供了一个一次读一行的方法,方便于对文本数据的获取。  * 当返回null时,表示读到文件末尾。 */public static void main(String[] args) throws IOException {// 创建一个读取流对象和文件相关联。FileReader fr = new FileReader("c:\\demo.txt");// 为了提高效率。加入缓冲技术。将字符读取流对象作为参数传递给缓冲对象的构造函数。BufferedReader br = new BufferedReader(fr);/* * char[] buf = new char[1024]; int index = 0; *  * while((index=br.read(buf))!=-1){ System.out.println(new * String(buf,0,index)); } */String str = null;while ((str = br.readLine()) != null) {System.out.println(str);}}}

三    通过缓冲区复制文本文件

练习:通过缓冲区复制一个.java文件

import java.io.*;class Demo {public static void main(String args[]) throws IOException {BufferedReader bufr = null;BufferedWriter bufw = null;try {bufr = new BufferedReader(new FileReader("Demo.java"));bufw = new BufferedWriter(new FileWriter("Demo.txt"));// 每行数据临时变量String line = null;// 循环写入每行数据while ((line = bufr.readLine()) != null) {bufw.write(line);bufw.flush();bufw.newLine();// 必须写}} 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("写关闭失败");}}}}

四    readLine的原理图例





readLine()无论是读一行,或者是获取多个字符,其实最终都是在硬盘上一个一个读取,

所以最终使用的还是 read() 方法一次读一个的方法。

具体做法就是:在内存中有个数组,先把数据存到数组(把数据临时存起来),当读到\r,就不会再读了,又读到\n,就判断这一行结束。

原来的数据 → 一行的数据放到数组 → 转为字符串 → 返回该字符串


“因为跨平台性,换行符不一样,所以读取的时候缓存中不存入换行,让使用者自己输入换行。


五    MyBufferedReader


明白了 BufferedReader 类中特有方法 readLine 的原理后,

可以自定义与readLine()功能一致的方法,来模拟一下BufferedReader。


import java.io.*;class Demo {public static void main(String args[]) throws IOException {FileReader fr = new FileReader("Demo.java");MyBufferedReader myBuf = new MyBufferedReader(fr);String line = null;while ((line = myBuf.myReadLine()) != null) {System.out.println(line);}myBuf.myClose();}}class MyBufferedReader {private FileReader fr;MyBufferedReader(FileReader fr) {this.fr = fr;}// 可以一次读一行数据的方法public String myReadLine() throws IOException {// 定义一个临时容器,原BufferedReader封装的是字符数组// 为了方便,定义一个StringBuilder容器,因为最终还是要将数据变成字符串StringBuilder sb = new StringBuilder();int ch = 0;while ((ch = fr.read()) != -1) {if (ch == '\r')continue;if (ch == '\n')return sb.toString();elsesb.append((char) ch);}// 防止最后一行没有换行的情况if (sb.length() != 0) {return sb.toString();} else {return null;}}public void myClose() throws IOException {fr.close();}}

六    装饰设计模式


可以上面的myReadLine()方法可知,readLine()的出现,增强了read方法的作用。

在BufferedReader中将FileReader对象传入,并对其原有的read()方法功能进行增强。

这时,BufferedReader就是装饰类,用于装饰FileReader。


装饰设计模式:

当想要对已有的对象进行功能增强时,可以定义类,

将已有对象传入,基于已有功能,并提供增强功能。

那么自定义的该类,就称为装饰类。


class Person {   //类比FileReader    public void chifan() {        System.out.println("吃饭");    }}class SuperPerson {  //类比BufferedReader    private Person p;    public SuperPerson(Person p) {        this.p = p;    }    public void super_chifan() {        System.out.println("喝汤");        p.chifan();        System.out.println("甜品");    }}public class Demo {    public static void main(String[] args) {        SuperPerson sp = new SuperPerson(new Person());        sp.super_chifan();    }}

通常Person和superPerson会同属于一个接口或者一个类。
装饰类通常会通过构造方法接收被装饰的对象,并基于被装饰的对象的功能,提供更强的功能。

七    装饰和继承的区别


1、通过实例比较装饰和继承


那么问题来了,SuperPerson继承Person后,不一样可以达到增强功能的目的吗?

有必要用装饰设计模式这么麻烦吗?装饰和继承有何区别?


现在定义一个MyReader类专门用于读取各种类型数据(文字、媒体、数据等),抽取后形成继承体系,

为提高MyReader类各个子类读取效率,又通过缓冲技术进行增强,最后就会形成下面这样的继承体系:


MyReader //专门用于读取数据的类

    |---- MyTextReader

        |----MyBufferTextReader

    |----MyMediaReader

        |----MyBufferTextReader

    |----MyDateReader

        |----MyBufferTextReader


这样的体系可以存在,但后期如果需要增强功能继续进行扩展,体系最后变得非常臃肿并且扩展性不好,因此要优化体系。


我们就会想到:如果所用的都是缓冲技术,可不可以要增强谁,就把谁增强呢?

由此,单独定义一个MyBufferedReader用于为别人提高效率,这样不管是文本还是媒体等,

都可以使用该类进行增强,代码如下:

class MyBufferReader {MyBufferReader(MyMediaReader media) {}MyBufferReader(MyTextReader text) {}}

可以预见上面这个类的扩展性会很差,因此再进行修改。

找到其参数的共同类型,通过多态的形式,可以提高扩展性。修改如下:


class MyBufferReader {MyBufferReader(MyReader r) {}}


最后,优化得出下面的体系:

MyReader

    |------MyTextReader

    |------MyMediaReader

    |------MyDateReader

    |------MyBufferReader


2、装饰和继承有何区别?


装饰模式比继承灵活,避免了继承体系的臃肿。而且降低了类与类之间的关系。

装饰类因为增强已有对象,具备的功能和已有的相同,只不过提供了更强的功能。

所以装饰类和被装饰类通常都是属于一个体系中的。


八    自定义装饰类


import java.io.*;public class MyBufferedReader{public static void main(String[] args) throws IOException {FileReader fr = new FileReader("c:\\demo.txt");MyBufferedReader1 br = new MyBufferedReader1(fr);String Line = null;while ((Line = br.MyReadLine()) != null) {System.out.println(Line);}}}class MyBufferedReader1 extends Reader {private FileReader r;public MyBufferedReader1(FileReader r) {this.r = r;}public String MyReadLine() throws IOException {StringBuilder sb = new StringBuilder();char ch = 0;while ((ch = (char) r.read()) != -1) {if (ch == '\r')continue;if (ch == '\n')return sb.toString();elsesb.append(ch);}if (sb.length() != 0)return sb.toString();return null;}/* * 覆盖Reader类中的抽象方法 */@Overridepublic int read(char[] cbuf, int off, int len) throws IOException {return r.read(cbuf, off, len);}@Overridepublic void close() throws IOException {r.close();}}


注意:extends Reader 后,要把Reader中的抽象方法也覆盖


九    LineNumberReader


BufferedReader的子类LineNumberReader,它是可以跟踪行号的缓冲字符输入流。
此类定义了方法 setLineNumber(int) 和 getLineNumber(),它们可分别用于设置和获取当前行号。

import java.io.*;public class LineNumberReaderDemo {public static void main(String[] args) throws IOException {FileReader fr = new FileReader("C:\\demo.txt");LineNumberReader lnr = new LineNumberReader(fr);String line = null;while ((line = lnr.readLine()) != null) {System.out.println(lnr.getLineNumber() + " : " + line);}}}

十    MyLineNumberReader


练习:模拟一个带行号的缓冲区对象

import java.io.BufferedReader;import java.io.FileReader;import java.io.IOException;class Demo {public static void main(String args[]) throws IOException {FileReader fr = new FileReader("Demo.java");myLineNumberReader lnr = new myLineNumberReader(fr);String line = null;while ((line = lnr.myReadLine()) != null) {System.out.println(lnr.myGetLineNumber() + ":" + line);}lnr.close();}}class myLineNumberReader {private BufferedReader br = null;private int lineNumber = 0;myLineNumberReader(FileReader fr) {br = new BufferedReader(fr);}public void lineNumberadd() {lineNumber++;}public int myGetLineNumber() {return lineNumber;}public String myReadLine() throws IOException {lineNumberadd();return br.readLine();}public void close() throws IOException {br.close();}}

十一    字节流File读写操作


以上的内容,已经讲完了字符流主要的几个类:FileReader、FileWriter;BufferedReader、BufferedWriter。

现在开始来了解字节流,InputStream和OutputStream。


  • 想要操作图片数据。这时就要用到字节流。
  • 字符流使用字符数组char[],字节流使用字节数组byte[]。
  • 注意:字符流一样走的字节,但它需要把字节临时存起来,中文两个字节,读完一半字节,先缓冲区存起来,再读另外一半字节,才组成一个完整的字,因此需要flush操作。而字节流就不需要进行flush操作。


示例代码:

import java.io.*;public class FileInputStreamDemo {public static void main(String[] args) throws IOException {// Method1();Method2();// Method3();}/* * public static void Method1() throws IOException{  * FileInputStream fis =new FileInputStream("C:\\demo.txt");  * byte by = 0 ; while((by=(byte) * fis.read())!=-1){  * System.out.println((char)by);  * } fis.close();  * } */public static void Method2() throws IOException {FileInputStream fis = new FileInputStream("C:\\demo.txt");byte[] by = new byte[1024];int len = 0;while ((len = fis.read(by)) != -1) {System.out.println(new String(by, 0, len));}}/* * public static void Method3() throws IOException{  * FileInputStream fis = new FileInputStream("C:\\demo.txt"); *  byte[] by = new byte[fis.available()]; *  fis.read(by);  *  System.out.println(new String(by)); * } */}


上面有三种字节流读取文件的方式,建议使用Method2()方法中的方式。


十二    拷贝图片


需求:通过字节流复制一个图片


思路:

  1. 用字节读取流对象和图片关联

  2. 用字节写入流对象创建一个图片文件。用于存储获取到的图片数据

  3. 通过循环读写,完成数据的存储

  4. 关闭资源


示例代码:

import java.io.*;public class CopyPic {public static void main(String args[]) {FileOutputStream fos = null;FileInputStream fis = null;try {fos = new FileOutputStream("CopyPic.jpg");fis = new FileInputStream("C:\\CopyPic.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 class BufferedInputStream extends FilterInputStreamBufferedInputStream

  • 为另一个输入流添加一些功能,即缓冲输入以及支持 mark 和 reset 方法的能力。

  • public class BufferedOutputStream extends FilterOutputStream该类实现缓冲的输出流。

  • 通过设置这种输出流,应用程序就可以将各个字节写入底层输出流中,而不必针对每次字节写入调用底层系统。


下面,通过字节流缓冲区完成复制Mp3文件。代码如下:

import java.io.*;public class CopyMp3 {public static void main(String[] args) throws IOException {long start = System.currentTimeMillis();fuzhi();long end = System.currentTimeMillis();System.out.println("复制用了 :" + (end - start) + "毫秒");}public static void fuzhi() throws IOException {BufferedInputStream bis = new BufferedInputStream(new FileInputStream("c:\\CopySong.mp3"));BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("c:\\Beijingqingjie.mp3"));int by = 0;while ((by = bis.read()) != -1) {bos.write(by);}}}

十四    自定义字节流的缓冲区 - read和write的特点


package package2;import java.io.*;public class Demo {    public static void main(String[] args) throws IOException {        long start = System.currentTimeMillis();        copy();        long end = System.currentTimeMillis();        System.out.println((end - start) + "毫秒");    }    public static void copy() throws IOException {        MyBufferedInputStream bufis = new MyBufferedInputStream(                new FileInputStream("C:\\Copy.mp3"));        BufferedOutputStream bufos = new BufferedOutputStream(                new FileOutputStream("Copy.mp3"));        int by = 0;        while ((by = bufis.myRead()) != -1) {            bufos.write(by);        }        bufis.myclose();        bufos.close();    }}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) {            count = in.read(buf);            if (count < 0)                return -1;            pos = 0;            byte b = buf[pos];            count--;            pos++;            return b & 255;        } else if (count > 0) {            byte b = buf[pos];            count--;            pos++;            return b & 255;        }        return count;    }    public void myclose() throws IOException {        in.close();    }}

基本思路:

FileInputStream将硬盘中的数据存入内存中的缓冲区BufferedInputStream,缓冲区存满后取出,

再将下一批数据存入缓冲区,再取,循环往复,直到数据全部取出。

避免了从硬盘到硬盘,减少了硬盘的负担。

硬盘 → 内存缓冲区硬盘。通过记录缓冲区的长度来标识何时停止。

需要3个工具:数组、计数器和指针。


如果单个字节出现1111-1111,会返回-1,表示数据已经读取完毕,造成错误。

  • 0000-0000 0000-0000 0000-0000 1111-1111 是255
  • 1111-1111  1111-1111 1111-1111 1111-1111 是-1

出现-1是因为前面8个1前面补的全是1,如果全部补0,

由-1变成了255,但仍然是8个1,原字节数据不变,而且避免了-1的出现。


具体操作是,byte变成int,此时仍是-1,再&255就变成了255。先类型提升,然后再&,变成255。

读取(read)时,将byte转换成int型(4个字节);写入(write)时,强转动作,将int转换成byte,

只保留最后8位,保证数据的原样性。字符流中的读取、写入本质上也是在操作字节,只是走了编码表,所以表现的都是字符。



十五    读取键盘录入


读取键盘录入,必须掌握,首先记住:

  • System.out:对应的是标准的输出设备,控制台。
  • System.in:对应的是标准的输入设备,键盘。


很简单的示例代码:

import java.io.*;public class ReadIn {public static void main(String[] args) throws IOException {InputStream in = System.in;int by = in.read();System.out.println(by);}}


上面这个程序,会读取你从控制台输入的一个字节,例如输入A就输出65。


留意下面的输出语句:

System.out.println((int)'\r'); //13

System.out.println((int)'\n'); //10

可见回车和换行也是会被当做数据录入的


需求:

通过键盘录入数据,当录入一行数据后,就将该行数据进行打印,

如果录入数据是over,那么就停止录入。


思考:当你敲回车符的时候,进行打印,否则加入StringBuilder缓冲。


import java.io.*;public class ReadIn {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;System.out.println(s.toUpperCase());sb.delete(0, sb.length());} else {sb.append((char) ch);}}}}

Q:为什么判断结束,不可以是while((ch=in.read())!=-1)

A:  -1 是两个字节,它不会被当成一个整体来处理。  45 49


十六    读取转换流


通过刚才的键盘录入一行数据并打印其大写,发现其实就是读一行数据的原理,也就是readLine()方法。

能不能直接使用readLine方法来完成键盘录入的一行数据的读写呢?

readLine方法是BufferedReader类中的方法。而键盘录入的read方法是字节流InputStream的方法。

那么能不能将字节流转为字符流,再使用字符流缓冲区的readLine方法呢?

由此,我们引入InputStreamReader,它是是字节流通向字符流的桥梁。


示例代码:

import java.io.*;public class Demo {public static void main(String args[]) throws IOException {// 获取键盘录入对象InputStream in = System.in;// 将字节流对象转换成字符流对象,使用转换流,InputStreamReaderInputStreamReader isr = new InputStreamReader(in);// 为了提高效率,将字符进行缓冲区技术高效操作,使用bufferedReaderBufferedReader bufr = new BufferedReader(isr);String line = null;while ((line = bufr.readLine()) != null) {if (line.equals("over")) {break;}System.out.println(line.toUpperCase());}bufr.close();}}



十七    写入转换流

与InputStreamReader相对应,OutputStreamWriter则是字符流通向字节流的桥梁。

例如我们有一些文字,需要把它变成字节(录入的是字符,存硬盘的是字节),有需要OutputStreamWriter


需求:通过键盘录入数据。当录入一行数据后,就将改行数据进行打印,如果录入数据是over,则停止录入


import java.io.*;public class Demo {public static void main(String args[]) throws IOException {// 获取键盘录入对象InputStream in = System.in;// 将字节流对象转换成字符流对象,使用转换流,InputStreamReaderInputStreamReader isr = new InputStreamReader(in);// 为了提高效率,将字符进行缓冲区技术高效操作,使用bufferedReaderBufferedReader 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 (line.equals("over")) {break;}bufw.write(line.toUpperCase());bufw.newLine();bufw.flush();}bufr.close();}}

一定要记住!!!键盘录入最常见的写法:

BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));


BufferedWriter bufr = new BufferedWriter(new OutputStreamWriter(System.Out));



考虑到下部视频有关于流的总结,将第19天的博文分开。


------- android培训、java培训、期待与您交流! ----------


0 0
原创粉丝点击