day19(上)_IO流2(BufferedReaer,BufferedWriter,BufferedInputStream,BufferedOutputStream)

1.IO总结:

IO体系

IO总结:

/*
2.分析类中常用方法总结:
    InputStream和OutputStream常用方法:
      ①int available()
         返回文本中的字节数.
     
      ②public abstract int read()throws IOException
         从输入流中读取数据的下一个字节。
         返回 0 到 255 范围内的 int 字节值。
         如果因为已经到达流末尾而没有可用的字节,则返回值 -1。
        
        public abstract void write(int b)throws IOException
          将指定的字节写入此输出流。
          write 的常规协定是:向输出流写入一个字节。
          要写入的字节是参数 b 的八个低位。b 的 24 个高位将被忽略。 
        
        Q1.read为什么不返回byte,而write传入的参数为int而不是byte?不是读取一个字节/写入一个字节吗?
           这是因为文件在计算机中以二进制补码存储,
          在未读到文件结尾前读到1111 1111 1111(-1),导致读取字节提前结束.
           解决方法:
         在read方法中:
             为了避免上面那种全为1的情况,对此类型提升为int
             (32bit)
             但是即使提升,发生符号扩展(补1)也就是
             byte:                             11111111
              int:11111111 11111111 11111111 11111111
             &255:00000000 00000000 00000000 11111111
             还是-1,那么这时候在不改变原byte值的情况下,让其&255改变了
             前24bit,而不改变后8bit,此时int值为255
             返回数据范围的是:
                  00000000 00000000 00000000 00000000(0)
                              |
                  00000000 00000000 00000000 11111111(255)
        在write方法中:
            写入的是:00000000~11111111(int中byte值)
            内部发生类型强制类型转换->截断后8bit
    
    ③ public int read(byte[] b)throws IOException
        从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中。
        以整数形式返回实际读取的字节数,如果因为流位于文件末尾而没有可用的字节,
        则返回值 -1;
      
      public void write(byte[] b)throws IOException
         将 b.length个字节从指定的 byte 数组写入此输出流。
    ④ 
  关于刷新:
     public void flush()throws IOException//OutputStream中flush
        刷新此输出流并强制写出所有缓冲的输出字节。
        flush 的常规协定是:如果此输出流的实现已经缓冲了以前写入的任何字节,
        则调用此方法指示应将这些字节立即写入它们预期的目标。 
     
     对于字节写入流来说可以不用flush也会写入文件,因为
      如果没有使用具体制定缓冲区,不论什么数据都以字节操作,
      对字节这个最小单位操作,直接写入目的地.
    
   public void flush()throws IOException//BufferedOutputStream中flush
     刷新此缓冲的输出流。这迫使所有缓冲的输出字节被写出到底层输出流中。 
      
     该方法内部实现细节:
        protected byte buf[];
        protected OutputStream out;
      private void flushBuffer() throws IOException {
        if (count > 0) {
            out.write(buf, 0, count);//buf内部定义的字节缓冲区
                                     //2.将buf中的数据写入到了输出流中
            count = 0;                 
        }
    }
      
    public synchronized void flush() throws IOException {
        flushBuffer();//1.首先刷新了缓冲区
        out.flush();//3.把流中的数据刷新到文本中
      }

    ⑤
    关闭缓冲区实际上关闭的是使用缓冲区的流对象:

    //BufferedInputStream.java
       public void close() throws IOException {
        byte[] buffer;
        while ( (buffer = buf) != null) {
            if (bufUpdater.compareAndSet(this, buffer, null)) {
                InputStream input = in;
                in = null;
                if (input != null)
                    input.close();//关闭的是 使用缓冲区的输入字节流对象
                                  //关闭此输入流并释放与该流关联的所有系统资源。
                return;
            }
            // Else retry in case a new buf was CASed in fill()
        }
    }

        
*/
/*
 Reader和Writer常用方法总结:
    ①public int read()throws IOException 
         读取单个字符。    
         返回:
         作为整数读取的字符,
         范围在 0 到 65535 之间 (0x0000-0xffff)(2byte范围),
         如果已到达流的末尾,则返回 -1 
      public void write(int c)throws IOException
        写入单个字符。要写入的字符包含在给定整数值的 16(2Byte)个低位中,
        16 高位被忽略。 

    ②public int read(char[] cbuf) throws IOException
      将字符读入数组。
      返回:读取的字符数,如果已达到流的末尾,则返回-1
      public void write(char[] cbuf)throws IOException
       字符数组中内容写入流中   
     ③public void write(String str)throws IOException
       写入字符串。 
    
    ④FileReader与InputStreamReader(转换流)
      当操作文件,用字节读取流关联文件:
        public FileReader(String fileName)throws FileNotFoundException
    
    //内部实现细节:
    
    public FileReader(String fileName) throws FileNotFoundException {
        super(new FileInputStream(fileName));//内部创建了字节读取流对象
    }                                        //传给了转换流
    public InputStreamReader(InputStream in) {
        super(in);
        try {
            sd = StreamDecoder.forInputStreamReader(in, this, (String)null); // ## check lock object
        } catch (UnsupportedEncodingException e) {
            // The default encoding should always be available
            throw new Error(e);
        }
    }    
   同理FileWriter和OutputStreamWriter
   //内部实现细节:
      public FileWriter(String fileName) throws IOException {
        super(new FileOutputStream(fileName));
    } 
   public OutputStreamWriter(OutputStream out) {
        super(out);
        try {
            se = StreamEncoder.forOutputStreamWriter(out, this, (String)null);
        } catch (UnsupportedEncodingException e) {
            throw new Error(e);
        }
    }
  ⑤flush:
    public abstract void flush()throws IOException
    刷新该流的缓冲。如果该流已保存缓冲区中各种 write() 方法的所有字符,
    则立即将它们写入预期目标。
   字符流必须刷新/关闭(关闭之前会刷新),把流中的数据刷到文本中.    

*/

/*
BufferedReader,BufferedWriter,LineNumberReader
    从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。 
    缓冲区内部使用数组来实现.
BufferedReader:
    public String readLine()throws IOException
    读取一个文本行。
    通过下列字符之一即可认为某行已终止:
    换行 ('\n')、回车 ('\r') 或回车后直接跟着换行。 
    
    返回:
    包含该行内容的字符串,不包含任何行终止符,如果已到达流末尾,则返回 null
   掌握readLine方法的原理 
BufferedWriter:
   public void newLine()throws IOException
    写入一个行分隔符。行分隔符字符串由系统属性 line.separator 定义,
    并且不一定是单个新行 ('\n') 符。
    注意:在不同平台下,行分隔符可能不同,例如:windows:"\r\n" linux:"\n"
    该方法是一个跨平台方法
LineNumberReader:
    默认情况下,行编号从 0 开始。
    该行号随数据读取在每个行结束符处递增,
    并且可以通过调用 setLineNumber(int) 更改行号。
  如果行编号从0开始,文本第一行为1,第二行为2......
close方法:底层依然关闭的是使用该缓冲的流对象,并且释放用到的系统底层资源
*/

2.字节流的读取和写入方法:

package filestream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

class FileStreamDemo{
      //方法一:一个字节一个字节读
      public static void read_1(){
      FileInputStream fis=null;
      try{
       //fis=new FileInputStream("fos.txt"); 
       fis=new FileInputStream("TestUnicode.txt");
       int ch=0;
       System.out.println("read_1:");
       while((ch=fis.read())!=-1)
          System.out.println(ch);
      }
      catch(IOException e){
       e.printStackTrace();
      }
      finally{
       try{
        if(fis!=null)
          fis.close();
       }
       catch(IOException e){
        e.printStackTrace();
       }
      }
 }

     /*
    方法二:
    public int read(byte[] b)throws IOException
    */
  public static void read_2(){
      FileInputStream fis=null;
      try{
       fis=new FileInputStream("fos.txt"); 
       int bytes=0;
       byte[] b=new byte[1024];
       System.out.print("read_2:");
       while((bytes=fis.read(b))!=-1)
          System.out.println(bytes+"..."+new String(b,0,bytes));//只输出读取到的字节数
      }
      catch(IOException e){
       e.printStackTrace();
      }
      finally{
       try{
        if(fis!=null)
          fis.close();
       }
       catch(IOException e){
        e.printStackTrace();
       }
      }
 }
  /*
  public int available()throws IOException
   该方法返回了文本中字节数.
   例如:abcd,返回4
   注意一点:
   abcd
   e   abcd后有回车,返回7->\r\n各占1byte
   这种方法有个缺陷:
     如果返回值过大(读取1G(1024*1024)文件)
     那么此时new的数组过大,可能发生内存溢出
     在使用时注意
  */
 public static void read_3(){
      FileInputStream fis=null;
      try{
       fis=new FileInputStream("fos.txt"); 
       int bytes=0;
       byte[] b=new byte[fis.available()];//定义一个刚刚好的缓冲区
       fis.read(b);//一次性把数据读到b中
       System.out.println("read_3:"+new String(b));
      }
      catch(IOException e){
       e.printStackTrace();
      }
      finally{
       try{
        if(fis!=null)
          fis.close();
       }
       catch(IOException e){
        e.printStackTrace();
       }
      }
 }
 
//写入方法: 
 public static void write(){
     FileOutputStream fos=null;
     FileInputStream fis=null;
        try{
          fis=new FileInputStream("1.txt");
          fos=new FileOutputStream("fos.txt");
          int aByte=0;
          while((aByte=fis.read())!=-1)
             fos.write(aByte);
          //fos.write("abcd".getBytes());//将字符串转成字节数组写入
          /*
           这里即使不刷新/关闭,abcd依然会写入到fos.txt中,
           字符流:例如读汉字(2byte),先读一个字节临时存储(底层用的字节流的缓冲区),再读一个->查表->汉字
           字节流:如果没有使用具体制定缓冲区,不论什么数据都以字节操作,
                  对字节这个最小单位操作,直接写入目的地.
          */
        }
        catch(IOException e){
         e.printStackTrace();
       }
       finally{
        try{
         if(fos!=null)
            fos.close();//
依然需要关闭此输出流并释放与此流有关的所有系统资源
        }
        catch(IOException e){
         e.printStackTrace();
        }
       }
   }
  
  
 public static void main(String[] args){
        write();
        read_1();
        read_2();
        read_3();

   }

}

字符流读写演示

3.多媒体文件(mp3,电影...)拷贝

/*
 FileInputStream 用于读取诸如图像数据之类的原始字节流
 FileOutputStream 用于写入诸如图像数据之类的原始字节的流。
复制图片:
    思想:
    1.创建字节读取流对象关联一个图片文件
    2.创建字节写入流对象创建一个图片文件,用于存储获取到的图片数据
    3.通过循环读写,完成数据存储
    4.关资源
*/
package copypic;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
class CopyPicture{
  public static void main(String[] args){
    FileInputStream fis=null;
    FileOutputStream fos=null;
   try{
    fis=new FileInputStream("c:\\Screen.png");
    fos=new FileOutputStream(".\\ScreenCopy.png");
    byte[] bbuf=new byte[1024];
    int bytes;
    while((bytes=fis.read(bbuf))!=-1)
      fos.write(bbuf,0,bytes);
   }
   catch(IOException e){
    e.printStackTrace();
   }
   finally{
    try{
     if(fis!=null)
       fis.close();
    }
   catch(IOException e){
     e.printStackTrace();
   }
   try{
     if(fos!=null)
       fos.close();
    }
   catch(IOException e){
     e.printStackTrace();
   }
   }
  }
}
/*
不要用字符流拷贝媒体文件
读取数据和写入数据可能发生不一致
导致写入的编码和原编码不一致,拷贝失败.
*/

4.字符流和字节流的缓冲区

①BufferedWriter与BufferedReader

/*BufferedWriter为什么没有空构造函数?

    这是因为缓冲区的出现是为了提高流的操作效率而出现的
     所以在创建缓冲区之前,必须要先有流对象
     
现实生活中例子:
  用蓄水池(缓冲区)储水(流对象),如果还没有水呢,要蓄水池干啥用呢
?

  先有水
->再有蓄水池 水和水杯也是. 比喻: 水一滴一滴的滴(FileReader的read()) 我想要喝水滴一滴喝一滴(write())很不爽 那么我用个杯子(缓冲区)接的差不多() 一饮而尽很爽~ */
package bufferedwriter;
import java.io.FileWriter;
import java.io.BufferedWriter;
import java.io.IOException;

class BufferedWriterDemo{
  public static void main(String[] args){
   FileWriter fw=null;
   BufferedWriter bufw=null;
   try{
        //创建一个字符写入流对象
        fw=new FileWriter("buff.txt");     
        
        //为了提高字符写入流效率,加入缓冲技术
        //其实缓冲技术原理在缓冲里面封装了数组
        bufw=new BufferedWriter(fw);//public BufferedWriter(Writer out)
                                    //创建一个使用默认大小 输出缓冲区(8KB)的 缓冲字符输出流。
                                    
       for(int i=0;i<4;++i){
        bufw.write("coding"+i);//写入缓冲区
        bufw.newLine();
        bufw.flush();//写一次刷一次.假如停电,未刷新此时数据可能还在流(内存)中,还没有写到硬盘中.
       }
   }
   catch(IOException e){
      e.printStackTrace();   
   }
   finally{
     
     try{
     //其实关闭缓冲区,就是在关闭缓冲区中的流对象
     //真正调用底层资源,进行读写的是流对象
     
     bufw.close();//在关闭之前也会刷新缓冲
     /*
     源码:
      public void close() throws IOException {
        synchronized (lock) {
            if (out == null) {
                return;
            }
            try {
                flushBuffer();//刷新缓冲
            } finally {
                out.close();//关闭使用缓冲的流对象
                out = null;
                cb = null;
            }
        }
    }
    */
    }
    
    catch(IOException e){
     e.printStackTrace();
    }
    
   }

  }
}
/*
 关于newLine方法
     在windows里面换行为"\r\n";
     而linux里面为:"\n"
     为了实现跨平台性,保证我们把同一个程序拿到不同平台下都能换行.
     BufferedWriter中提供了一个newLine方法.
     相当于在windows版的JDK源文件中:
     封装了write("\r\n");
     linux版的
     封装了write("\n");
*/

/*
关于缓冲区理解:
如果是边读边写,就会很慢,也伤硬盘。
缓冲区(字节数组/字符数组)就是内存里的一块临时存储数据的区域,把数据先存到缓冲区中,然后写入硬盘.
*/

/*
 BufferedReader(缓冲字符输入流):
    从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和 行 的高效读取。
*/
package bufferedreader;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

class BufferedReaderDemo{
    public static void main(String[] args){
     
     //创建一个字符读取流对象和文件相关联
       BufferedReader bufr=null;
       try{
          
        //依然为了提高效率,加入缓冲技术,
        //将字符读取流对象作为参数传递给缓冲区对象的构造函数
        bufr=new BufferedReader(new FileReader("buff.txt"));
        String readStr;
        while((readStr=bufr.readLine())!=null)//从缓冲区中一行一行读,直到文件流末尾
            System.out.println(readStr);//打印字符串。如果参数为 null,则打印字符串 "null"。否则,
 //按照平台的默认字符编码将字符串的字符转换为字节,并完全以write(int)方法的方式写入这些字节。
        }
       catch(IOException e){
        e.printStackTrace();
       }
       finally{
          try{
            if(bufr!=null)
              bufr.close();   
          
          }
          catch(IOException e){
           e.printStackTrace();
          }
        }
    }

}

利用字符流缓冲区复制文件:

/*
通过缓冲区复制一个.java文件
其实就是BufferedReader与BufferedWriter综合运用
*/
package copy;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
class CopyTest{
  
  public static void readWrite(BufferedReader bufr,BufferedWriter bufw){
     try{
          String line=null;
          while((line=bufr.readLine())!=null){
            bufw.write(line);
            bufw.newLine();
            bufw.flush();
          }
      }
      catch(IOException e){
          e.printStackTrace();
      }
  } 
  
  public static void copy(String file,String destPath){
 
  BufferedReader bufr=null;    
  BufferedWriter bufw=null;
  try{
    bufr=new BufferedReader(new FileReader(file));
   
    if(file.indexOf('\\')==-1){//当前目录下的文件,也就是说可以不用写 .\\文件
       bufw=new BufferedWriter(new FileWriter(destPath+file));
       readWrite(bufr,bufw);
    }
    else{//需要获取路径中的文件名
     String[] fileName = file.split("\\\\");
     bufw=new BufferedWriter(new FileWriter(destPath+fileName[fileName.length-1]));
     readWrite(bufr,bufw);
    }
   
  }
  catch(IOException e){
   e.printStackTrace();
  }
  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 main(String[] args){
      
  copy("3_CopyTest.java","d:\\");
 }
}
/*
readLine原理:
 画个示意图.
拷贝过程原理图
*/

readLine原理图:

    readLine原理

以上拷贝过程示意图:

读写缓冲区

②BufferedInputStream与BufferedOutputStream

/*
字节流两个缓冲区:
 BufferedInputStream
    BufferedInputStream为另一个输入流添加一些功能,
    即缓冲输入以及支持 mark 和 reset 方法的能力。
    在创建BufferedInputStream 时,会创建一个内部缓冲区数组。
 BufferedOutputStream
    该类实现缓冲的输出流。
    通过设置这种输出流,应用程序就可以将各个字节写入底层输出流中,
    而不必针对每次字节写入调用底层系统。 
*/
package copymp3;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
class ByteBuffered{
 /*
  方法一:使用int read()
              从此输入流中读取下一个数据字节。
              返回一个 0 到 255 范围内的 int 字节值
           void write(int b)
             将指定的字节写入此缓冲的输出流。
 */
  public static void copy_1()throws IOException{
   BufferedInputStream bis=new BufferedInputStream(new FileInputStream("E:\\SONG\\Bandari - 爱尔兰风笛.mp3"));
   BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream(".\\copy.mp3")); 
   int aByte;
   while((aByte=bis.read())!=-1)
      bos.write(aByte);
   bis.close();
   bos.close();
  }
  

 public static void main(String[] args)throws IOException{
  long start,end;   
  start=System.currentTimeMillis();
   copy_1();
  end=System.currentTimeMillis();
  System.out.println((end-start)+"ms");//计算下拷贝花费时间
  /*
  这里可以回顾下设计模式:模板方法模式(继承(下)中已阐述)
 */
 }
}
原文地址:https://www.cnblogs.com/yiqiu2324/p/3085362.html