IO流(1):基礎字節流、字符流

一、IO概述

1. 什麼是IO?

  • 把計算機上數據的傳輸,按照流動的方向,以內存爲基準,分爲輸入input輸出output
  • 所謂的IO操作,就是利用java.io包下的內容,進行輸入輸出操作

2. IO的分類

2.1 按流動的方向分

  • 輸入流:把數據從其他設備上讀取到內存中的流
  • 輸出流:把數據從內存上寫出到其他設備上的流

2.2 按數據的類型分

  • 字節流:以字節爲單位,讀寫數據的流
    • 字節流處理的最基本單位是單個字節,通常用來處理二進制數據。
  • 字符流:以字符爲單位,讀寫數據的流
    • Java中的字符流處理的最基本單位是Unicode碼元,(大小2字節)。通常處理文本數據

3. IO流中的根類

在這裏插入圖片描述

二、字節流

在這裏插入圖片描述

1. 一切皆字節

  • 一切文件數據(文本、圖片、視頻等)在存儲的時候,都是以二進制數據保存的,最後都是一個一個的字節,那麼在傳輸的時候肯定也是一樣的
  • 所以,字節流可以傳輸任意文件數據,在操作流的時候,需要明確的一點是,無論使用什麼樣的流對象,底層傳輸的始終都只是二進制數據,也就是字節

2. 字節輸出流(抽象類):OutputStream

  • java.io.OutputStream抽象類是表示字節輸出流的所有類的根類,將指定的字節信息輸出到目的地。它定義了字節輸出流的基本的共性的功能和方法:
    • public void close():關閉輸出流,並釋放與此流相關的任何系統資源。
      • 當完成流的操作時,必須調用此方法,釋放系統資源。
    • public void flush():刷新此輸出流,並強制任何緩衝的輸出字節被寫出
    • public void write(byte[] b):將b.length長度的字節從指定的字節數組寫入此輸出流
    • public void write(byte[] b, int off, int len):從指定的直接數組寫入長度爲len的字節,從偏移量off開始輸出到此輸出流
    • public abstract void write(int b):寫入指定的字節到這個輸出流

3. FileOutputStream

  • java.io.FileOutputStream:文件輸出流,將數據寫出到文件。也就是抽象類OutputStream最簡單的一個繼承類

3.1 構造方法

  • public FileOutputStream(File file):創建文件輸出流以寫入指定的File對象表示的文件

  • public FileOutputStream(String name):創建文件輸出流以指定的名稱寫入文件

  • 當創建一個流對象的時候,必須傳入一個文件路徑。該路徑下:

    • 如果沒有這個文件,則會創建該文件
    • 如果有這個文件,會清空該文件的數據
  • 示例代碼

public class FileOutputStreamConstructor_Demo01 {

    public static void main(String[] args) throws IOException {
        //使用File對象創建流對象
        File file = new File("aa.txt");
        FileOutputStream fos = new FileOutputStream(file);

        //使用文件名創建流對象
        FileOutputStream fos1 = new FileOutputStream("b.txt");

    }
}
  • 效果:在當前項目的根目錄下存在了兩個txt文件

在這裏插入圖片描述

3.2 寫出字節數據

3.2.1 寫出單個字節

  • 寫出字節write(int b),每次可以寫出一個字節數據
  • 代碼
public class FOSWrite_Demo02 {

    public static void main(String[] args) throws IOException {
        //0. 使用文件名創建流對象
        FileOutputStream fos = new FileOutputStream("fos.txt");
        //1. 寫出數據
        fos.write(97);//1.1 對應ASCII碼中 的a
        fos.write(98);
        fos.write(99);
        //2. 關閉資源
        fos.close();
    }
}

  • 結果
    在這裏插入圖片描述
  • 注意這裏int類型的大小是4個字節,但只會保留一個字節的信息寫出,也就是第一個字節的int值

3.2.2 寫出字節數組

  • write(byte[] b):每次寫出數組中的數據

  • 代碼

public class FOSWrite_Demo03 {

    public static void main(String[] args) throws IOException {
        //0. 使用文件名創建流對象
        FileOutputStream fos = new FileOutputStream("fos.txt");
        //1. 字符串轉換爲字節數組
        byte[] b = "正在學IO".getBytes();
        //2. 寫出字節數組
        fos.write(b);
        //3. 關閉資源
        fos.close();
    }
}

  • 效果
    在這裏插入圖片描述

3.2.3 寫出指定起點和長度的字節數組

  • write(byte[] b, int off, int len):從off開始,往後len個字節

  • 代碼

public class FOSWrite_Demo04 {

    public static void main(String[] args) throws IOException {
        //0. 使用文件名創建流對象
        FileOutputStream fos = new FileOutputStream("fos.txt");
        //1. 字符串轉換爲字節數組
        byte[] b = "abcde".getBytes();
        //2. 寫出指定開始位置和長度的 字節數組
        //2.1 起始位置(索引) = 2:c
        //2.2 長度=2,從起始位置算起, cd
        fos.write(b,2,2);
        //3. 關閉資源
        fos.close();
    }
}

  • 效果
    在這裏插入圖片描述

  • 上述的寫出字節數據,可以這樣理解嗎:通過fos這個輸出流對象,把內存中的數據以字節爲單位輸出到了指定的一個文件中去

3.3 數據追加續寫(2個相關的構造方法)

  • 上面的寫出方式中,每創建一個流對象,都會先清空掉文件中的內容
  • 如何在保留文件內容的基礎上,再往後面進行寫入數據呢?

3.3.1 相關方法(構造方法)

  • public FileOutputStream(File file, boolean append):創建文件輸出流寫入由指定的File對象表示的文件

  • public FileOutputStream(String name, boolean append):創建文件輸出流,以指定的名稱寫入文件

  • 上述方法中的boolean類型的append

    • true:表示追加數據
    • false:表示清空原有數據
  • 代碼

public class FOSWrite_Demo05 {

    public static void main(String[] args) throws IOException {
        //0. 使用文件名創建流對象
        FileOutputStream fos = new FileOutputStream("fos.txt",true);
        //1. 字符串轉換爲字節數組
        byte[] b = "追加寫入".getBytes();
        //2. 寫出字節數組
        fos.write(b);
        //3. 關閉資源
        fos.close();
    }
}
  • 寫入前
    在這裏插入圖片描述
  • 寫入後
    在這裏插入圖片描述

3.4 寫出換行

3.4.1 不同操作系統換行的不同

  • Windows系統裏,每行結尾是 回車+換行 ,即\r\n
    • 回車符\r:回到一行的開頭(return)。
    • 換行符\n:下一行(newline)。
  • Unix系統裏,每行結尾只有 換行 ,即\n
  • Mac系統裏,每行結尾是 回車 ,即`r `。從 Mac OS X開始與Linux統一

3.4.2 代碼

  • 代碼
public class FOSWrite_Demo06 {

    public static void main(String[] args) throws IOException {
        //0. 使用文件名創建流對象
        FileOutputStream fos = new FileOutputStream("fos.txt",true);
        //1. 字符串轉換爲字節數組
        String[] b = {"換","行"};
        for (int i = 0; i < b.length; i++) {
            //1.1 寫出
            fos.write(b[i].getBytes());
            //1.2 寫出換行, 將換行轉換爲數組寫出
            fos.write("\r\n".getBytes());
        }
        //3. 關閉資源
        fos.close();
    }
}

  • 效果
    在這裏插入圖片描述

4. 字節輸入流InputStream

  • java.io.InputStream抽象類時表示字節輸入流的所有類的超類,可以讀取字節信息到內存,它定義了字節輸入流的基本共性方法
  • public void close():關閉輸入流並釋放與此流相關聯的任何系統資源
  • public abstract int read():從輸入流讀取數據的下一個字節
  • public int read(byte[] b):該方法返回的int代表的是讀取了多少字節,讀到幾個返回幾個,沒有讀到返回-1

5. FileInputStream類

5.1 構造方法

其實就是分別通過文件對象和路徑名去指定到一個實際文件,然後創建對應的FileInputStream

  • FileInputStream(File file):打開與實際文件的連接來創建一個FileInputStream,該文件由文件系統中的File對象file命名。

  • FileInputStream(String name):打開與實際文件的連接來創建一個FileInputStream,該文件由文件系統中的路徑名name命名。

  • 推薦使用第二種

public class FISRead_Demo01 {
    public static void main(String[] args) throws FileNotFoundException {
        //0. 通過file對象創建流對象
        File file = new File("D:\\IO流學習\\aa.txt");
        FileInputStream fis = new FileInputStream(file);
        //1. 通過文件名 創建流對象
        FileInputStream fis1 = new FileInputStream("D:\\IO流學習\\aa.txt");
    }
}

5.2 FileInputStream讀取字節數據

5.2.1 read方法讀取單個字節數據

讀到文件末尾返回-1

  • 文件數據爲
    在這裏插入圖片描述
public class FISRead_Demo02 {
    public static void main(String[] args) throws IOException {
        //0. 通過文件名 創建流對象
        FileInputStream fis = new FileInputStream("D:\\IO流學習\\aa.txt");
        //1. 開始讀取數據

        int b;
        while (( b = fis.read()) != -1){
            System.out.println((char)b);
        }
        fis.close();
    }
}

  • 結果
    在這裏插入圖片描述

  • 每次讀取一個字節,而英文字符剛好佔據一個字節

  • 本質上返回的是字符的ASCII碼,是int類型的數字,這裏又轉換爲了char

5.2.2 使用字節數組按指定字節長度讀取:read(byte[] b)

每次讀取的是該字節數組長度個字節

public class FISRead_Demo03 {
    public static void main(String[] args) throws IOException {
        //0. 通過文件名 創建流對象
        FileInputStream fis = new FileInputStream("D:\\IO流學習\\aa.txt");
        //1. 開始讀取數據
        byte[] b = new byte[2];
        int len;
        while ((len= fis.read(b)) != -1){
            System.out.println(new String(b));
        }
        fis.close();
    }
}
  • 每次讀取2個字節

  • 輸出
    在這裏插入圖片描述

  • 這個輸出存在一點小的問題,因爲我們本來預期的應該是

  • ab

  • cd

  • e

  • 原因在於,上次讀取的內容沒有被全部替換
    在這裏插入圖片描述

  • 所以不能按上述代碼讀取,而要讀取有效字段len

public class FISRead_Demo03 {
    public static void main(String[] args) throws IOException {
        //0. 通過文件名 創建流對象
        FileInputStream fis = new FileInputStream("D:\\IO流學習\\aa.txt");
        //1. 開始讀取數據
        byte[] b = new byte[2];
        int len;
        while ((len= fis.read(b)) != -1){
            System.out.println(new String(b,0,len));
        }
        fis.close();
    }
}
  • 輸出
    在這裏插入圖片描述

  • 此種方式正確

5.2.2.1 按字節數組讀取的模板代碼
  • 在開發中,一般按照1024個字節長度,也就是1MB的大小進行讀取,下面是模板代碼
public class FISRead_Demo04 {
    public static void main(String[] args) throws IOException {
        //0. 通過文件名 創建流對象
        FileInputStream fis = null;
        try {
            fis = new FileInputStream("D:\\IO流學習\\aa.txt");
            //1. 開始讀取數據
            byte[] b = new byte[1024];
            int len;
            while ((len= fis.read(b)) != -1){
                System.out.println(new String(b,0,len));
            }
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            try {
                fis.close();
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }
}

5.3 FileInputStream和FileOutputStream複製圖片/文本/視頻

  • 源數據
    在這裏插入圖片描述
public class FISRead_Demo05 {
    public static void main(String[] args) throws IOException {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            //0. 指定數據源
            fis = new FileInputStream("D:\\IO流學習\\1.jpg");
            //1. 指定寫入目的地
            fos = new FileOutputStream("D:\\IO流學習\\1_copy.jpg");


            //2. 開始讀寫數據
            byte[] b = new byte[1024];
            int len;
            while ((len= fis.read(b)) != -1){
                //3. 從源數據紅讀取的 寫入目的地
                fos.write(b,0,len);
            }
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            try {
                fis.close();
                fos.close();
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }
}

在這裏插入圖片描述

三、字符流(Writer/Reader)

在這裏插入圖片描述

1. 字符流的由來

  • 一切數據在底層都是字節,但是對於同一個字符來說,對於不同的編碼方式佔據的字節數不同。特別是中文字符在UTF-8編碼下,一箇中文佔3個字節,在GBK編碼下,一箇中文佔2個字節
  • 所以如果直接用字節流去讀取可能會發生亂碼,所需需要對字符直接進行高效流操作,這就需要字符流
  • 字符流本質上還是基於字節流讀取,只是讀取的時候去查詢指定的碼錶

下面這個例子展示了直接用字節流去讀取中文字符產生的亂碼現象

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

        //0. 使用 字節輸入流創建對象
        FileInputStream fis = new FileInputStream("D:\\IO流學習\\c.txt");
        //1. 指定字節數組
        byte[] bytes = new byte[2];
        //2. 開始讀取
        int len = 0;
        while ((len = fis.read(bytes)) != -1){
            System.out.println((char)len);
        }
    }
}
  • 輸出
    在這裏插入圖片描述

  • 如果還是堅持使用字節流如何正確的讀出中文數據呢?

  • 使用String

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

        //0. 使用 字節輸入流創建對象
        FileInputStream fis = new FileInputStream("D:\\IO流學習\\c.txt");
        //1. 指定字節數組
        byte[] bytes = new byte[1024];
        //2. 開始讀取
        int len = 0;
        while ((len = fis.read(bytes)) != -1){
            System.out.print(new String(bytes,0,len));
        }
    }
}
  • 輸出
    在這裏插入圖片描述

  • 這是爲什麼呢?因爲new String()是帶有解碼功能的,而且默認的編碼是utf-8,源碼如下

  public String(byte bytes[], int offset, int length) {
        checkBounds(bytes, offset, length);
        this.value = StringCoding.decode(bytes, offset, length);
    }

1.1 小結

  • 在處理文本數據的時候,字節流也是可以的,不過比較麻煩
  • 所以出現了字符流,以字符爲單位來讀取數據,字符流專門用於處理文本數據
  • 所以需要處理文本數據的場景下優先考慮字符流,其他場景,如視頻,音樂,圖片就使用字節流
  • 本質上:字符流=字節流+編碼表

2. 字符輸入流(Reader)

  • java.io.Reader抽象類是字符輸入流的所有類的父類,可以讀取字符信息到內存中

共性方法:

  • public void close():關閉流、釋放資源
  • public int read():從輸入流中讀取一個字符
  • public int read(char[] cbuf):從輸入流中讀取一些字符,並存儲到字符數組cbuf

3. FileReader類

讀取字符文件的遍歷類,構造時使用系統默認的字符編碼和默認字節緩衝區

3.1 構造方法

  • FileReader(File file)
  • FileReader(String fileName)

3.1 FileReader讀取字符數據

3.1.1 read方法

  • 每次去讀一個字符的數據,提升爲int類型,讀到文件末尾,返回-1

按字符讀取中文數據

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

        //0. 使用 字節輸入流創建對象
        FileReader fr = new FileReader("D:\\IO流學習\\c.txt");
        //1. 開始讀取
        int len = 0;
        while ((len = fr.read()) != -1){
            System.out.print((char)len);
        }
    }
}
  • 輸出
    在這裏插入圖片描述

4. 字符輸出流(Writer)

  • java.io.Writer是字符輸出流的所有類的超類,將字符信息寫入到指定目的地

共性方法

  • void close() 關閉此流,但要先刷新它。
  • void flush()刷新該流的緩衝。
  • void write(int c) 寫入單個字符。
  • void write(char[] cbuf)寫入字符數組。
  • abstract void write(char[] cbuf, int off, int len)寫入字符數組的某一部分,off數組的開始索引,len寫的字符個數。
  • void write(String str)寫入字符串。
  • void write(String str, int off, int len) 寫入字符串的某一部分,off字符串的開始索引,len寫的字符個數。

5. FileWriter類

寫出字符到文件的便利類。構造時使用系統默認的字符編碼和默認字節緩衝區

5.1 構造方法

  • FileWriter(File file)
  • FileWriter(String fileName)

5.2 FileWriter寫出數據

5.2.1 write(int b)

  • 每次寫出一個字符數據

演示

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

        //0. 使用 字節輸入流創建對象
        FileWriter fw = new FileWriter("D:\\IO流學習\\d.txt");
        //1. 寫出數據
        fw.write(99);
        fw.write("字符流可以直接寫出數據");
        fw.close();
    }
}
  • 輸出
    在這裏插入圖片描述

5.2.2 不關閉FileWriter輸出流對象無法把字符寫入到文件

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

        //0. 使用 字節輸入流創建對象
        FileWriter fw = new FileWriter("D:\\IO流學習\\e.txt");
        //1. 寫出數據
        fw.write(99);
        fw.write("字符流可以直接寫出數據");
//        fw.close();
    }
}
  • 輸出
    在這裏插入圖片描述

5.3 關閉close和刷新flush

  • 上面的例子我們看到了如果不關閉流對象,則無法寫出數據到文件
  • 這是因爲我們的數據是先存放在一個緩衝區的,只有關閉的時候再從緩衝區到文件
  • 但是一旦關閉了流,後續就無法使用流對象了
  • 那麼如果我既想把緩衝區的數據寫到文件,同時又不想關閉流對象,那麼該如何做呢?
  • flush

下面展示如何使用flush來完成上述的設想

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

        //0. 使用 字節輸入流創建對象
        FileWriter fw = new FileWriter("D:\\IO流學習\\e.txt");
        //1. 寫出數據
        fw.write("使用flush");
        fw.flush();
        fw.write("就可以完成緩衝區寫入到文件");
        fw.flush();
    }
}
  • 輸出
    在這裏插入圖片描述
  • 上述代碼並沒有使用close()依然可以完成字符到文件的寫入,但是在開發中還是需要使用close()

下面展示調用了close()後再次寫入報錯

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

        //0. 使用 字節輸入流創建對象
        FileWriter fw = new FileWriter("D:\\IO流學習\\e.txt");
        //1. 寫出數據
        fw.write("使用flush");
        fw.close();
        fw.write("就可以完成緩衝區寫入到文件");
        fw.close();
    }
}
  • 輸出
    在這裏插入圖片描述

5.4 FileWriter的續寫和換行

FileOutputStream完全類似的操作

實驗

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

        //0. 使用 字節輸入流創建對象
        FileWriter fw = new FileWriter("D:\\IO流學習\\f.txt",true);
        //1. 寫出數據
        fw.write("使用flush");
        fw.write("\r\n");
        fw.append("可以追加和換行");
        fw.close();
    }
}
  • 輸出
    在這裏插入圖片描述

5.5 FileReader和FileWriter完成文本文件的複製

  • 一次讀取一個字符
public class CharIO_Demo05 {
    public static void main(String[] args) throws IOException {

        //0.創建 字符輸入流
        FileReader fr = new FileReader("D:\\IO流學習\\f.txt");
        //0.1 創建字符輸出流
        FileWriter fw = new FileWriter("D:\\IO流學習\\f_copy.txt");
        //1. 方法1 一次讀取一個字符
        int ch;
        while ((ch = fr.read()) != -1){
            fw.write(ch);
        }
        fr.close();
        fw.close();
    }
}

在這裏插入圖片描述

  • 一次讀取一個字符數組
public class CharIO_Demo06 {
    public static void main(String[] args) throws IOException {

        //0.創建 字符輸入流
        FileReader fr = new FileReader("D:\\IO流學習\\f.txt");
        //0.1 創建字符輸出流
        FileWriter fw = new FileWriter("D:\\IO流學習\\f_copy_2.txt");
        //1. 方法1 一次讀取一個字符
        char[] chars = new char[1024];
        int ch;
        while ((ch = fr.read(chars)) != -1){
            fw.write(chars,0,ch);
        }
        fr.close();
        fw.close();
    }
}

四、IO異常處理

  • 一般使用try...catch..finally的順序來處理

  • 一般按照如下模式

public class IOExceptionHandle {
    public static void main(String[] args) {

        //0. 聲明流對象
        FileWriter fw = null; 
        try {
            //1. 創建流對象
            fw = new FileWriter("a.txt");
            //2. 實際操作代碼
            fw.write("我們");
            

        }catch (IOException e){
            e.printStackTrace();
        }finally {
            try {
                if (fw != null){
                    fw.close();
                }
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章