看完這個,Java IO今後不在難

一、IO體系

Java IO 體系看起來類不少,感受很複雜,但實際上是 IO 涉及的因素太多了。在設計 IO 相關的類時,編寫者也不是從同一個方面考慮的,因此會給人一種很亂的感受,而且還有設計模式的使用,更加難以使用這些 IO 類,因此特意對 Java 的 IO 作一個總結。java

IO 類設計出來,確定是爲了解決 IO 相關的操做的,想想哪裏會有 IO 操做?網絡、磁盤。網絡操做相關的類是在 java.net 包下,不在本文的總結範圍內。提到磁盤,你可能會想到文件,文件操做在 IO 中是比較典型的操做。在 Java 中引入了 「流」 的概念,它表示任何有能力產生數據源或有能力接收數據源的對象。數據源能夠想象成水源,海水、河水、湖水、一杯水等等。數據傳輸能夠想象爲水的運輸,古代有用桶運水,用竹管運水的,如今有鋼管運水,不一樣的運輸方式對應不一樣的運輸特性。git

從數據來源或者說是操做對象角度看,IO 類能夠分爲:編程

  • 一、文件(file):FileInputStream、FileOutputStream、FileReader、FileWriter
  • 二、數組([]):
    • 2.一、字節數組(byte[]):ByteArrayInputStream、ByteArrayOutputStream
    • 2.二、字符數組(char[]):CharArrayReader、CharArrayWriter
  • 三、管道操做:PipedInputStream、PipedOutputStream、PipedReader、PipedWriter
  • 四、基本數據類型:DataInputStream、DataOutputStream
  • 五、緩衝操做:BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter
  • 六、打印:PrintStream、PrintWriter
  • 七、對象序列化反序列化:ObjectInputStream、ObjectOutputStream
  • 八、轉換:InputStreamReader、OutputStreWriter
  • 九、字符串(String)Java8中已廢棄StringBufferInputStream、StringBufferOutputStream、StringReader、StringWriter

數據源節點也能夠再進行二次處理,使數據更加容易使用,因此還能夠劃分紅節點流和處理流,這裏涉及到設計模式,後面會有專門的文章說。 設計模式

按操做對象劃分.jpg
從數據傳輸方式或者說是運輸方式角度看,能夠將 IO 類分爲:

  • 一、字節流
  • 二、字符流

字節流是以一個字節單位來運輸的,好比一杯一杯的取水。而字符流是以多個字節來運輸的,好比一桶一桶的取水,一桶水又能夠分爲幾杯水。數組

字節流和字符流的區別:緩存

字節流讀取單個字節,字符流讀取單個字符(一個字符根據編碼的不一樣,對應的字節也不一樣,如 UTF-8 編碼是 3 個字節,中文編碼是 2 個字節。)字節流用來處理二進制文件(圖片、MP三、視頻文件),字符流用來處理文本文件(能夠看作是特殊的二進制文件,使用了某種編碼,人能夠閱讀)。簡而言之,字節是個計算機看的,字符纔是給人看的。微信

字節流和字符流的劃分能夠看下面這張圖。 網絡

按字節和字符劃分.png

不能否認,Java IO 相關的類確實不少,但咱們並非全部的類都會用到,咱們經常使用的也就是文件相關的幾個類,如文件最基本的讀寫類 File 開頭的、文件讀寫帶緩衝區的類 Buffered 開頭的類,對象序列化反序列化相關的類 Object 開頭的類。app

二、IO類和相關方法

IO 類雖然不少,但最基本的是 4 個抽象類:InputStream、OutputStream、Reader、Writer。最基本的方法也就是一個讀 read() 方法、一個寫 write() 方法。方法具體的實現仍是要看繼承這 4 個抽象類的子類,畢竟咱們平時使用的也是子類對象。這些類中的一些方法都是(Native)本地方法、因此並無 Java 源代碼,這裏給出筆者以爲不錯的 Java IO 源碼分析 傳送門,按照上面這個思路看,先看子類基本方法,而後在看看子類中還新增了那些方法,相信你也能夠看懂的,我這裏就只對上後面說的經常使用的類進行總結。源碼分析

先來看 InputStream 和 OutStream 中的方法簡介,由於都是抽象類、大都是抽象方法、因此就不貼源碼嘍!注意這裏的讀取和寫入,其實就是獲取(輸入)數據和輸出數據。

InputStream 類

方法 方法介紹
public abstract int read() 讀取數據
public int read(byte b[]) 將讀取到的數據放在 byte 數組中,該方法其實是根據下面的方法實現的,off 爲 0,len 爲數組的長度
public int read(byte b[], int off, int len) 從第 off 位置讀取 len 長度字節的數據放到 byte 數組中,流是以 -1 來判斷是否讀取結束的(注意這裏讀取的雖然是一個字節,可是返回的倒是 int 類型 4 個字節,這裏固然是有緣由,這裏就再也不細說了,推薦這篇文章,連接
public long skip(long n) 跳過指定個數的字節不讀取,想一想看電影跳過片頭片尾
public int available() 返回可讀的字節數量
public void close() 讀取完,關閉流,釋放資源
public synchronized void mark(int readlimit) 標記讀取位置,下次還能夠從這裏開始讀取,使用前要看當前流是否支持,可使用 markSupport() 方法判斷
public synchronized void reset() 重置讀取位置爲上次 mark 標記的位置
public boolean markSupported() 判斷當前流是否支持標記流,和上面兩個方法配套使用

OutputStream 類

方法 方法介紹
public abstract void write(int b) 寫入一個字節,能夠看到這裏的參數是一個 int 類型,對應上面的讀方法,int 類型的 32 位,只有低 8 位才寫入,高 24 位將捨棄。
public void write(byte b[]) 將數組中的全部字節寫入,和上面對應的 read() 方法相似,實際調用的也是下面的方法。
public void write(byte b[], int off, int len) 將 byte 數組從 off 位置開始,len 長度的字節寫入
public void flush() 強制刷新,將緩衝中的數據寫入
public void close() 關閉輸出流,流被關閉後就不能再輸出數據了

再來看 Reader 和 Writer 類中的方法,你會發現和上面兩個抽象基類中的方法很像。

Reader 類

方法 方法介紹
public int read(java.nio.CharBuffer target) 讀取字節到字符緩存中
public int read() 讀取單個字符
public int read(char cbuf[]) 讀取字符到指定的 char 數組中
abstract public int read(char cbuf[], int off, int len) 從 off 位置讀取 len 長度的字符到 char 數組中
public long skip(long n) 跳過指定長度的字符數量
public boolean ready() 和上面的 available() 方法相似
public boolean markSupported() 判斷當前流是否支持標記流
public void mark(int readAheadLimit) 標記讀取位置,下次還能夠從這裏開始讀取,使用前要看當前流是否支持,可使用 markSupport() 方法判斷
public void reset() 重置讀取位置爲上次 mark 標記的位置
abstract public void close() 關閉流釋放相關資源

Writer 類

方法 方法介紹
public void write(int c) 寫入一個字符
public void write(char cbuf[]) 寫入一個字符數組
abstract public void write(char cbuf[], int off, int len) 從字符數組的 off 位置寫入 len 數量的字符
public void write(String str) 寫入一個字符串
public void write(String str, int off, int len) 從字符串的 off 位置寫入 len 數量的字符
public Writer append(CharSequence csq) 追加吸入一個字符序列
public Writer append(CharSequence csq, int start, int end) 追加寫入一個字符序列的一部分,從 start 位置開始,end 位置結束
public Writer append(char c) 追加寫入一個 16 位的字符
abstract public void flush() 強制刷新,將緩衝中的數據寫入
abstract public void close() 關閉輸出流,流被關閉後就不能再輸出數據了

下面咱們就直接使用他們的子類,在使用中再介紹下面沒有的新方法。

一、讀取控制檯中的輸入

import java.io.*;

public class IOTest {
    public static void main(String[] args) throws IOException {
        // 三個測試方法
// test01();
// test02();
        test03();
    }

    public static void test01() throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        System.out.println("請輸入一個字符");
        char c;
        c = (char) bufferedReader.read();
        System.out.println("你輸入的字符爲"+c);
    }

    public static void test02() throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        System.out.println("請輸入一個字符,按 q 鍵結束");
        char c;
        do {
            c = (char) bufferedReader.read();
            System.out.println("你輸入的字符爲"+c);
        } while (c != 'q');
    }

    public static void test03() throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        System.out.println("請輸入一行字符");
        String str = bufferedReader.readLine();
        System.out.println("你輸入的字符爲" + str);
    }
}

複製代碼

至於控制檯的輸出,咱們其實一直都在使用呢,System.out.println() ,out 實際上是 PrintStream 類對象的引用,PrintStream 類中固然也有 write() 方法,可是咱們更經常使用 print() 方法和 println() 方法,由於這兩個方法能夠輸出的內容種類更多,好比一個打印一個對象,實際調用的對象的 toString() 方法。

二、二進制文件的寫入和讀取

注意這裏文件的路徑,能夠根據本身狀況改一下,雖然這裏的文件後綴是txt,但該文件倒是一個二進制文件,並不能直接查看。

@Test
    public void test04() throws IOException {
        byte[] bytes = {12,21,34,11,21};
        FileOutputStream fileOutputStream = new FileOutputStream(new File("").getAbsolutePath()+"/io/test.txt");
        // 寫入二進制文件,直接打開會出現亂碼
        fileOutputStream.write(bytes);
        fileOutputStream.close();
    }

    @Test
    public void test05() throws IOException {
        FileInputStream fileInputStream = new FileInputStream(new File("").getAbsolutePath()+"/io/test.txt");
        int c;
        // 讀取寫入的二進制文件,輸出字節數組
        while ((c = fileInputStream.read()) != -1) {
            System.out.print(c);
        }
    }
複製代碼

三、文本文件的寫入和讀取

write() 方法和 append() 方法並非像方法名那樣,一個是覆蓋內容,一個是追加內容,append() 內部也是 write() 方法實現的,也非說區別,也就是 append() 方法能夠直接寫 null,而 write() 方法須要把 null 當成一個字符串寫入,因此二者並沒有本質的區別。須要注意的是這裏並無指定文件編碼,可能會出現亂碼的問題。

@Test
    public void test06() throws IOException {
        FileWriter fileWriter = new FileWriter(new File("").getAbsolutePath()+"/io/test.txt");
        fileWriter.write("Hello,world!\n歡迎來到 java 世界\n");
        fileWriter.write("不會覆蓋文件本來的內容\n");
// fileWriter.write(null); 不能直接寫入 null
        fileWriter.append("並非追加一行內容,不要被方法名迷惑\n");
        fileWriter.append(null);
        fileWriter.flush();
        System.out.println("文件的默認編碼爲" + fileWriter.getEncoding());
        fileWriter.close();
    }

    @Test
    public void test07() throws IOException {
        FileWriter fileWriter = new FileWriter(new File("").getAbsolutePath()+"/io/test.txt", false); // 關閉追加模式,變爲覆蓋模式
        fileWriter.write("Hello,world!歡迎來到 java 世界\n");
        fileWriter.write("我來覆蓋文件本來的內容");
        fileWriter.append("我是下一行");
        fileWriter.flush();
        System.out.println("文件的默認編碼爲" + fileWriter.getEncoding());
        fileWriter.close();
    }

    @Test
    public void test08() throws IOException {
        FileReader fileReader = new FileReader(new File("").getAbsolutePath()+"/io/test.txt");
        BufferedReader bufferedReader = new BufferedReader(fileReader);
        String str;
        while ((str = bufferedReader.readLine()) != null) {
            System.out.println(str);
        }
        fileReader.close();
        bufferedReader.close();
    }

    @Test
    public void test09() throws IOException {
        FileReader fileReader = new FileReader(new File("").getAbsolutePath()+"/io/test.txt");
        int c;
        while ((c = fileReader.read()) != -1) {
            System.out.print((char) c);
        }
    }
複製代碼

使用字節流和字符流的轉換類 InputStreamReader 和 OutputStreamWriter 能夠指定文件的編碼,使用 Buffer 相關的類來讀取文件的每一行。

@Test
    public void test10() throws IOException {
        FileOutputStream fileOutputStream = new FileOutputStream(new File("").getAbsolutePath()+"/io/test2.txt");
        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream, "GBK"); // 使用 GBK 編碼文件
        outputStreamWriter.write("Hello,world!\n歡迎來到 java 世界\n");
        outputStreamWriter.append("另一行內容");
        outputStreamWriter.flush();
        System.out.println("文件的編碼爲" + outputStreamWriter.getEncoding());
        outputStreamWriter.close();
        fileOutputStream.close();
    }

    @Test
    public void test11() throws IOException {
        FileInputStream fileInputStream = new FileInputStream(new File("").getAbsolutePath()+"/io/test2.txt");
        InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, "GBK"); // 使用 GBK 解碼文件
        BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
        String str;
        while ((str = bufferedReader.readLine()) != null) {
            System.out.println(str);
        }
        bufferedReader.close();
        inputStreamReader.close();
    }
複製代碼

四、複製文件

這裏筆者作了一些測試,不使用緩衝對文件複製時間的影響,文件的複製實質仍是文件的讀寫。緩衝流是處理流,是對節點流的裝飾。

注:這裏的時間是在我這臺華碩筆記本上測試獲得的,只是爲了說明使用緩衝對文件的讀寫有好處。

@Test
    public void test12() throws IOException {
        // 輸入和輸出都使用緩衝流
        FileInputStream in = new FileInputStream("E:\\視頻資料\\大數據原理與應用\\1.1大數據時代.mp4");
        BufferedInputStream inBuffer = new BufferedInputStream(in);
        FileOutputStream out = new FileOutputStream("1.1大數據時代.mp4");
        BufferedOutputStream outBuffer = new BufferedOutputStream(out);
        int len = 0;
        byte[] bs = new byte[1024];
        long begin = System.currentTimeMillis();
        while ((len = inBuffer.read(bs)) != -1) {
            outBuffer.write(bs, 0, len);
        }
        System.out.println("複製文件所需的時間:" + (System.currentTimeMillis() - begin)); // 平均時間約 200 多毫秒
        inBuffer.close();
        in.close();
        outBuffer.close();
        out.close();
    }


    @Test
    public void test13() throws IOException {
        // 只有輸入使用緩衝流
        FileInputStream in = new FileInputStream("E:\\視頻資料\\大數據原理與應用\\1.1大數據時代.mp4");
        BufferedInputStream inBuffer = new BufferedInputStream(in);
        FileOutputStream out = new FileOutputStream("1.1大數據時代.mp4");
        int len = 0;
        byte[] bs = new byte[1024];
        long begin = System.currentTimeMillis();
        while ((len = inBuffer.read(bs)) != -1) {
            out.write(bs, 0, len);
        }
        System.out.println("複製文件所需時間:" + (System.currentTimeMillis() - begin)); // 平均時間約 500 多毫秒
        inBuffer.close();
        in.close();
        out.close();
    }

    @Test
    public void test14() throws IOException {
        // 輸入和輸出都不使用緩衝流
        FileInputStream in = new FileInputStream("E:\\視頻資料\\大數據原理與應用\\1.1大數據時代.mp4");
        FileOutputStream out = new FileOutputStream("1.1大數據時代.mp4");
        int len = 0;
        byte[] bs = new byte[1024];
        long begin = System.currentTimeMillis();
        while ((len = in.read(bs)) != -1) {
            out.write(bs, 0, len);
        }
        System.out.println("複製文件所需時間:" + (System.currentTimeMillis() - begin)); // 平均時間 700 多毫秒
        in.close();
        out.close();
    }

    @Test
    public void test15() throws IOException {
        // 不使用緩衝
        FileInputStream in = new FileInputStream("E:\\視頻資料\\大數據原理與應用\\1.1大數據時代.mp4");
        FileOutputStream out = new FileOutputStream("1.1大數據時代.mp4");
        int len = 0;
        long begin = System.currentTimeMillis();
        while ((len = in.read()) != -1) {
            out.write(len);
        }
        System.out.println("複製文件所需時間:" + (System.currentTimeMillis() - begin)); // 平均時間約 160000 毫秒,約 2 分多鐘
        in.close();
        out.close();
    }
複製代碼

關於序列化和反序列化的內容,這裏給出我以前寫的博客,傳送門。 總結:Java IO 類不少,可是把握住整個體系,掌握關鍵的方法,學習起來就會輕鬆不少,看完這篇文章,你是否以爲 Java IO 並無你想的那麼難呢?歡迎你在下方留言,和咱們討論。

歡迎關注下方的微信公衆號哦,另外還有各類學習資料免費分享!

編程心路
相關文章
相關標籤/搜索