Java 基礎之詳解 Java IO

Java IO基本概念

Java IO:即Java輸入/輸出系統,區分Java的輸入和輸出:把本身當成程序, 當你從外邊讀數據到本身這裏就用輸入(InputStream/Reader), 向外邊寫數據就用輸出(OutputStream/Writer)。html

Stream:Java中將數據的輸入輸出抽象爲,流是一組有順序的,單向的,有起點和終點的數據集合,就像水流。按照流中的最小數據單元又分爲字節流和字符流。
1) 字節流:以8位(即1byte, 8bit)做爲一個數據單元,數據流中最小的數據單元是字節
2) 字符流:以16位(即1char, 2byte, 16bit)做爲一個數據單元,數據流中最小的數據單元是字符, Java中的字符是Unicode編碼,一個字符佔用兩個字節。java

IO的分類

enter image description here

流式部分和非流式部分

Java的IO主要包含兩個部分:web

  1. 流式部分:是IO的主體部分,也是本文介紹的重點,
    流式部分根據流向分爲輸入流(InputStream/Reader)和輸出流(OutputStream/Writer),
    根據數據不一樣的操做單元,分爲字節流(InputStream/OutputStream)和字符流(Reader/Writer),依據字節流和字符流,Java定義了用來操做數據的抽象基類InputStream/OutputStream 和 Reader/Writer,再根據不一樣應用場景(或功能),在這兩種抽象基類上基於數據載體或功能派上出不少子類,用來知足文件,網絡,管道等不一樣場景的IO需求,從而造成了Java的基本IO體系。
    下面是Java IO體系中經常使用的流類:
    enter image description here
  2. 非流式部分:主要包含一些輔助流式部分的類,如:SerializablePermission類、File類、RandomAccessFile類和FileDescriptor等;

節點流和處理流

Java io分類方式有不少,根據是否直接處理數據,Java io又分爲節點流和處理流,節點流是真正直接處理數據的;處理流是裝飾加工節點流的。面試

節點流

  • 文件流:FileInputStream,FileOutputStrean,FileReader,FileWriter,它們都會直接操做文件,直接與OS底層交互。所以他們被稱爲節點流 ,注意:使用這幾個流的對象以後,須要關閉流對象,由於java垃圾回收器不會主動回收。不過在Java7以後,能夠在 try() 括號中打開流,最後程序會自動關閉流對象,再也不須要顯示地close。
  • 數組流:ByteArrayInputStream,ByteArrayOutputStream,CharArrayReader,CharArrayWriter,對數組進行處理的節點流;
  • 字符串流:StringReader,StringWriter,其中StringReader能從String中讀取數據並保存到char數組。
  • 管道流:PipedInputStream,PipedOutputStream,PipedReader,PipedWrite,對管道進行處理的節點流。算法

    處理流

    處理流是對一個已存在的流的鏈接和封裝,經過所封裝的流的功能調用實現數據讀寫。如BufferedReader。處理流的構造方法老是要帶一個其餘的流對象作參數。
    經常使用處理流(經過關閉處理流裏面的節點流來關閉處理流)api

  • 緩衝流 :BufferedImputStrean,BufferedOutputStream,BufferedReader ,BufferedWriter,須要父類做爲參數構造,增長緩衝功能,避免頻繁讀寫硬盤,能夠初始化緩衝數據的大小,因爲帶了緩衝功能,因此就寫數據的時候須要使用flush方法,另外,BufferedReader提供一個readLine()方法能夠讀取一行,而FileInputStream和FileReader只能讀取一個字節或者一個字符,所以BufferedReader也被稱爲行讀取器
  • 轉換流:InputStreamReader,OutputStreamWriter,要inputStream 或OutputStream做爲參數,實現從字節流到字符流的轉換,咱們常常在讀取鍵盤輸入(System.in)或網絡通訊的時候,須要使用這兩個類。
  • 數據流:DataInputStream,DataOutputStream,提供將基礎數據類型寫入到文件中,或者讀取出來。數組

字節流

字節輸入流

下面是IO中輸入字節流的繼承關係。
InputStreamtomcat

  • ByteArrayInputStream
  • FileInputStream
  • FilterInputStream
  • * PushbackInputStream
  • * DataInputStream
  • * BufferedInputStream
  • LineNumberInputStream
  • ObjectInputStream
  • PipedInputStream
  • SequenceInputStream
  • StringBufferInputStream
    總結:
  1. InputStream是全部的輸入字節流的父類,它是一個抽象類。
  2. PushbackInputStream、DataInputStream和BufferedInputStream都是處理流,他們的的父類是FilterInputStream。
  3. ByteArrayInputStream、StringBufferInputStream、FileInputStream是三種基本的介質流,它們分別從Byte數組、StringBuffer、和本地文件中讀取數據。PipedInputStream是從與其它線程共用的管道中讀取數據。

InputStream中的三個基本的讀方法
abstract int read() :讀取一個字節數據,並返回讀到的數據,若是返回-1,表示讀到了輸入流的末尾。
int read(byte[] b) :將數據讀入一個字節數組,同時返回實際讀取的字節數。若是返回-1,表示讀到了輸入流的末尾。
int read(byte[] b, int off, int len) :將數據讀入一個字節數組,同時返回實際讀取的字節數。若是返回-1,表示讀到了輸入流的末尾。off指定在數組b中存放數據的起始偏移位置;len指定讀取的最大字節數。網絡

字節輸出流

下面是IO中輸出字節流的繼承關係。
OutputStreamdom

  • ByteArrayOutputStream
  • FileOutputStream
  • FilterOutputStream
    • BufferedOutputStream
    • DataOutputStream
    • PrintStream
  • ObjectOutputStream
  • PipedOutputStream
    總結:
  1. OutputStream是全部的輸出字節流的父類,它是一個抽象類。
  2. ByteArrayOutputStream、FileOutputStream是兩種基本的介質流,它們分別向Byte數組、和本地文件中寫入數據。PipedOutputStream是向與其它線程共用的管道中寫入數據,
  3. BufferedOutputStream、DataOutputStream和PrintStream都是處理流,他們的的父類是FilterOutputStream。

outputStream中的三個基本的寫方法

  • abstract void write(int b):往輸出流中寫入一個字節。
  • void write(byte[] b) :往輸出流中寫入數組b中的全部字節。
  • void write(byte[] b, int?off, int?len) :往輸出流中寫入數組b中從偏移量off開始的len個字節的數據。

其它重要方法:

  • void flush() :刷新輸出流,強制緩衝區中的輸出字節被寫出。
  • void close() :關閉輸出流,釋放和這個流相關的系統資源。

字節流的輸入與輸出的對應

java io的輸入和輸出是高度對應的,下圖表示字節流的輸入與輸出的對應關係。
enter image description here
上圖中藍色的爲主要的對應部分,紅色的部分是不對應部分。紫色的虛線部分表明這些流通常要搭配使用。
咱們主要看看這些字節流中不對稱的幾個類:

  1. PushbackInputStream 爲另外一個輸入流添加性能,即「推回 (push back)」或「取消讀取 (unread)」一個字節的能力。
  2. SequenceInputStream能夠認爲是一個工具類,將兩個或者多個輸入流當成一個輸入流依次讀取。徹底能夠從IO包中去除,還徹底不影響IO包的結構。
  3. PrintStream也能夠認爲是一個輔助工具。主要能夠向其餘輸出流,或者FileInputStream寫入數據,自己內部實現仍是帶緩衝的。本質上是對其它流的綜合運用的一個工具而已。同樣能夠踢出IO包!System.io和System.out就是PrintStream的實例!
  4. StringBufferInputStream和StringBufferInputStream已通過時,還容許它存在只是爲了保持版本的向下兼容而已。

搭配使用的三對類:
ObjectInputStream/ObjectOutputStream和DataInputStream/DataOutputStream主要是要求寫對象/數據和讀對象/數據的次序要保持一致,不然可能不能獲得正確的數據,甚至拋出異常(通常會如此);PipedInputStream/PipedOutputStream在建立時通常就一塊兒建立,調用它們的讀寫方法時會檢查對方是否存在,或者關閉!

字符流

字符輸入流Reader

下面是IO中輸入字符流的繼承關係。
Reader

  • BufferedReader
  • LineNumberReader
  • CharArrayReader
  • FilterReader
    • PushbackReader
  • InputStreamReader
  • FileReader
  • PipedReader
  • StringReader
    總結:
  1. Reader是全部的輸入字符流的父類,它是一個抽象類。
  2. CharReader、StringReader是兩種基本的介質流,它們分別將Char數組、String中讀取數據。PipedReader是從與其它線程共用的管道中讀取數據。
  3. BufferedReader很明顯就是一個裝飾器,它和其子類負責裝飾其它Reader對象。
  4. FilterReader是全部自定義具體裝飾流的父類,其子類PushbackReader對Reader對象進行裝飾,會增長一個行號。
  5. InputStreamReader是一個鏈接字節流和字符流的橋樑,它將字節流轉變爲字符流。

Reader基本的三個讀方法(和字節流對應):
(1) public int read() throws IOException; 讀取一個字符,返回值爲讀取的字符
(2) public int read(char cbuf[]) throws IOException; 讀取一系列字符到數組cbuf[]中,返回值爲實際讀取的字符的數量
(3) public abstract int read(char cbuf[],int off,int len) throws IOException; 讀取len個字符,從數組cbuf[]的下標off處開始存放,返回值爲實際讀取的字符數量,該方法必須由子類實現

字符輸出流Writer

下面是IO中輸出字符流的繼承關係。
Writer

  • BufferedWriter
  • CharArrayWriter
  • FilterWriter
  • OutputStreamWriter
  • FileWriter
  • PipedWriter
  • PrintWriter
  • StringWriter
    總結(和字節輸出流對應):
  1. Writer是全部的輸出字符流的父類,它是一個抽象類。
  2. CharArrayWriter、StringWriter是兩種基本的介質流,它們分別向Char數組、String中寫入數據。PipedWriter是向與其它線程共用的管道中寫入數據,
  3. BufferedWriter是一個裝飾器爲Writer提供緩衝功能。
  4. PrintWriter和PrintStream極其相似,功能和使用也很是類似。
  5. OutputStreamWriter是OutputStream到Writer轉換的橋樑,它的子類FileWriter其實就是一個實現此功能的具體類。

writer的主要寫方法:

(1) public void write(int c) throws IOException; //寫單個字符

(2) public void write(char cbuf[]) throws IOException; //將字符數組cbuf[]寫到輸出流

(3) public abstract void write(char cbuf[],int off,int len) throws IOException; //將字符數組cbuf[]中的從索引爲off的位置處開始的len個字符寫入輸出流

(4) public void write(String str) throws IOException; //將字符串str中的字符寫入輸出流

(5) public void write(String str,int off,int len) throws IOException; //將字符串str 中從索引off開始處的len個字符寫入輸出流

字符流的輸入與輸出的對應

可參照(字節流的輸入與輸出的對應)記憶
enter image description here

Java IO常見用法

一、讀取鍵盤輸入,打印到控制檯
在刷題網站刷算法題的時候,在程序開頭都須要和鍵盤進行交互,用到的就是行奪取器BufferedReader和轉換流InputStreamReader。

public static void keyInAndPrintConsole() throws IOException {
        PrintWriter out = null;
        BufferedReader br = null;
        try{
            System.out.println("請輸入:");
            out = new PrintWriter(System.out, true);
            br = new BufferedReader(new InputStreamReader(System.in));
            String line = null;
            while ((line = br.readLine()) != null) {
                if (line.equals("exit")) {
                    System.exit(1);
                }
                out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            out.close();
            br.close();
        }
}

運行結果:
enter image description here
2 、用字節流讀寫文件
由於是用字節流來讀媒介,因此對應的是InputStream和OutputStream並且媒介對象是文件,因此用到子類是FileInputStream和FileOutputStream,這裏還能夠經過BufferedInputStream用緩衝流來讀取文件。

public static void readAndWriteByteToFile() throws IOException {
        InputStream is =null;
        OutputStream os = null;
        try {
            // 在try()中打開文件會在結尾自動關閉
            is = new FileInputStream("D:/FileInputStreamTest.txt");
            os = new FileOutputStream("D:/FileOutputStreamTest.txt");
            byte[] buf = new byte[4];
            int hasRead = 0;
            while ((hasRead = is.read(buf)) > 0) {
                os.write(buf, 0, hasRead);
            }
            System.out.println("write success");
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            os.close();
            is.close();
        }
    }

運行結果:
enter image description here

enter image description here
三、用字符流進行讀寫操做
(1)FileReader和FileWriter

// 在try() 中打開的文件, JVM會自動關閉
    public static void readAndWriteCharToFile() throws IOException{
        Reader reader = null;
        Writer writer =null;
        try {
            File readFile = new File("d:/FileInputStreamTest.txt");
            reader = new FileReader(readFile);

            File writeFile = new File("d:/FileOutputStreamTest.txt");
            writer = new FileWriter(writeFile);

            char[] byteArray = new char[(int) readFile.length()];
            int size = reader.read(byteArray);
            System.out.println("大小:" + size + "個字符;內容:" + new String(byteArray));
            writer.write(byteArray);

        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            reader.close();
            writer.close();
        }
    }

運行結果:
enter image description here

enter image description here
(2)StringReader和StringWriter

public static void stringNode() throws IOException {
    StringReader sr =null;
    StringWriter sw =null;
    try {
        String str = "學習不刻苦\n" + "不如賣紅薯;\n";
        char[] buf = new char[32];
        int hasRead = 0;
        // StringReader將以String字符串爲節點讀取數據
        sr = new StringReader(str);
        while ((hasRead = sr.read(buf)) > 0) {
            System.out.print(new String(buf, 0, hasRead));
        }

        // 因爲String是一個不可變類,所以建立StringWriter時,其實是以一個StringBuffer做爲輸出節點
        sw = new StringWriter();
        sw.write("黑夜給了我黑色的眼睛\n");
        sw.write("我卻用它尋找光明\n");
        // toString()返回sw節點內的數據
        System.out.println(sw.toString());
    } catch (Exception e) {
        e.printStackTrace();
    }finally{
        sw.close();
        sr.close();
    }
}

運行結果:
enter image description here
四、字節流轉換爲字符流
在5.3中用字符流讀文件時,打印到控制檯的中文會亂碼,使用轉換流能夠解決這一問題

public static void convertByteToChar() throws IOException {
    InputStream is =null;
    Reader reader = null;
    try {
        File file = new File("d:/FileInputStreamTest.txt");
        is = new FileInputStream(file);
        reader = new InputStreamReader(is,"gbk");
        char[] byteArray = new char[(int) file.length()];
        int size = reader.read(byteArray);
        System.out.println("大小:" + size + ";內容:" + new String(byteArray));
    } catch (Exception e) {
        e.printStackTrace();
    }finally{
        reader.close();
        is.close();
    }
}

運行結果:
enter image description here
五、隨機讀寫文件
使用RandomAccessFile能夠實現對文件的隨機讀取,主要是經過seek()方法實現指針偏移。

public static void randomAccessFileReadAndWrite() throws IOException {
    RandomAccessFile randomAccessFile =null;
    try {
        // 建立一個RandomAccessFile對象
        randomAccessFile = new RandomAccessFile("d:/File.txt", "rw");
        // 經過seek方法來移動指針
        randomAccessFile.seek(10);
        // 獲取當前指針
        long pointerBegin = randomAccessFile.getFilePointer();
        // 從當前指針開始讀
        byte[] contents = new byte[10];
        randomAccessFile.read(contents);
        long pointerEnd = randomAccessFile.getFilePointer();
        System.out.println("pointerBegin:" + pointerBegin + "\n" + "pointerEnd:" + pointerEnd + "\n" + new String(contents));

        randomAccessFile.seek(20);
        // 獲取當前指針
        long begin = randomAccessFile.getFilePointer();
        randomAccessFile.write(contents);
        long end = randomAccessFile.getFilePointer();
        System.out.println("begin:" + begin + "\n" + "end:" + end + "\n");
    } catch (Exception e) {
        e.printStackTrace();
    }finally{
        randomAccessFile.close();
    }
}

運行結果:
操做前文件內容以下圖:
enter image description here
操做後控制檯打印信息:
enter image description here
操做後的文件內容:
enter image description here
六、讀寫管道
管道流要成對使用

public static void piped() throws IOException {
    final PipedOutputStream output = new PipedOutputStream();
    final PipedInputStream input = new PipedInputStream(output);
    Thread thread1 = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                output.write("Hello world, pipe!".getBytes());
            } catch (IOException e) {
            }
        }
    });
    Thread thread2 = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                int data = input.read();
                while (data != -1) {
                    System.out.print((char) data);
                    data = input.read();
                }
            } catch (IOException e) {
            } finally {
                try {
                    input.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    });
    thread1.start();
    thread2.start();
}

運行結果:
enter image description here
七、將多個輸入流當成一個輸入流依次讀取

public static void sequeue() throws IOException {
    FileInputStream fistream1 =null;
    FileInputStream fistream2 =null;
    SequenceInputStream sistream =null;
    FileOutputStream fostream =null;
    try {
         fistream1 = new FileInputStream("d:/A.txt");
         fistream2 = new FileInputStream("d:/B.txt");
         sistream = new SequenceInputStream(fistream1, fistream2);
         fostream = new FileOutputStream("d:/C.txt");
         int temp;
         while( ( temp = sistream.read() ) != -1) {
            System.out.print( (char) temp );
            fostream.write(temp);
         }
    } catch (Exception e) {
        e.printStackTrace();
    }finally{
        fostream.close();
        sistream.close();
        fistream1.close();
        fistream2.close();
    }
}

運行結果:
enter image description here

enter image description here
八、推回輸入流使用實例

public static void pushback() throws FileNotFoundException, IOException {
    try (PushbackReader pr = new PushbackReader(new FileReader("D:/A.txt"), 64)) {
        char[] buf = new char[32];
        String lastContent = "";
        int hasRead = 0;
        while ((hasRead = pr.read(buf)) > 0) {
            String content = new String(buf, 0, hasRead);
            int targetIndex = 0;
            if ((targetIndex = (lastContent + content).indexOf("A")) > 0) {
                System.out.println(targetIndex);
                pr.unread((lastContent + content).toCharArray());
                if (targetIndex > 32) {
                    buf = new char[targetIndex];
                }
                pr.read(buf, 0, targetIndex);
                System.out.println(new String(buf, 0, targetIndex));
                System.out.println(new String(buf, targetIndex, buf.length-targetIndex));
                System.exit(0);
            } else {
                System.out.println(lastContent);
                lastContent = content;
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

運行結果:
enter image description here
enter image description here

JavaIO常見面試題

一、字節流和字符流的區別?
(1)讀寫單位不一樣:字節流以字節(8bit)爲單位,字符流以字符爲單位,根據碼錶映射字符,一次可能讀多個字節。
(2)處理對象不一樣:字節流能處理全部類型的數據(如圖片、avi等),而字符流只能處理字符類型的數據。
(3)字節流沒有緩衝區,是直接輸出的,而字符流是輸出到緩衝區的。所以在輸出時,字節流不調用colse()方法時,信息已經輸出了,而字符流只有在調用close()方法關閉緩衝區時,信息才輸出。要想字符流在未關閉時輸出信息,則須要手動調用flush()方法。

二、什麼是節點流,什麼是處理流,它們各有什麼用處,處理流的建立有什麼特徵?
見上文:節點流和處理流;
處理流的構造器必需要 傳入節點流的子類

三、把包括基本類型在內的數據和字符串按順序輸出到數據源,或者按照順序從數據源讀入,通常用哪兩個流?
DataInputStream DataOutputStream

四、什麼叫對象序列化,什麼是反序列化,實現對象序列化須要作哪些工做?
對象序列化:將對象以二進制的形式保存在硬盤上;
反序列化:將二進制的文件轉化爲對象讀取;
實現serializable接口能夠實現對象序列化,其中沒有須要實現的方法,implements Serializable只是爲了標註該對象是可被序列化的。
例如,在web開發中,若是對象被保存在了Session中,tomcat在重啓時要把Session對象序列化到硬盤,這個對象就必須實現Serializable接口。若是對象要通過分佈式系統進行網絡傳輸,被傳輸的對象就必須實現Serializable接口。

五、什麼是Filter流 有哪些?
FilterStream是一種IO流,主要做用是用來對存在的流增長一些額外的功能,像給目標文件增長源文件中不存在的行數,或者增長拷貝的性能等。在java.io包中主要由4個可用的filter Stream。兩個字節filter stream,兩個字符filter stream. 分別是FilterInputStream, FilterOutputStream, FilterReader and FilterWriter.這些類是抽象類,不能被實例化的。
Filter流的子類:

  • DataInputStream 有些特殊的方法如readInt(), readDouble()和readLine() 等能夠讀取一個 int, double和一個string。
  • BufferedInputStream 增長性能
  • PushbackInputStream 推送要求的字節到系統中

六、說說RandomAccessFile?
它在java.io包中是一個特殊的類,既不是輸入流也不是輸出流,它二者均可以作到。他是Object的直接子類。一般來講,一個流只有一個功能,要麼讀,要麼寫。可是RandomAccessFile既能夠讀文件,也能夠寫文件。 並且RandomAccessFile支持對文件的隨機訪問,實例可見上文:5.5隨機讀寫文件。

總結

不少初學者剛剛學習java的IO時會比較茫然,確實IO類不少,不容易記憶,咱們能夠嘗試對其進行總結記憶,把流式部分歸納爲:兩個對應一個橋樑一個隨機。
兩個對應指:
1.字節流(Byte Stream)和字符流(Char Stream)的對應;
2.輸入和輸出的對應。
一個橋樑指:從字節流到字符流的橋樑。對應於輸入和輸出爲InputStreamReader和OutputStreamWriter;
一個隨機是:RandomAccessFile。能夠隨機讀取文件。

參考

http://tool.oschina.net/apidocs/apidoc?api=jdk-zh
https://blog.csdn.net/lgzaaron/article/details/52202443
https://blog.csdn.net/qq_25184739/article/details/51205186#
https://www.2cto.com/kf/201704/524652.html
https://blog.csdn.net/baobeisimple/article/details/1713797#t4
https://blog.csdn.net/lihuapiao/article/details/50731405
https://blog.csdn.net/zhuojiuyihu/article/details/7307567
相關文章
相關標籤/搜索