Java----IO詳解

IO是java中很重要的一塊,包含了對文件,文件目錄,二進制流文件等各種資源的操作,同時IO操作複雜多變,用得好會提高性能,用的不好導致效率低下,所以詳細瞭解並掌握IO相關知識是很有必要的。

IO主要分爲字節流和字符流,然後每一種又分爲輸入流和輸出流,涉及到的類很多,下面一一分析。

字節流—-InputStream和OutputStream

先上類圖,InputStream這個大家庭的:

這裏寫圖片描述

(1)文件流:FileInputStream

    public static void testStream(){
        try {
            byte [] bytes = new byte [12];
            FileInputStream fis = new FileInputStream(new File("D:\\javaFile\\test/hah.txt"));
            while(fis.read(bytes)!=-1){
                System.out.println("Each:"+new String(bytes));
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

說明:FileInputStream是沒有緩衝功能的,但是上面的程序裏fis將讀出的字節放到了字節數組bytes中實現了緩存功能。

while(fis.read()!=-1) //這樣就沒有緩存功能,一個一個字節讀取

(2)對象流:ObjectInputStream

    public static void testStream(){
        try {
            //爲了讀取,我們先寫一個文件,注意這個文件是二進制的,所以打開看是亂碼
            FileOutputStream fos = new FileOutputStream("D:\\javaFile\\test\\student.txt");
            ObjectOutputStream oop = new ObjectOutputStream(fos);
            oop.writeObject(new Student(001 , "wangliang" , true , 22));
            oop.writeObject(new Student(002 , "lisi" , false , 22));
            //開始讀取
            FileInputStream fis = new FileInputStream("D:\\javaFile\\test\\student.txt");
            ObjectInputStream ons = new ObjectInputStream(fis);
            //一次只能讀取一個Object對象出來
            Student stu = (Student) ons.readObject();

            System.out.println("age:"+stu.stuAge);
            System.out.println("No:"+stu.stuId);
            System.out.println("name:"+stu.stuName);
            System.out.println("sex:"+(stu.stuSex==true?"男":"女"));

        } catch (IOException | ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

輸出結果如下:

age:22
No:1
name:wangliang
sex:

從結果可以看出,我們寫入了兩個對象,但是隻讀出了一個,那麼應該怎樣讀出所有的對象呢?方法是循環讀取,用EOFException作爲邊界結束,像這樣:

try{
            while(true){
            Student stu = (Student) ons.readObject();
            System.out.println("age:"+stu.stuAge);
            System.out.println("No:"+stu.stuId);
            System.out.println("name:"+stu.stuName);
            System.out.println("sex:"+(stu.stuSex==true?"男":"女"));
            }
            }catch(EOFException e){}

如果你覺得上面的方式不那麼優雅的話,還可以這樣:

//如果插入是可控的,那麼可以在結尾插入一個結束標記符,一般爲null.但是如果還要追加數據的話就比較麻煩了。
oop.writeObject(null);
//然後在讀取的時候讀到這個結束符就判定結束了
while(true){
            Student stu = (Student) ons.readObject();
            if(stu == null) break;
            System.out.println("age:"+stu.stuAge);
            System.out.println("No:"+stu.stuId);
            System.out.println("name:"+stu.stuName);
            System.out.println("sex:"+(stu.stuSex==true?"男":"女"));
            }

(3)字節數組流:ByteArrayInputStream

ByteArrayInputStream的兩個構造函數都需要接受一個byte數組,不同於其他流接受文件或者流。

        //ByteArrayInputStream
        byte[] byteArray = {'a','b',8,'c','d','e',-128,127,0,-1,2,'i'};
        ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
        int tem;
        while((tem = bais.read())!=-1){
            //在讀取到8後,跳過後面的c,d,e三個字節
            if(tem==8)
                bais.skip(3);
            System.out.println("byte:"+tem);
        }

這個類我不知道存在的意義是什麼?在知乎上找到一個回答是說將byte數組轉換爲InputStream,從而可以使用一些InputStream的API,但是我個人覺得直接操作byte數組更方便啊。歡迎留言討論。

(4)字符流:StringBufferInputStream

這個用作將一個字符串轉換爲流對象,現在早已被標記out了,可以用 StringReader或者ByteArrayInputStream代替。

(5)順序合併流:SequenceInputStream

這個流的作用呢,就是將幾個流按照順序一個一個去讀取出來,可以保證順序。

//第一種寫法
            FileInputStream fis = new FileInputStream("D:\\javaFile\\test\\hah.txt");
            FileInputStream fis1 = new FileInputStream("D:\\javaFile\\test\\hah1.txt");
            SequenceInputStream sis = new SequenceInputStream(fis , fis1);
            byte[] temp = new byte [1024];
            while(sis.read(temp)!=-1){
                System.out.println("Sequence:"+new String(temp));
            }
            //第二種寫法
            Enumeration en;
            Vector<FileInputStream> vectors = new Vector<FileInputStream>();
            vectors.addElement(fis1);
            vectors.addElement(fis);
            en = vectors.elements();
            SequenceInputStream sis2 = new SequenceInputStream(en);
            byte[] temp2 = new byte [1024];
            while(sis2.read(temp2)!=-1){
                System.out.println("Sequence2:"+new String(temp2));
            }

(6)管道流:PipedInputStream和PipedOutputStream

這兩個必須配套使用,管道流用於跨線程數據交互。首先將PipedInputStream和PipedOutputStream通過connect方法綁定,然後在A線程中通過PipedOutputStream寫數據,此時,寫入的數據會發送到與之關聯的PipedInputStream中,在線程B中就可以讀取數據了。

//管道流測試
        //創建輸入輸出流
        final PipedOutputStream pos = new PipedOutputStream();
        final PipedInputStream pis = new PipedInputStream();
        try {
            //關聯起來
            pos.connect(pis);
        } catch (IOException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }
        //線程A發數據
        new Thread(){
            @Override
            public void run(){
                try {
                    pos.write("hello,i come from thread A!".getBytes());
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }.start();
        //線程B接受數據
        new Thread(){
            @Override
            public void run(){
                byte [] temp = new byte[27];
                try {
                    pis.read(temp);
                    System.out.println("來自線程A的消息:"+new String(temp));
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }.start();

(7)回退流:PushbackInputStream

在讀取下一個字節(或者下一個字節數組)前用unread推回流的前端,注意這個會影響流的mark和reset方法,使用者兩個方法前一定要先判斷。

//PushbackInputStream測試
        try {
            FileInputStream fis = new FileInputStream("D:\\javaFile\\test\\hah.txt");
            //最多可以插入512個字節,是總共的字節數,不是一次的
            PushbackInputStream pbis = new PushbackInputStream(fis ,30 );
            byte [] temp3 = new byte[5];
            while(pbis.read(temp3)!=-1){
                if(new String(temp3).equals("hello")){
                    pbis.unread("i am contents".getBytes());
                    //continue;
                }
                System.out.println("PushbackInputStream:"+new String(temp3));
            }
        } catch (IOException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }

輸出結果是:hello,i am contents

(8)緩存流:BufferedInputStream

關於這個流需要好好說道說道;首先,看到這個流,再聯想到上面講FileInputStream時使用緩存時的場景,你可能會疑問有什麼區別呢?FileInputStream用read(byte [])也可以緩存啊。

第一:BufferedInputStream的read()方法會使用默認的byte[]來緩存,雖然還是一個字節一個字節的讀,但是是從緩存的byte[]裏面讀取了,而FileInputStream一個字節一個字節的直接從數據源讀取,IO操作增加了很多。

第二:BufferedInputStream存在的意義是封裝了mark和reset方法,如果有mark和reset的操作,用這個流比較好。

mark和reset

mark()是標記當前位置,reset可以回到標記的位置。光說不練假把式,上代碼:

//mark,reset測試
        byte[] samplebyte = {'a','b','c','d','e','f','h','i','j','k'};
        //將byte[]讀入ByteArrayInputStream
        ByteArrayInputStream bis = new ByteArrayInputStream(samplebyte);
        //用BufferedInputStream封裝ByteArrayInputStream
        BufferedInputStream buffer = new BufferedInputStream(bis);
        //開始讀取
        try {
            int curbyte;
            //標記只回退一次
            boolean haveMarked = false;
            while((curbyte=buffer.read())!=-1){
                //在讀取到c的時候mark,參數是限制最多字節數
                if(curbyte == 'c' && !haveMarked){
                    if(buffer.markSupported()){
                    buffer.mark(5);
                    haveMarked = true;
                    }
                }
                System.out.print(curbyte+",");
            }
            //在讀取到h的時候回到標記的位置
            if(buffer.markSupported())              
                    buffer.reset();
                    int curbyte2 ;
                    while((curbyte2=buffer.read())!=-1){
                        System.out.print(curbyte2+":");
                    }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

結果可以看到,讀取完了以後,從我們mark的位置開始從新讀取(這裏的從新讀取不是又進行一次IO操作,而是用之前緩存的那個byte[],這樣就提高了效率):

97,98,99,100,101,102,104,105,106,107,100:101:102:104:105:106:107:

關於mark(int readlimit)的參數是表明讀取多少字節標記才失效,但是實際中是取readlimit和BufferedInputStream類的緩衝區(默認緩存大小byte[1024*8])大小兩者中的最大值,而並非完全由readlimit確定。


字符流

顧名思義了,字符流呢是以字符爲操作單位了。
還是先上類圖:

這裏寫圖片描述

大部分類的用法和字節流的用法差不多,就不一一贅述了,只有一個LineNumberReader多了一個按行讀取的方法。

//LineNumberReader
        try {
            Reader fileReader2 = new FileReader("D:\\javaFile\\test\\hah.txt");
            LineNumberReader lineNumberReader = new LineNumberReader(fileReader2);
            String line;
            while((line=lineNumberReader.readLine())!=null){
                System.out.println("LineNumberReader:"+line);
            }
        } catch (IOException e2) {
            // TODO Auto-generated catch block
            e2.printStackTrace();
        }

        //CharArrayReader和BufferedReader
        char[] char1 = {'a' ,1,'我',234,1234};
        char[] tempchar;
        BufferedReader bufferReader = new BufferedReader(new CharArrayReader(char1));
        int temp;
        try {
            while((temp = bufferReader.read())!=-1){
                System.out.println("char:"+temp);
            }
        } catch (IOException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }

        //FileReader
        try {
            FileInputStream fileInputStream = new FileInputStream("D:\\javaFile\\test\\hah.txt");
            int bytecount = 0; 

            while(fileInputStream.read()!=-1){
                bytecount++;
            }
            System.out.println("字節數:"+bytecount);
            Reader fileReader = new FileReader("D:\\javaFile\\test\\hah.txt");
            int count = 0;
            while(fileReader.read()!=-1){
                count++;
            }
            System.out.println("字符數:"+count);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

基本上關於IO的知識點都梳理在這裏了,不管是字符流還是字節流,都只講解了輸入流,輸出流基本都和輸入流是一一對應的。IO涉及到的類比較多,系統完整的學習一次大有裨益。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章