5. 1 IO流(字符流(6個),字節流(4個),(緩衝區裝飾類(裝飾設計模式)),(轉換流))

1.輸入流,輸出流,字節流,字符流

1.IO流用來處理設備之間的數據傳輸。
    設備:硬盤(速度慢),內存(速度快)
    傳輸:輸入(硬盤(外設)->內存),動作:讀
         輸出(內存->硬盤(外設)),動作:寫
2.Java對數據的操作是通過流的方式。
3.Java用於操作流的對象都在IO包中。
4.流按 流向          分爲兩種:輸入流,輸出流。
  流按 操作的數據類型 分爲兩種:字節流,字符流。

5.字符流(後來出現的):unicode碼錶的出現,不論什麼字符都用兩個字符表示,一箇中文在不同的碼錶中表示的不同。所以就將讀取數據的字節流和編碼表相結合,封裝成字符流。就是字節流讀取文字字節數據後,不直接操作而是先查指定的編碼表,獲取對應的文字。
字符流 = 字節流 + 編碼表。

2 . IO包功能:讀,寫。

1.IO體系中有4個頂層基類:
字節流的抽象基類:InputStream(輸入流),OutputStream(輸出流)
字符流的抽象基類:Reader(讀者),Writer(作者)

注意:它們子類名的規律:
前綴:功能意義。
後綴:父類名。

3.字符流:如果要操作文字數據,優先考慮字符流。

①寫入:FileWriter

//需求:將一些文字存儲到硬盤的一個文件中。
數據要從內存寫到硬盤上,要使用字符流中的輸出流,Writer。
硬盤的數據基本體現是文件,所以找可以操作文件的Writer的子類,Writer->OutputStreamWriter->FileWriter:用來寫入字符文件的便捷類。

1.FileWriter細節:換行,續寫模式。見示例。

示例:

public class FileWriterDemo {
    //全局常量:系統對應的換行符。
    private static final String LINE_SEPARATOR = System.getProperty("line.separator");

    public static void main(String[] args) throws IOException {     

        //創建一個可以往文件中寫入字符數據的字符輸出流對象。
        /*              
                既然是往一個文件中寫入文字數據,那麼在創建對象時,就必須明確該文件的位置(即用於存儲數據的目的地)

            FileWriter構造方法一:
                    FileWriter(String fileName) 
                            根據給定的文件名構造一個 FileWriter 對象。

                注意:
                如果文件不存在,則會自動創建。
                如果文件存在,則會被覆蓋。
         */
        FileWriter fw =new FileWriter("demo.txt");


        //調用Writer對象中的writer(string)方法,寫入數據。
        /*
                其實數據寫入到了臨時存儲緩衝區中,就是寫到了輸出流中。
            另外:

                void write(String str) 
                      寫入字符串。        
                void write(String str, int off, int len) 
                      寫入字符串的某一部分。       
                void write(char[] cbuf) 
                      寫入字符數組。 
                void write(char[] cbuf, int off, int len) 
                      寫入字符數組的某一部分。 
                void write(int c) 
                      寫入單個字符。 

         */
        fw.write("諶中錢");


        /*
            Writer類中的flush()方法:
                    abstract  void flush() 
                                            刷新該流的緩衝。 
         */     
        //刷新輸出流的緩存區,並立即將緩衝區的數據寫到目的文件中。
        fw.flush();
        fw.write("大帥逼");


        //關閉流,關閉資源。相當於記事本右上角的“ × ”
        /*
            Writer類中的flush()方法:
                abstract  void close() 
                            關閉此流,但要先刷新它。在關閉前會先調用flush刷新緩衝中的數據到目的地。 
        */
//      fw.close();




        //換行:windows:\r\n
//      fw.write("\r\n恩,你是最棒的。");
        fw.write(LINE_SEPARATOR + "恩,你是最棒的。");
        fw.flush();
        fw.close();


        //續寫模式:
        /*
            FileWriter構造方法二:

                FileWriter(String fileName, boolean append) 
                        根據給定的文件名以及指示是否附加寫入數據的 boolean 值來構造 FileWriter 對象。

          如果append設置爲ture,在下一次重新運行寫入數據時,文件名相同時,不會覆蓋原來的文件,對源文件進行續寫。      
         */
        FileWriter fw2 = new FileWriter("demo2.txt",true);
//      fw2.write("C");
        fw2.write("中錢");
        fw2.flush();
        fw2.close();

    }

}
2.FileWriter異常處理:

示例:

//FileWriter異常處理:
public class IOEeceptionDemo {
    private static final String LINE_SEPARATOR = System.getProperty("line.separator");

    public static void main(String[] args) {

        FileWriter fw = null;
        try {

            fw = new FileWriter("k:\\demo3.txt"); // 異常原因:文件可能不存在,路徑錯誤

            fw.write("czq"+ LINE_SEPARATOR + "恩,你是最棒的。"); // 異常原因:寫到壞道寫失敗了。

        } catch (IOException e) {
            System.out.println(e.toString());
        } finally {
            if(fw!=null)   //否者會出現空指針異常
            try {
                fw.close(); // 異常原因:關閉底層資源出現異常。
            } catch (IOException e) {
                //code...
                throw new RuntimeException("關閉失敗");
            }
        }
    }

}

②讀取:FileRreader

//需求:讀取一個文本文件。將讀取到的字符打印到控制檯。
數據要從硬盤讀到內存上,要使用字符流中的輸入流,Reader。
硬盤的數據基本體現是文件,所以找可以操作文件的Reader的子類,Reader->InputStreamReader->FileReader:用來寫入字符文件的便捷類。

1.兩種讀取方式:read(), read(char[]):

示例:


public class FileReaderDemo {

    public static void main(String[] args) throws IOException {

        //1。創建讀取字符數據的流對象。
        /*
                    既然是從一個文件裏讀取文字數據,那麼在創建對象時,就必須明確該文件的位置(即讀取數據的目的地)。
                    一定要確定該文件是存在的。

                    FileReader構造方法:
                            FileReader(String fileName) 
                                        在給定從中讀取數據的文件名的情況下創建一個新 FileReader。

         */
            FileReader fr = new FileReader("demo.txt");


            //2.用Reader中的read方法讀取字符。

            /*
                    ①public int read()
                            讀取單個字符。在字符可用、發生 I/O 錯誤或者已到達流的末尾前,此方法一直阻塞。 
                    返回:
                            作爲整數讀取的字符,範圍在 0 到 65535 之間 (0x00-0xffff),如果已到達流的末尾,則返回 -1 
            */
//          long l1 = System.currentTimeMillis();
            int ch = 0;  //自定義一個緩衝區
            while((ch = fr.read())!=-1){

                System.out.print((char)ch);
            }
//          long l2 = System.currentTimeMillis();
//          System.out.println(l2-l1);    //耗時23毫秒

            //fr.close();


            //3.用Reader中的read(char[])讀取文本文件數據。讀取次數更少,效率高。

            /*
                    ② public int read(char[] cbuf)
                            將字符讀入數組。在某個輸入可用、發生 I/O 錯誤或者已到達流的末尾前,此方法一直阻塞。
                    參數:
                            cbuf - 目標緩衝區 
                    返回:
                            讀取的字符數,如果已到達流的末尾,則返回 -1 


                    注意:如果數組長度不夠,讀取會暫停,返回讀取到的字符數。
                            下次重新讀取時,數組會從暫停處開始讀取,新的字符會從數組開頭開始填充,導致數組最後的字符依次被擠掉。

             */
            char[] ch1 = new char[1024];   //自定義一個緩衝區

//          long l1 = System.currentTimeMillis();
            int len = 0;
            while((len = fr.read(ch1))!=-1){
                System.out.print(new String(ch1,0,len));
            }
//          long l2 = System.currentTimeMillis();
//          System.out.println(l2-l1);    // 耗時1毫秒

            fr.close();

/*          int num = fr.read(ch1);   //將讀到的字符存到數組中
            System.out.print(num+":"+new String(ch1));  

            int num1 = fr.read(ch1);
            System.out.print(num1+":"+new String(ch1)); 

            int num2 = fr.read(ch1);  
//          System.out.print(num2+":"+new String(ch1));   
            System.out.print(num2+":"+new String(ch1,0,num2));   //優化最後一次讀取輸出,只輸出讀取到的內容
*/          
    }

}

4.字符流練習:複製文本文件。

import java.io.FileReader;
import java.io.FileWriter;

/*
 代碼規範:
 需求:
 練習:將C盤的一個文本文件複製到D盤
 文件位置:C:\practice1.txt

 複製原理:讀取c盤文件中的數據。將這些數據寫入到d盤中。
 連讀帶寫。

 思路:
 1.需要讀取源。
 2.將讀到的源數據寫入到目的地。
 3.既然是操作文本數據,使用字符流。

 步驟:寫在代碼裏。
 */
public class Practice1 {

    private static final int BUFFER_SIZE = 1024;

    public static void main(String[] args) {
        long l1 = System.currentTimeMillis();

        System.out.println("正在複製...");
        // 1.讀取一個已有的文本文件,使用字符讀取流和文件關聯。
        FileReader fr = null; // 關閉資源的時候,需要存在關閉的資源

        // 2.創建一個目的,用於存儲讀到的數據。
        FileWriter fw = null;

        try {
            fr = new FileReader("C:\\practice1.txt");
            fw = new FileWriter("D:\\practice1.txt", true);//緩衝區不夠的情況,加true

            // 3.頻繁讀寫操作
            // 創建一個臨時容器,用於緩衝讀到的字符。就是一個緩衝區。
            char[] ch = new char[BUFFER_SIZE];

            // 定義一個變量記錄讀取到的字符數,其實就是往數組裏裝的字符個數
            int len = 0;

            while ((len = fr.read(ch)) != -1) {
                fw.write(ch, 0, len);
                fw.flush();
            }
        } catch (Exception e) {
                //暫時不處理
                new RuntimeException("讀寫失敗!");
        } finally {

            // 4.關閉流資源。 (文件流沒有關閉時,表示文件正在執行時,是刪除不掉的)
            if (fr != null)
                try {
                    fr.close();
                } catch (Exception e) {
                    //直接拋出,以後圖形化界面代替

                }

            if (fw != null)
                try {
                    fw.close();
                } catch (Exception e) {
                    //直接拋出
                }
        }

        long l2 = System.currentTimeMillis();
        System.out.println("複製成功!耗時:" + (l2 - l1) + "毫秒");

    }

}

複製文本原理圖:中間的是緩衝區。單個字符和數組都是緩衝區,是用戶自己定義的。

複製文本原理圖

5.字符流的緩衝區:Java自己封裝的緩衝區對象。

1.緩存區的出現提高了對數據的讀寫效率。
2.對應類:
    BufferedWriter
    BufferedReader
3.緩衝區要結合流纔可以使用。
4.在流的基礎上對流的功能進行了增強。

1.BufferedWriter : 特有newLine()方法
將文本寫入字符輸出流,緩衝各個字符,從而提供單個字符、數組和字符串的高效寫入。
可以指定緩衝區的大小,或者接受默認的大小。在大多數情況下,默認值就足夠大了。

該類提供了 newLine() 方法,它使用平臺自己的行分隔符概念,此概念由系統屬性 line.separator 定義。並非所有平臺都使用新行符 (‘\n’) 來終止各行。因此調用此方法來終止每個輸出行要優於直接寫入新行符。

1.構造方法:
    BufferedWriter(Writer out) 
          創建一個使用默認大小輸出緩衝區的緩衝字符輸出流。
    BufferedWriter(Writer out, int sz) 
          創建一個使用給定大小輸出緩衝區的新緩衝字符輸出流。
2.所有方法:
     void close() 
          關閉此流,但要先刷新它。 
     void flush() 
          刷新該流的緩衝。 
     void newLine() 
          寫入一個行分隔符。 
     void write(char[] cbuf, int off, int len) 
          寫入字符數組的某一部分。 
     void write(int c) 
          寫入單個字符。 
     void write(String s, int off, int len) 
          寫入字符串的某一部分。 
示例:

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;


public class BufferedWriterDemo {

    private static final String LINE_SEPARATOR = System.getProperty("line.separator");

    public static void main(String[] args) throws IOException {
            FileWriter fw = new FileWriter("Demo2.txt");

            //爲了提高寫入的效率。使用了字符流的緩衝區。
            //創建了一個字符寫入流的緩衝區對象,並和指定要被緩衝的流對象相關聯。
            BufferedWriter bufw = new BufferedWriter(fw);

            //使用緩存區的寫入方法將數據先寫入到緩存區中。
            bufw.write("hahahahaxxx"+LINE_SEPARATOR+"xixixi");
            bufw.newLine();         //寫入一個換行符
            bufw.write("czq");
            //使用緩衝區的刷新方法將數據刷到目的地中。
            bufw.flush();

            //關閉緩衝區(其實底層關閉的是被緩衝的流fw.close()),真正執行操作的其實還是流在操作,緩衝區只是提高了效率。
            bufw.close();


    }

}

2.BufferedReader : 特有readLine()方法

1.構造函數:
    BufferedReader(Reader in) 
          創建一個使用默認大小輸入緩衝區的緩衝字符輸入流。 
    BufferedReader(Reader in, int sz) 
          創建一個使用指定大小輸入緩衝區的緩衝字符輸入流。 
2.方法:
     void close() 
          關閉該流並釋放與之關聯的所有資源。
     int read() 
          讀取單個字符。  
     int read(char[] cbuf, int off, int len) 
          將字符讀入數組的某一部分。

     String readLine()
          讀取一個文本行。通過下列字符之一即可認爲某行已終止:換行 ('\n')、回車 ('\r') 或回車後直接跟着換行。 
        返回:
            包含該行內容的字符串,不包含任何行終止符,如果已到達流末尾,則返回 null 
示例:

public class BufferedReaderDemo {

    public static void main(String[] args) throws IOException {

        FileReader fr = new FileReader("demo2.txt");

        BufferedReader bufr = new BufferedReader(fr);

        String line = null;

        while((line=bufr.readLine())!= null){  //讀取行的時候,不帶換行符。

                System.out.print(line);
        }       
    }
}

BufferedReader的 readLine()方法的原理:

1.BufferedReader重寫了父類Reader的read()讀取單個字符和read(char[] cbuf, int off, int len) 部分數組的方法,沒有重寫read(char[] cbuf)讀取數組的方法。

是因爲,read()和read(char[] cbuf, int off, int len) 是將數據讀取到緩衝區裏,以提高效率,而read(char[] cbuf)讀取數組的方法有自己的緩衝區數組,不需要重寫。

其實,BufferedReader底層實現的是父類的read(char[] cbuf)讀取數組的方法,來構建BufferedReader自己的緩衝區。重寫的read()和read(char[] cbuf, int off, int len)方法再來讀自己緩衝區的數據。

2.BufferedReader的 readLine()方法:基於read()方法,加一個判斷終止符條件。用一個臨時容器來緩衝一行數據,不包含任何行終止符。

readLine()原理:使用了讀取緩衝區的read方法,將讀取到的字符進行緩存(這裏緩存可以是用StringBuilder,因爲最終返回的是字符串)並判斷換行標記。將標記前的緩衝數據變成字符串返回。

BufferedReader底層實現原理(就是調用了父類的read(char[] cbuf)方法,自己實現read()方法,readLine()等方法)。
read(),readLine()方法原理圖:
BufferedReader的 readLine()方法的原理

6.字符流緩衝區練習:複製文本文件改進版。

public class Practice2 {

    public static void main(String[] args) throws IOException {

        long l1 = System.currentTimeMillis();

        System.out.println("正在複製...");

        FileReader fr = new FileReader("C:\\practice1.txt");// 關閉資源的時候,需要存在關閉的資源
        FileWriter fw = new FileWriter("D:\\practice1.txt"); // 緩衝區不夠的情況,加true

        BufferedReader bufr = new BufferedReader(fr); // 已經把數據加載到緩衝區裏了。
        BufferedWriter bufw = new BufferedWriter(fw);

        String line = null;

        while ((line = bufr.readLine()) != null) { // 從緩衝區裏開始讀
            bufw.write(line); // 由緩衝區裏開始寫
            bufw.newLine(); // 由於行讀取操作不帶換行符,爲了還原,加上換行
            bufw.flush();
        }

        bufr.close();
        bufw.close();

        long l2 = System.currentTimeMillis();
        System.out.println("複製成功!耗時:" + (l2 - l1) + "毫秒");

    }
}

7.自定義MyBufferedReader-read()方法,MyBufferedReader-readLine()方法

import java.io.FileReader;
import java.io.IOException;

/**
 * 自定義的讀取緩衝區。其實就是模擬一個BufferedReader.
 * 
 * 分析: 緩衝區中無非就是封裝了一個數組。 並對外提供了更多的方法對數組進行訪問。 其實這些方法最終操作的都是數組的角標。
 * 
 * 緩衝的原理: 其實就是從源中獲取一批數據裝進緩衝其中。 再從緩衝區中不斷的取出一個一個數據。
 * 
 * 在此次取完後,再從源中繼續取一批數據進緩衝區。 當源中的數據取光時,用-1作爲結束標記。
 * 
 * @author 言冘
 * 
 */
public class MyBufferedReader {    //按照裝飾類思想,這裏應該繼承Reader,但是還要實現Reader的所有抽象方法,麻煩所以沒有寫繼承。

    private FileReader r;   //按照裝飾類思想,這裏應該對一組子類進行增強,即Reader r。爲了方便所以只增強了FileReader。

    // 定義一個數組作爲緩衝區
    private char[] buf = new char[1024];

    // 定義一個指針用於操作這個數組中的元素。當操作當最後一個元素後,指針應該歸0。
    private int pos = 0;

    // 定義一個計數器,用於記錄緩衝區中的數據個數,當該數據減到0,就從源中繼續獲取數據到緩衝區中。
    private int count = 0;

    MyBufferedReader(FileReader r) {
        this.r = r;
    }

    /**
     * 該方法中緩衝區中一次取一個字符。
     * @return
     * @throws IOException
     */
    public int myRead() throws IOException {

        if(count==0){
            count = r.read(buf);
            pos =0;             
        }
        if(count < 0)
            return -1;

        char ch = buf[pos++];
        count--;

        return ch;

        /*
        // 1.從源中獲取一批數據到緩衝區中.需要先做判斷,只有計數器爲0時,才需要從源中獲取數據。
        if (count == 0) {
            count = r.read(buf);

            if(count < 0)
                return -1;

            //每次獲取數據到緩衝區後,角標歸零
            pos = 0 ;
            char ch = buf[pos];
            pos++;
            count--;

            return (int)ch;
        }else if (count >0){

            char ch = buf[pos];
            pos++;
            count--;

            return (int)ch;
        }
        */
    }

    public String myReadLine() throws IOException {
        StringBuilder sb = new StringBuilder();
        int ch = 0;
        while((ch = myRead())!= -1){
            if(ch=='\r')
                continue;
            if(ch=='\n'){
                return sb.toString();
            }
            sb.append((char)ch);            
        }

        if(sb.length()!= 0)//解決如果最後一行結尾沒有\n,導致最後一行不會返回的問題。
            return sb.toString();
        return null;

    }

    public void close() throws IOException {
        r.close();
    }
}

8.字符流緩衝區:裝飾設計模式

1.BufferedReader緩衝區類的設計對另一個FileReader類進行了功能增強。
這種增強在   裝飾設計模式 中也有所體現:

裝飾設計模式:當一組對象的功能進行增強時,就可以使用該模式進行問題的解決。
設計模式是用來解決的一種思想。

BufferedReader就是裝飾設計模式設計出來的。

示例:

public class PersonDemo {

    public static void main(String[] args) {
        Person p = new Person();
        NewPerson np = new NewPerson(p);

        np.chifan();
    }

}

class Person {
    void chifan() {
        System.out.println("吃飯");
    }
}

//裝飾設計模式: 這個類的出現是爲了增強Person而出現的
class NewPerson {
    private Person p;

    NewPerson(Person p) {
        this.p = p;
    }

    void chifan() { // 對Person類的chifan方法進行增強。
        System.out.println("開胃酒");
        p.chifan();
        System.out.println("甜點");
    }
}

//繼承實現:
class NewPerson2 extends Person {

    void chifan() {
        System.out.println("開胃酒");
        super.chifan();
        System.out.println("甜點");
    }
}
2.裝飾設計模式和繼承的區別:
裝飾和繼承都能實現一樣的特點:進行功能的擴展增強。

區別:裝飾比繼承靈活。
特點:裝飾類和被裝飾類都必須所屬一個接口或者父類。

9.字符流緩衝區-跟蹤行號LineNumberReader:BufferedReader的子類。
比如讀java文件的時候用到。

API:
public class LineNumberReaderextends BufferedReader跟蹤行號的緩衝字符輸入流。此類定義了方法 setLineNumber(int) 和 getLineNumber(),它們可分別用於設置和獲取當前行號。 

默認情況下,行編號從 0 開始。該行號隨數據讀取在每個行結束符處遞增,並且可以通過調用 setLineNumber(int) 更改行號。但要注意的是,setLineNumber(int) 不會實際更改流中的當前位置;它只更改將由 getLineNumber() 返回的值。 

可認爲行在遇到以下符號之一時結束:換行符('\n')、回車符('\r')、回車後緊跟換行符。


示例:
public class LineNumberReaderDemo {

    public static void main(String[] args) throws IOException{

        FileReader fr = new FileReader("practice1.txt");
        LineNumberReader lnr = new LineNumberReader(fr);

        String line = null;
        lnr.setLineNumber(100); //設置行號初始值,用於改變getLineNumber()的值。

        while((line= lnr.readLine())!= null){
            System.out.println(lnr.getLineNumber()+":"+line);  //獲取行號
        }
        lnr.close();
    }
}

10.字符流小結:6個,2個文件的,2個buffer的,2個轉換流的

①java.io.Writer.OutputStreamWriter.FileWriter(flush())寫入字符②java.io.Reader.InputStreamReader.FileReader讀取字符

轉換流:
③InputStreamReader 讀取字節,查表,轉換成字符。是字節流通向字符流的橋樑。
    簡單說,把控制檯或其他字節流 讀取的字節變成字符。

④OutputStreamWriter  將字符數據通過字符流對象 解碼 成字節 寫到傳入的字節流中是。字符流通向字節流的橋樑。
    簡單說,把讀取的字符變成字節傳給控制檯或其他的字節流。

⑤java.io.Writer.BufferedWriter(特有newLine())
⑥java.io.Reader.BufferedReader(特有readLine())
java.io.Reader.BufferedReader.LineNumberReader

11.字節流:

字節流的抽象基類:
java.io.InputStream(輸入流)
java.io.OutputStream(輸出流)

1.基本操作與字符流相同。
2.不僅可以操作字符,還可以操作其他媒體文件。

文本文件:
讀取:
java.io.InputStream.FileInputStream
    特有方法:
    int available() 
          返回下一次對此輸入流調用的方法可以不受阻塞地從此輸入流讀取(或跳過)的估計剩餘字節數。 

寫入:
java.io.OutputStream.FileOutputStream

字節流緩衝區:緩衝區的flush()方法是有用的,但是每個都刷的話,非常慢。
java.io.InputStream.FilterInputStream.BufferedInputStream
java.io.OutputStream.FilterOutputStream.BufferedOutputStream



字節流的緩衝區是字節數組。
字符流的緩衝區是字符數組。


示例:
public class ByteStreamDemo {

    private static final int BUFFER_SIZE = 100;

    public static void main(String[] args) throws IOException {

        demo_reader();
        demo_write();
    }

        public static void demo_reader() throws IOException {

            FileInputStream fis = new FileInputStream("demo4.txt");

            System.out.println(fis.available());  //讀取文件大小。剩餘8個字節

            //byte[] buf = new byte[fis.available];  //慎用,文件太大數組就會很大,可能溢出。小文件可以用
            //fis.read(buf);
            //System.out.println(new String(buf));

            byte[] buf = new byte[BUFFER_SIZE];     
            int len = 0;

            while((len = fis.read(buf)) != -1){
                System.out.println(new String(buf,0,len));
            }

            System.out.println(fis.available());   //剩餘0個字節
            fis.close();

    }

        public static void demo_write() throws IOException {

            FileOutputStream fos = new FileOutputStream("demo4.txt");

            fos.write("abcdesdf".getBytes());  //直接寫到了目的地中,不需要緩衝。

            fos.close();
    }

}

1.字節流練習——複製媒體文件(電影):

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.DecimalFormat;


public class Practice2 {

    private static final int BUFFER_SIZE = 1024;

    public static void main(String[] args) throws IOException {

        String sRead = "E:\\電影\\經典\\秒速五釐米 - 秒速5釐米(Av4433361,P1).flv";       //源地址,730M
        String sWrite = "E:\\秒速五釐米 - 秒速5釐米(Av4433361,P1).flv";      //目的地址

        long l1 = System.currentTimeMillis();
        System.out.println("正在複製...");

        //copy(sRead,sWrite);   //不使用緩衝區更快,數組接收。速度快,7s  
        //  copy2(sRead,sWrite);   //使用緩衝區,不刷新。速度慢,70s
        //copy3(sRead,sWrite);   //不使用緩衝區,讀取源文件字節數available(),使用一次性存儲完畢的字節數組,適合小文件。熟讀較快,12s

        copy4(sRead,sWrite);            //不使用緩衝區,直接字節一個一個讀寫。速度超級慢。千萬別用

        long l2 = System.currentTimeMillis();
        String l = new DecimalFormat("0.00").format((l2-l1)/1000.0);
        System.out.println("複製成功!耗時:" + l + "秒");
    }

    private static void copy(String sRead, String sWrite) throws IOException {

        FileInputStream fis = new FileInputStream(sRead);
        FileOutputStream fos = new FileOutputStream(sWrite);
        byte[] buf = new byte[BUFFER_SIZE];
        int len = 0;
        while((len = fis.read(buf))!=-1){
            fos.write(buf, 0, len);
        }
        fis.close();
        fos.close();

    }


    private static void copy2(String sRead, String sWrite) throws IOException {

        FileInputStream fis = new FileInputStream(sRead);
        BufferedInputStream bis = new BufferedInputStream(fis);
        FileOutputStream fos = new FileOutputStream(sWrite);
        BufferedOutputStream bos = new BufferedOutputStream(fos);

        int len = 0;
        while((len = bis.read())!=-1){
            bos.write(len);
            //bos.flush();               //緩衝區的flush()是有用的,但是每個都刷的話,非常慢
        }
        bis.close();
        bos.close();

    }

private static void copy3(String sRead, String sWrite) throws IOException {

        FileInputStream fis = new FileInputStream(sRead);
        FileOutputStream fos = new FileOutputStream(sWrite);
        byte[] buf = new byte[fis.available()];   //讀取源文件剩餘字節數available(),使用一次性存儲完畢的字節數組,適合小文件。

        fis.read(buf);
        fos.write(buf);

        fis.close();
        fos.close();

    }



private static void copy4(String sRead, String sWrite) throws IOException {

    FileInputStream fis = new FileInputStream(sRead);
    FileOutputStream fos = new FileOutputStream(sWrite);

    int len = 0;
    while((len = fis.read())!=-1){
        fos.write(len);
    }
    fis.close();
    fos.close();

    }
}
用字符流複製一個圖片?機率很小
原因:讀取字節數據後,去查表,但是媒體文件在unicode碼錶查不到,這個時候碼錶會拿一些未知字符區域的字符
來寫到目的地中去。導致源數據和目的數據不一致,那麼目的數據就不能解析成圖片。

12.字節流小結:4個,2個文件的,2個buffer的

java.io.InputStream.FileInputStream
java.io.OutputStream.FileOutputStream(不需要flush(),直接寫入)
java.io.InputStream.FilterInputStream.BufferedInputStream(存在flush(),但是一個個字節刷新太慢)
java.io.OutputStream.FilterOutputStream.BufferedOutputStream

13.示例:讀取鍵盤錄入:

import java.io.IOException;
import java.io.InputStream;

/*
        讀取一個鍵盤錄入的數據,並打印在控制檯上。

        鍵盤本身就是一個標準的輸入設備。
        對於java而言,對於這種輸入設備都有對應的對象。


 */
public class ReadKey {

    public static void main(String[] args) throws IOException {

        //readKey();   //終止辦法:點紅點結束程序
        /*
                獲取用戶鍵盤錄入的數據
                並將數據變成大寫顯示再控制檯上。
                如果用戶輸入的是over,則結束鍵盤錄入

                思路:
                1.因爲鍵盤錄入只讀取一個字節,要判斷是否是over,需要將讀取的字節轉換成字符串。
                2.那就需要一個容器。StringBuilder
                3.在用戶回車之前,將錄入的數據轉換成字符串判斷即可。
         */
        readKey1();   //自定義結束標誌
    }

    public static void readKey1() throws IOException {
        StringBuilder sb = new StringBuilder();  //裝飾類BufferedReader的readLine()方法內部實現也可以是StringBuilder接收緩衝區的數據。

        InputStream in = System.in;

        int ch = 0;
        while((ch = in.read())!= -1){
            if(ch == '\r')
                continue;
            if(ch == '\n'){
                String temp = sb.toString();
                if("over".equals(temp))
                    break;
                System.out.println(temp.toUpperCase());
                sb.delete(0, sb.length());   //顯示結束後清空緩衝區
            }else

            sb.append((char)ch);
        }

    }

    public static void readKey() throws IOException {

        InputStream in = System.in;  //等待輸入,將字符解碼爲字節
    //左邊,字節輸入流。
    //右邊,字節輸入流對象,此流已打開並準備提供輸入數據。通常,此流對應於鍵盤輸入或者由主機環境或用戶指定的另一個輸入源。 接收字符數據。

    //  int ch = in.read();  //讀取數據的字節形式。read():阻塞式方法,沒數據就阻塞等待

        int ch = 0;
        while((ch = in.read())!= -1){   //讀取數據的字節形式。回車\r\n的兩個字節也錄入了,13,10。
            System.out.print((char)ch);
        }

    //  in.close();   //這個標準流隨着系統只有一個,如果關了,就再也新建不了了。隨着系統的關係而關閉,和其他流不一樣。

    }

}
//數組方式實現

public class ReadKey2 {

    public static void main(String[] args) throws IOException {
        readKey1();    //自定義結束標誌
    }

    public static void readKey1() throws IOException {
        char[] cha = new char[100];

        InputStream in = System.in;

        int ch = 0;
        int i = 0;
        while((ch = in.read())!= -1){
            if(ch == '\r')
                continue;
            if(ch == '\n'){
                String temp = new String(cha,0,i);
                if("over".equals(temp))
                    break;
                System.out.println(temp.toUpperCase());
                i =0;
            }else{

                cha[i] = (char)ch;
                i++;
            }
        }

    }

}

14.轉換流:

字節流轉字符流 鍵盤錄入——讀取演示:InputStreamReader

//優化上面的例子:
//想法:用字符流 的readLine()方法 代替 實現字節流 的行讀取代碼塊。
/*
    InputStreamReader 讀取字節,查表,轉換成字符。是字節流通向字符流的橋樑。
        簡單說,把控制檯或其他字節流 讀取的字節變成字符。

    OutputStreamWriter  將字符數據通過字符流對象 解碼 成字節 寫到傳入的字節流中是。字符流通向字節流的橋樑。
        簡單說,把讀取的字符變成字節傳給控制檯或其他的字節流。
*/
public class TransStreamDemo {

    public static void main(String[] args) throws IOException {

//字節流轉字符流 讀取演示:InputStreamReader

        //字節流
        InputStream in =System.in;   //對中文讀兩次,將字符編碼爲字節

        //字符流
        InputStreamReader r = new InputStreamReader(in);    //in轉換成字符流。將字節流in的字節讀取,查表解碼,轉成字符。這時對中文一步讀取,先讀兩個字節,然後查表解碼成字符。

        BufferedReader bufr = new BufferedReader(r);  //字符裝飾類只能裝飾字符流

        String len = null;
        while((len = bufr.readLine())!=null){   //不帶換行符
            if("over".equals(len))
                break;
            System.out.println(len.toUpperCase());
        }
        bufr.close();

    }

}

猜想:

字符流FileReader和FileWriter能直接讀取(字符形式)和寫入(字節形式) 內部實現可能是:
採用類似的字節流System.in和System.out(或是操作File類型的字節流:FileInputStream類和FileOutputStream類)讀取和寫入字節,通過繼承InputStreamReader類和OutputStreamWriter類,轉換成字符流,然後使用字符流InputStreamReader類和OutputStreamWriter類,和父類Reader,Writer及父類的修飾類BufferedReader,BufferedWriter 的屬性和方法。
reader()將讀到的字節按碼錶解碼成字符,
writer(String)將獲得的字符按碼錶編碼成字節。
System.out.println()解析:

①System類中的屬性out,是PrintStream類的對象。而PrintStream類是字節寫入流OutputStream的子類。
out
public static final PrintStream out
“標準”輸出流。此流已打開並準備接受輸出數據。通常,此流對應於顯示器輸出或者由主機環境或用戶指定的另一個輸出目標。 
對於簡單獨立的 Java 應用程序,編寫一行輸出數據的典型方式是: 
     System.out.println(data)
 請參閱 PrintStream 類中的 println 方法。

②PrintStream類:需要刷新flush()。
PrintStream 打印的所有字符都使用平臺的默認字符編碼轉換爲字節。在需要寫入字符而不是寫入字節的情況下,應該使用 PrintWriter 類。

自動刷新:
構造函數字節輸出流設置刷新參數爲true, 然後調用其中一個 println 方法,自動刷新。
或寫入一個換行符或字節 ('\n'), 然後自己刷新。

常見方法:
     void print(String s) 
          打印字符串。 
      void println(String x) 
          打印 String,然後終止該行。 
示例:
1.調用其中一個 println 方法,自動刷新。
OutputSrtream out = System.out;   //字節寫入流對象。把接收到的字節傳給控制檯。
//out.println("line.toUpperCase()")  //這是將讀到的字符編碼爲字節,傳給到控制檯。控制檯再解碼顯示。而將字符流獲取的數據轉換成字節流是由println 方法內部實現,自動刷新。
中間有一個流轉換過程,如下:

2.寫入一個換行符或字節 ('\r\n'),然後刷新。
OutputStreamWriter osw = new OutputStreamWriter(out);  //字節流out轉換成字符流對象。
osw.writer(line.toUpperCase() +"\n"); //把讀到的字符數據解碼成字節寫到緩衝區,目的地是流轉換對象osw。然後傳給out字節流對象。
osw.flush();//刷新

字節流轉字符流 鍵盤錄入——寫入演示:OutputStreamWriter

public class TransStreamDemo {

    public static void main(String[] args) throws IOException {

        //字節流
        InputStream in =System.in;  //對中文讀兩次,將字符編碼爲字節

        //字符流
        InputStreamReader r = new InputStreamReader(in);  
        //in轉換成字符流。將字節流in編碼的字節讀取,查表,轉成字符。這時對中文一次性解碼,先讀兩個字節,然後查表變成字符。


/*
    InputStreamReader 讀取字節,查表,轉換成字符。是字節流通向字符流的橋樑。
        簡單說,把控制檯或其他字節流 讀取的字節變成字符。

    OutputStreamWriter  將字符數據通過字符流對象 解碼 成字節 寫到傳入的字節流中是。字符流通向字節流的橋樑。
        簡單說,把讀取的字符變成字節傳給控制檯或其他的字節流。     
 */

        BufferedReader bufr = new BufferedReader(r);  //字符裝飾類只能裝飾字符流

        OutputStreamWriter osw = null;
        BufferedWriter bufw =null;

        String line = null;
        while((line = bufr.readLine())!=null){   //不帶換行符
            if("over".equals(line))
                break;


//  字節流轉字符流  寫入演示:OutputStreamWriter


            //字節流
            OutputStream out = System.out;  

            //字符流
            osw = new OutputStreamWriter(out); 
                        //字節流out轉字符流。來接收字符流的數據。方向是由下往上
            bufw = new BufferedWriter(osw);
                        //爲了實現和System.out.println(line.toUpperCase()); 一樣的效果,
                            //這裏使用修飾類BufferedWriter的newLine()方法,使用緩衝區也提高了效率。
                            //或者直接加上"\r\n"
                    //osw.write(line.toUpperCase()+"\r\n");

            bufw.write(line.toUpperCase());    //將字符編碼爲字節傳給緩衝區。
                            //目的地是轉換流osw。然後給字節流out,輸出到控制檯
            bufw.newLine();
            bufw.flush();       //字符流需要手動刷新



//          System.out.println(line.toUpperCase());  
            //一樣的效果,只是一步完成。將接收到字符轉成字節,並把字節傳給控制檯, 並自動刷新。         

        }
        bufr.close();
        bufw.close();
    }
}

轉換流 方向分析圖:
轉換流方向分析圖

15.轉換流 演示代碼(鍵盤錄入)優化:

 import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;

public class TransStreamDemo {

    public static void main(String[] args) throws IOException {

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

        BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));
//      如果只是輸出到控制檯的話,可以使用System.out.println(line);  進行簡化書寫。

        String line = null;
        while ((line = bufr.readLine()) != null) {
            if ("over".equals(line))
                break;
//          System.out.println(line);

            bufw.write(line.toUpperCase());
            bufw.newLine();
            bufw.flush();
        }
        bufr.close();       
        bufw.close();
    }
}

鍵盤錄入的幾個小需求:

/*
        1.需求:將鍵盤錄入的數據寫入到一個文件當中。

 */

public class TransStreamDemo {

    public static void main(String[] args) throws IOException {

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

        BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("Demo5.txt")));

//      等效:BufferedWriter bufw = new BufferedWriter(new FileWriter("Demo5.txt"));
//      其實new FileWriter("Demo5.txt")內部實現就是 new OutputStreamWriter(new FileOutputStream("Demo5.txt"))


        String line = null;
        while ((line = bufr.readLine()) != null) {
            if ("over".equals(line)){
                System.out.println("錄入結束!");
                break;
            }
            bufw.write(line);
            bufw.newLine();
            bufw.flush();

            System.out.println("...錄入成功!");
        }
        bufr.close();       
        bufw.close();

    }
}




/*
        2.需求:將一個文本文件顯示在控制檯上。

 */

public class TransStreamDemo {

    public static void main(String[] args) throws IOException {

        BufferedReader bufr = new BufferedReader(new InputStreamReader(new FileInputStream("Demo5.txt")));

//      等效:BufferedWriter bufw = new BufferedWriter(new FileReader("Demo5.txt"));
//      其實new FileReader("Demo5.txt")內部實現就是 new InputStreamReader(new FileIntputStream("Demo5.txt"))

//      BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));


        String line = null;
        while ((line = bufr.readLine()) != null) {
            System.out.println(line);

//          bufw.write(line);
//          bufw.newLine();
//          bufw.flush();

        }
        System.out.println("文本讀取結束!");
        bufr.close();       
//      bufw.close();

    }
}





/*
        3.需求:將一個文本文件的內容追加到另一個文本文件當中。

 */

public class TransStreamDemo {

    public static void main(String[] args) throws IOException {

        BufferedReader bufr = new BufferedReader(new InputStreamReader(new FileInputStream("Demo6.txt")));
        BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("Demo7.txt",true)));

        String line = null;
        while ((line = bufr.readLine()) != null) {
            bufw.write(line);
            bufw.newLine();
            bufw.flush();

        }
        System.out.println("文本追加結束!");
        bufr.close();       
        bufw.close();

    }
}

17.轉換流小結:

java.io.Reader.InputStreamReader : 字節流->字符流。解碼。
java.io.Writer.OutputStreamWriter: 字符流->字節流。編碼。

16.流的操作基本規律:

原因:流對象太多,開發時不知道用哪個對象合適。

要知道開發時用到哪些對象。只要通過四個明確:
1.明確源和目的(匯)。
    源:InputStream , Reader 兩個體系
    匯:OutputStream , Writer 兩個體系
2.明確數據是否是純文本數據。
    源:是純文本:Reader
          不是: InputStream
    匯:是純文本:Writer
          不是: OutputStream

    到這裏,就可以明確需求中具體要使用哪個體系。
3.明確具體的設備。
    源設備:
        硬盤:File
        鍵盤:System.in
        內存:數組
        網絡:Socket流
    目的設備:
        硬盤:File
        控制檯:System.out
        內存:數組
        網絡:Socket流

    到這裏,就可以明確需求中具體要使用哪個體系的哪個對象。
4.是否需要其他額外功能。(後面還要學其他的各種流對象提供額外功能)
    1.是否需要高效(緩衝區):
        是,就加上buffer.

流的操作基本規律——需求體現:

需求1:複製一個文本文件。
        1.明確源和目的:
            源:InputStream, Reader
            目的:OuputStream, Writer
        2.確數據是否是純文本數據:
            源:是,Reader
            目的:是,Writer
        3.明確具體的設備:
            源:硬盤,File
            目的:硬盤,File

            FileReader fr = new FileReader("a.txt");
            FileWriter fw = new FileWriter("a_copy.txt");       

        4:是否需要其他額外功能:
            是否需要高效:
                源:需要,Buffer
                目的:需要,Buffer

                BufferedReader bufr = new BufferedReader(new FileReader("a.txt"));
                BufferedWriter bufr = new BufferedWriter(new FileWriter("a_copy.txt"));


需求2:讀取鍵盤的錄入信息,並寫入到一個文件中。
        2.源:Reader
        目的:Writer
        3.源:鍵盤,System.in
        目的:硬盤,File
            InputStream in = System.in;
            FileWriter fw = new FileWriter("a.txt");
            這樣可以完成,但是麻煩。將讀取的字節數據轉成字符串,再由字符流操作。

        4.額外功能。
            1.轉換。
            源:需要,將字節流轉換成字符流。因爲明確的源是Reader,這樣操作文本數據更便捷。InputStreamReader
            目的:不需要
            InputStreamReader isr = new InputStreamReader(System.in);
            FileWriter fw = new FileWriter("a.txt");

            2.高效。
            源:需要,Buffer
            目的:需要,Buffer

            BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
            BufferedWriter bufw = new BufferedWriter(new FileWriter("a.txt"));


需求3:將一個文本文件數據顯示在控制檯上。
        2.Reader,Writer
        3.源:硬盤,File
        目的:控制檯,System.out   //可以直接使用簡寫:System.out.println("");
        FileReader fr = new FileReader("a.txt");
        OutputStream out = System.out;  //in是PrintStream的對象,PrintStream是OutputStream的子類。這裏用到了多態。
        //也可以直接使用System.out的println()方法,直接將字符轉成字節,傳給控制檯。內部自己實現了流轉換。

        4.額外功能。
            1.轉換:
            源:不需要。
            目的:需要,將字符流轉換成字節流。因爲明確的目的是Writer,這樣操作文本更便捷。OutputStreamWriter
            2.高效:
            源:需要 buffer
            目的:需要 buffer

            BufferedReader bufr = new BufferedReader(new FileReader("a.txt"));
            BufferedWriter bufw = new BufferedWriter(new OutputSreeamWriter(System.out));
            //System.out.println(line);


需求4:讀取鍵盤錄入數據,顯示在控制檯上。
            2.Reader,Writer
            3.System.in,System.out  (可以直接使用簡寫:System.out.println("");)
            4.
            轉換:
            源:需要,因爲都是字節流,但操作的確實文本數據,使用字符流操作文本更便捷。InputStreamReader
            目的:需要,OutputStreamWriter

            高效:
            源:需要 buffer
            目的:需要 buffer

            BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
            BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));
            //System.out.println(line);

17.轉換流的 編碼解碼:轉換流的第二個功能:指定字符集。

FileWriter和FileReader是由轉換流以平臺默認的系統字符集封裝而成的字符流。
FileWriter fw = new FileWriter("gbk.txt");
這句話其實就是轉換流指定了本機默認碼錶的體現。而且這個轉換流的子類對象,可以方便操作文本文件。
        簡單說:操作文件的字節流 + 本機默認的編碼表。這時按照默認碼錶來操作文件的便捷類。
等同於OutputStreamWriter opw = new OutputStreamWriter(new FileOutputStream("gbk.txt"));


所以要重新 指定字符集,就得將操作文件的字節流 通過 轉換流 重新封裝 並設置指定字符集 成新的操作文件的字符流。
指定編碼:OutputStreamWriter opw = new OutputStreamWriter(new FileOutputStream("utf8_1.txt"),"UTF-8");
指定解碼:InputStreamReader opr = new InputStreamReader(new FileInputStream("utf8_1.txt"),"UTF-8");

示例:

/*
        需求:將一箇中文字符串數據按照指定的編碼表寫入到一個文本文件中。

        分析:
                既然需求中已經明確了指定編碼表的動作,就不可以使用FileWriter,只能使用其父類OutputStreamWriter。
                OutputStreamWriter接受一個字節流輸出對象,既然是操作文件,那麼對象應該是FileOutputStream


        源:一箇中文字符串。
        匯:指定編碼的字符流。在 轉換流 中指定需要被轉換的字節流 和 編碼表,封裝成指定編碼集的字符流。

        額外功能:轉換流
        源:不需要       
        匯:寫入的是按照指定編碼表 編碼的字節。所以需要將字符流轉換成 特定編碼表的 字節流。OutputStreamWriter。
        (如果是將字符流轉換成 默認編碼表的字節,則直接用FileWriter)

        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("a.txt"),"UTF-16");
 */
public class TransStreamDemo1 {

    public static void main(String[] args) throws IOException, FileNotFoundException {
        //Java 字符編碼 用的是unicode碼錶編碼:漢字英文都佔兩個字節。寫法大小寫都可以
        //          字符串用的是本地系統的gbk碼錶編碼:漢字佔2個字節,英文佔1個字節。
        //unicode的升級版UTF-8:一個字節裝的下就用一個字節,裝不下就用兩個字節,再不夠再擴展。其中,漢字用3個字節。UTF-8編碼有編碼頭

        OutputStreamWriter ows = new OutputStreamWriter(new FileOutputStream("utf8_1.txt"),"UTF-8");//測試後9個字節
//      OutputStreamWriter ows = new OutputStreamWriter(new FileOutputStream("gbk_1.txt"),"GBK");//測試後6個字節
//      等同於簡化形式,FileWriter fw = new FileWriter("gbk.txt");

//      InputStreamReader opr = new InputStreamReader(new FileInputStream("utf8_1.txt"),"GBK");
        FileReader fr = new FileReader("utf8_1.txt");
        BufferedReader bufr = new BufferedReader(fr);

        ows.write("諶中錢");
        ows.flush();

        System.out.println(bufr.readLine());    //UTF-8編碼,GBK解碼,結果是亂碼"璋屼腑閽?"

        opw.close();
        bufr.close();
    }

}
轉換流使用的場景:
1.源或者目的對應的設備是字節流,但是操作的卻是文本數據,可以使用轉換流作爲橋樑。提高對文本操作的便捷。
2.一旦操作文本涉及到具體的指定編碼表時,就必須使用轉換流。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章