Java - InputStream, OutputStream & Reader, Writer

By sunwc 2023-03-31 Java

流的分類

1.操作資料單位:字節流、字符流

2.資料的流向:輸入流、輸出流

3.流的角色:節點流、處理流

流的體系結構

文件輸入字符流(FileReader):read()

@Test
    public void fileReaderTest() {
        FileReader fr = null;
        try {
            File file = new File("hello.txt");
    //        System.out.println(file.getAbsolutePath());

            // 準備具體的文件字符流
            fr = new FileReader(file);

            // 每次調用read()即讀取一個字符
            // 當方法回傳-1時,代表以至文件末尾
            int aChar;
            while ((aChar = fr.read()) != -1) {
                System.out.print((char) aChar);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 關閉流
            if (fr != null) {
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

文件輸入字符流(FileReader):int read(char[] ch)

有快遞車一次載5個商品,直到載完全部的商品;每次會回報這次實際載了幾個商品

  @Test
    public void fileReaderWithCharArr() {

        FileReader fr = null;
        try {
            File file = new File("hello.txt");

            fr = new FileReader(file);

            // 新建一個字符陣列,可以至多一次讀5個字符
            char[] chBuffer = new char[5];

            // len 接收每次讀取的實際字符數
            // 若已至文件末尾,回傳-1
            int len;
            while ((len = fr.read(chBuffer)) != -1) {

                // 方式一
//                for (int i = 0; i < len; i++) {
//                    System.out.print(chBuffer[i]);
//                }

                // 方式二
                // 將char陣列轉字串輸出
                String s = new String(chBuffer, 0, len);
                System.out.print(s);

            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {

            if (fr != null) {
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

文件輸出字符流(FileWriter):write(String str)/ write(char[] ch)

@Test
    public void fileWriterTest() {

        FileWriter fw = null;
        try {

            File file = new File("hello1.txt");

            // new FileWriter(File file, boolean append)
            // append 屬性默認為false,新寫出的內容會覆蓋原來的內容
            // append 屬性為true,則會在原有基礎上附加內容
            fw = new FileWriter(file, false);

            // 方式一
//            fw.write("I am using file writer at the beginning.\n");
//            fw.write("this is another line");

            // 方式二
            String content = "I am using file writer at the beginning.\nthis is another line";
            fw.write(content.toCharArray());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fw != null) {
                try {
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

文件輸入輸出一起操作(複製文件):使用文件流讀取字符(char)

 @Test
    public void copyDocumentTest() {

        FileReader fr = null;
        FileWriter fw = null;
        try {
            File srcDoc = new File("hello.txt");
            File destDoc = new File("hello2.txt");

            fr = new FileReader(srcDoc);
            fw = new FileWriter(destDoc, false);

            char[] chBuffer = new char[5];
            int len;

            while ((len = fr.read(chBuffer)) != -1) {
                // len的長度為實際讀取的字符數
                // 因此也可以作為寫出的實際字符數
                fw.write(chBuffer, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fw != null) {
                    fw.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

            try {
              if (fr != null) {
                  fr.close();
              }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

圖片輸入輸出一起操作(複製圖片)

讀取字節使用文件流(FileInputStream):int read(byte[] bytes)/

寫入字節使用文件流(FileOutputStream):write(byte[] bytes, int offset, int count)

    /**
     * 圖片複製
     */
    @Test
    public void imageCopyTest() {

        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            File srcImg = new File("girl.png");
            File destImg = new File("girl2.png");

            // 準備字節輸入流物件
            fis = new FileInputStream(srcImg);
            // 準備字節輸出流物件
            fos = new FileOutputStream(destImg);

            // 一次讀取至多1024 bytes
            byte[] byteBuffer = new byte[1024];
            // 每次讀取會回傳實際讀取的bytes數
            // 若回傳-1,代表已讀完整的圖片
            int len;
            while ((len = fis.read(byteBuffer)) != -1) {
                // 依據本次實際讀取的字節數寫出
                fos.write(byteBuffer,0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

影片輸入輸出一起操作(影片複製):使用緩衝流讀取字節(byte)

提高流的讀取、寫入的速度,原因:內部提供了一個緩衝區

@Test
    public void videoCopyTest() {

        String srcPath = "C:\\Users\\ching\\Desktop\\google-video.mp4";
        String destPath = "C:\\Users\\ching\\Desktop\\google-video1.mp4";

        long start = System.currentTimeMillis();
        copyFile(srcPath,destPath);
        long end = System.currentTimeMillis();

        System.out.println(end - start); // 4599 1321
    }

    private void copyFile(String srcPath, String destPath) {

        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        try {
            File srcImg = new File(srcPath);
            File destImg = new File(destPath);

            // 準備字節輸入流物件
            FileInputStream fis = new FileInputStream(srcImg);
            bis = new BufferedInputStream(fis);

            // 準備字節輸出流物件
            FileOutputStream fos = new FileOutputStream(destImg);
            bos = new BufferedOutputStream(fos);

            // 一次讀取至多1024 bytes
            byte[] byteBuffer = new byte[1024];
            // 每次讀取會回傳實際讀取的bytes數
            // 若回傳-1,代表已讀完整的圖片
            int len;
            while ((len = bis.read(byteBuffer)) != -1) {
                // 依據本次實際讀取的字節數寫出
                bos.write(byteBuffer, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (bis != null) {
                try {
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bos != null) {
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

讀取字符使用緩衝流(BufferReader):int read(byte[] bytes)/ String readLine(byte[] bytes)/

寫入字符使用緩衝流(BufferWriter):write(char[] ch, int offset, int count)/ write(String str)

@Test
    public void copyDocumentTest() {

        BufferedReader br = null;
        BufferedWriter bw = null;
        try {
            File srcDoc = new File("hello.txt");
            File destDoc = new File("hello2.txt");

            FileReader fr = new FileReader(srcDoc);
            FileWriter fw = new FileWriter(destDoc, false);

            br = new BufferedReader(fr);
            bw = new BufferedWriter(fw);

            char[] chBuffer = new char[5];
            int len;
            // 方式一
//            while ((len = br.read(chBuffer)) != -1) {
//                // len的長度為實際讀取的字符數
//                // 因此也可以作為寫出的實際字符數
//                bw.write(chBuffer, 0, len);
//            }

            // 方式二
            // 我們可以使用BufferedReader.readLine() 代表一次讀取一行
            // 但readLine() 不會配給我們換行符
            // 所以可以另外調BufferedReader.newLine()換行
            // 當readLine()回傳 null 代表文件已經讀到末尾了
            String str;
            while ((str = br.readLine()) != null) {
                bw.write(str);
                // 換行
                bw.newLine();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {

          // 只要關閉最外層的流,內層的就會一起關閉了
            try {
                if (bw != null) {
                    bw.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

            try {
                if (br != null) {
                    br.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

轉換流(處理流)

  • 作用:提供 字符流(char) 與 字節流(byte) 之間的轉換

  • 類型:

    • 字符流(char):
      • InputStreamReader:將一個字節的輸入流轉換為字符的輸入流(解碼)
      • OutputStreamWriter:將一個字符的輸出流轉換為字節的輸出流(編碼)
      • 解碼:byte、byte[] => char[]、String
      • 編碼:char[]、String => byte、byte[]
  • InputStreamReader 例子

    /**
     * 例外要以try-catch-finally處理較正規
     * @throws IOException
     */
    @Test
    public void inputStreamReaderTest() throws IOException {

        // 使用文件字節流讀取到console
        // 可能會有中文字亂碼的危機
        FileInputStream fis = new FileInputStream("words.txt");

        // 因此要想辦法將字節流 轉換成 字符流 (解碼的過程需提供文件儲存時 指定的編碼 )
        InputStreamReader isr = new InputStreamReader(fis, "UTF-8");

        char[] chArr = new char[20];
        int len;
        while ((len = isr.read(chArr)) != -1) {
            // 輸出方式一
            String str = new String(chArr, 0, len);
            // 在console輸出
            System.out.print(str);

            // 輸出方式二
//            for (int i = 0; i < len; i++) {
//                System.out.print(chArr[i]);
//            }
        }

        // 關閉最外層的inputStreamReader就可以了
        isr.close();
    }

綜合使用InputStreamReader, OutputStreamWriter將文件複製一份輸出,編碼從UTF-8 改 BIG5

    /**
     * 例外要以try-catch-finally處理較正規
     * @throws IOException
     */
    @Test
    public void docUTF8ToBIG5() throws IOException {

        File srcFile = new File("words.txt");
        File destFile = new File("words_BIG5.txt");

        FileInputStream fis = new FileInputStream(srcFile);
        FileOutputStream fos = new FileOutputStream(destFile);

        // byte[] => char[] 解碼
        InputStreamReader isr = new InputStreamReader(fis, "UTF-8");
        // char[] => byte[] 編碼
        OutputStreamWriter osr = new OutputStreamWriter(fos, "BIG5");

        char[] chBuffer = new char[20];
        int len;
        while((len = isr.read(chBuffer)) != -1) {

            osr.write(chBuffer, 0, len);
        }

        osr.close();
        isr.close();
    }

使用 ByteArrayOutputStream 實現複製文件

使用 ByteArrayOutputStream 的好處:若輸入流讀取後的資料直接寫入 ByteArrayOutputStream 儲存,就不用擔心每個字符到底對應幾個byte,因為在使用byte[] 盛裝資料時,可能會因為本次array空間不足就只儲存一個字符對應的部分bytes、而導致當要將bytes轉回字符時而產生亂碼的問題;ByteArrayOutputStream 底層是使用陣列append所有bytes之後,再一次性地將資料轉換成字串

/**
* 使用 ByteArrayOutputStream 實現複製文件
* 例外要以try-catch-finally處理較正規
* @throws IOException
*/
@Test
public void byteArrayOutputStreamTest() throws IOException {

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

    FileOutputStream fos = new FileOutputStream("hello2.txt");
    ByteArrayOutputStream baos = new ByteArrayOutputStream();

    byte[] buffer = new byte[20];
    int len;
    while ((len = fis.read(buffer)) != -1) {
        // 先將從byte[] 讀取的字節保存到 ByteArrayOutputStream
        baos.write(buffer, 0, len);
    }

    // 再一次性地將 ByteArrayOutputStream 的資料寫入到文件
    baos.writeTo(fos);

    // 關閉流
    baos.close();
    fos.close();
    fis.close();
}

使用 StringBuilder 接收讀取的字符(指定編碼)資料並一次性輸出至console

/**
* 使用 StringBuilder 接收讀取的字符資料並一次性輸出至console
* 例外要以try-catch-finally處理較正規
*/
@Test
public void consoleStringBuilderTest() throws IOException {

    FileInputStream fis = new FileInputStream("hello.txt");
    InputStreamReader isr = new InputStreamReader(fis, "Big5");

    // 輸出console 準備 StringBuilder 接收資料
    StringBuilder sb = new StringBuilder();

    char[] chBuffer = new char[20];
    int len;
    while ((len = isr.read(chBuffer)) != -1) {

        sb.append(new String(chBuffer, 0, len));
    }

    System.out.println(sb.toString());

    isr.close();

}