java IO 詳解總結

weixin_34162695發表於2017-03-29
3778985-de5ce44cc1de0ce3.png
Paste_Image.png

這是java io 比較基本的一些處理流,除此之外我們還會提到一些比較深入的基於io的處理類,比如console類,SteamTokenzier,Externalizable介面,Serializable介面等等一些高階用法極其原理。

一、java io的開始:檔案
  1. 我們主要講的是流,流的本質也是對檔案的處理,我們循序漸進一步一步從檔案將到流去。
  2. java 處理檔案的類 File,java提供了十分詳細的檔案處理方法,舉了其中幾個例子。
package com.hxw.io;  
import java.io.*;  
public class FileExample{  
    public static void main(String[] args) {  
       
        createFile();  
    }  
   
  /** 
   * 檔案處理示例 
   */  
  public static void createFile() {  
     File f=new File("E:/電腦桌面/jar/files/create.txt");  
        try{  
            f.createNewFile();  //當且僅當不存在具有此抽象路徑名指定名稱的檔案時,不可分地建立一個新的空檔案。  
            System.out.println("該分割槽大小"+f.getTotalSpace()/(1024*1024*1024)+"G"); //返回由此抽象路徑名錶示的檔案或目錄的名稱。  
            f.mkdirs();  //建立此抽象路徑名指定的目錄,包括所有必需但不存在的父目錄。  
//            f.delete(); //  刪除此抽象路徑名錶示的檔案或目錄  
           System.out.println("檔名  "+f.getName());  //  返回由此抽象路徑名錶示的檔案或目錄的名稱。  
           System.out.println("檔案父目錄字串 "+f.getParent());// 返回此抽象路徑名父目錄的路徑名字串;如果此路徑名沒有指定父目錄,則返回 null。 
        }catch (Exception e) {  
            e.printStackTrace();  
        }  
   }  
}  
二、位元組流:

1.位元組流有輸入和輸出流,我們首先看輸入流InputStream,我們首先解析一個例子(FileInputStream)。

package com.hxw.io;  
import java.io.File;  
import java.io.FileInputStream;  
import java.io.IOException;  
import java.io.InputStream;  
public class FileCount {  
   /** 
    * 我們寫一個檢測檔案長度的小程式,別看這個程式挺長的,你忽略try catch塊後發現也就那麼幾行而已。 
    */  
   publicstatic void main(String[] args) {  
      //TODO 自動生成的方法存根  
             int count=0;  //統計檔案位元組長度  
      InputStreamstreamReader = null;   //檔案輸入流  
      try{  
          streamReader=newFileInputStream(new File("D:/David/Java/java 高階進階/files/tiger.jpg"));  
          /*1.new File()裡面的檔案地址也可以寫成D:\\David\\Java\\java 高階進階\\files\\tiger.jpg,前一個\是用來對後一個 
           * 進行轉換的,FileInputStream是有緩衝區的,所以用完之後必須關閉,否則可能導致記憶體佔滿,資料丟失。 
          */  
          while(streamReader.read()!=-1) {  //讀取檔案位元組,並遞增指標到下一個位元組  
             count++;  
          }  
          System.out.println("---長度是: "+count+" 位元組");  
      }catch (final IOException e) {  
          //TODO 自動生成的 catch 塊  
          e.printStackTrace();  
      }finally{  
          try{  
             streamReader.close();  
          }catch (IOException e) {  
             //TODO 自動生成的 catch 塊  
             e.printStackTrace();  
          }  
      }  
   }  
   
}  

我們一步一步來,首先,上面的程式存在問題是,每讀取一個自己我都要去用到FileInputStream,我輸出的結果是“---長度是: 64982 位元組”,那麼進行了64982次操作!可能想象如果檔案十分龐大,這樣的操作肯定會出大問題,所以引出了緩衝區的概念。可以將streamReader.read()改成streamReader.read(byte[]b)此方法讀取的位元組數目等於位元組陣列的長度,讀取的資料被儲存在位元組陣列中,返回讀取的位元組數,InputStream還有其他方法mark,reset,markSupported方法,例如:

markSupported 判斷該輸入流能支援mark 和 reset 方法。
mark用於標記當前位置;在讀取一定數量的資料(小於readlimit的資料)後使用reset可以回到mark標記的位置。
FileInputStream不支援mark/reset操作;BufferedInputStream支援此操作;
mark(readlimit)的含義是在當前位置作一個標記,制定可以重新讀取的最大位元組數,也就是說你如果標記後讀取的位元組數大於readlimit,你就再也回不到回來的位置了。
通常InputStream的read()返回-1後,說明到達檔案尾,不能再讀取。除非使用了mark/reset。

2.FileOutputStream 循序漸進版, InputStream是所有位元組輸出流的父類,子類有ByteArrayOutputStream,FileOutputStream,ObjectOutputStreanm,這些我們在後面都會一一說到。先說FileOutputStream

我以一個檔案複製程式來說,順便演示一下快取區的使用。(Java I/O預設是不緩衝流的,所謂“緩衝”就是先把從流中得到的一塊位元組序列暫存在一個被稱為buffer的內部位元組陣列裡,然後你可以一下子取到這一整塊的位元組資料,沒有緩衝的流只能一個位元組一個位元組讀,效率孰高孰低一目瞭然。有兩個特殊的輸入流實現了緩衝功能,一個是我們常用的BufferedInputStream.)

package com.hxw.io;  
import java.io.*;  
   
public class FileCopy {  
   
  public static void main(String[] args) {  
     // TODO自動生成的方法存根  
     byte[] buffer=new byte[512];   //一次取出的位元組數大小,緩衝區大小  
     int numberRead=0;  
     FileInputStream input=null;  
     FileOutputStream out =null;  
     try {  
        input=new FileInputStream("D:/David/Java/java 高階進階/files/tiger.jpg");  
        out=new FileOutputStream("D:/David/Java/java 高階進階/files/tiger2.jpg"); //如果檔案不存在會自動建立  
         
        while ((numberRead=input.read(buffer))!=-1) {  //numberRead的目的在於防止最後一次讀取的位元組小於buffer長度,  
           out.write(buffer, 0, numberRead);       //否則會自動被填充0  
        }  
     } catch (final IOException e) {  
        // TODO自動生成的 catch 塊  
        e.printStackTrace();  
     }finally{  
        try {  
           input.close();  
           out.close();  
        } catch (IOException e) {  
           // TODO自動生成的 catch 塊  
           e.printStackTrace();  
        }  
         
     }  
  }  
   
}  
3.讀寫物件:ObjectInputStream 和ObjectOutputStream ,該流允許讀取或寫入使用者自定義的類,但是要實現這種功能,被讀取和寫入的類必須實現Serializable介面,其實該介面並沒有什麼方法,可能相當於一個標記而已,但是確實不合缺少的。例項程式碼如下:
package com.hxw.io;  
   
import java.io.*;  
   
public class ObjetStream {  
   
   /** 
    * @param args 
    */  
   public static void main(String[] args) {  
      // TODO自動生成的方法存根  
      ObjectOutputStream objectwriter=null;  
      ObjectInputStream objectreader=null;  
       
      try {  
         objectwriter=new ObjectOutputStream(new FileOutputStream("D:/David/Java/java 高階進階/files/student.txt"));  
         objectwriter.writeObject(new Student("gg", 22));  
         objectwriter.writeObject(new Student("tt", 18));  
         objectwriter.writeObject(new Student("rr", 17));  
         objectreader=new ObjectInputStream(new FileInputStream("D:/David/Java/java 高階進階/files/student.txt"));  
         for (int i = 0; i < 3; i++) {  
            System.out.println(objectreader.readObject());  
         }  
      } catch (IOException | ClassNotFoundException e) {  
         // TODO自動生成的 catch 塊  
         e.printStackTrace();  
      }finally{  
         try {  
            objectreader.close();  
            objectwriter.close();  
         } catch (IOException e) {  
            // TODO自動生成的 catch 塊  
            e.printStackTrace();  
         }  
          
      }  
       
   }  
   
}  
class Student implements Serializable{  
   private String name;  
   private int age;  
    
   public Student(String name, int age) {  
      super();  
      this.name = name;  
      this.age = age;  
   }  
   
   @Override  
   public String toString() {  
      return "Student [name=" + name + ", age=" + age + "]";  
   }  
    
    
}  

執行後系統輸出:
Student [name=gg, age=22]
Student [name=tt, age=18]
Student [name=rr, age=17]

4.有時沒有必要儲存整個物件的資訊,而只是要儲存一個物件的成員資料,成員資料的型別假設都是Java的基本資料型別,這樣的需求不必使用到與Object輸入、輸出相關的流物件,可以使用DataInputStream、DataOutputStream來寫入或讀出資料。

下面是一個例子:(DataInputStream的好處在於在從檔案讀出資料時,不用費心地自行判斷讀入字串時或讀入int型別時何時將停止,使用對應的readUTF()和readInt()方法就可以正確地讀入完整的型別資料。)

package com.hxw;  
public class Member {  
    private String name;  
    private int age;  
    public Member() {  
    }  
   public Member(String name, int age) {  
        this.name = name;  
        this.age = age;  
    }  
    public void setName(String name){  
        this.name = name;  
    }  
    public void setAge(int age) {  
        this.age = age;  
    }  
    public String getName() {  
        return name;  
    }  
    public int getAge() {  
        return age;  
    }  
}  

打算將Member類例項的成員資料寫入檔案中,並打算在讀入檔案資料後,將這些資料還原為Member物件。下面的程式碼簡單示範瞭如何實現這個需求。

package com.hxw;  
import java.io.*;  
public class DataStreamDemo  
{  
  public static void main(String[]args)  
  {  
     Member[] members = {newMember("Justin",90),  
                        newMember("momor",95),  
                        newMember("Bush",88)};  
        try  
     {  
        DataOutputStreamdataOutputStream = new DataOutputStream(new FileOutputStream(args[0]));  
   
        for(Member member:members)  
        {  
            //寫入UTF字串  
           dataOutputStream.writeUTF(member.getName());  
           //寫入int資料  
           dataOutputStream.writeInt(member.getAge());  
        }  
   
        //所有資料至目的地  
        dataOutputStream.flush();  
        //關閉流  
        dataOutputStream.close();  
   
            DataInputStreamdataInputStream = new DataInputStream(new FileInputStream(args[0]));  
   
        //讀出資料並還原為物件  
        for(inti=0;i<members.length;i++)  
        {  
           //讀出UTF字串  
           String name =dataInputStream.readUTF();  
           //讀出int資料  
           int score =dataInputStream.readInt();  
           members[i] = newMember(name,score);  
        }  
   
        //關閉流  
        dataInputStream.close();  
   
        //顯示還原後的資料  
        for(Member member : members)  
        {  
           System.out.printf("%s\t%d%n",member.getName(),member.getAge());  
        }  
     }  
     catch(IOException e)  
     {  
            e.printStackTrace();  
     }  
  }  
}  

5.PushbackInputStream類繼承了FilterInputStream類是iputStream類的修飾者。提供可以將資料插入到輸入流前端的能力(當然也可以做其他操作)。簡而言之PushbackInputStream類的作用就是能夠在讀取緩衝區的時候提前知道下一個位元組是什麼,其實質是讀取到下一個字元後回退的做法,這之間可以進行很多操作,這有點向你把讀取緩衝區的過程當成一個陣列的遍歷,遍歷到某個字元的時候可以進行的操作,當然,如果要插入,能夠插入的最大位元組數是與推回緩衝區的大小相關的,插入字元肯定不能大於緩衝區吧!下面是一個示例。

package com.hxw.io;  
   
import java.io.ByteArrayInputStream; //匯入ByteArrayInputStream的包  
import java.io.IOException;  
import java.io.PushbackInputStream;  
   
/** 
 * 回退流操作 
 * */  
public class PushBackInputStreamDemo {  
public static void main(String[] args) throws IOException {  
    String str = "hello,rollenholt";  
    PushbackInputStream push = null; // 宣告回退流物件  
    ByteArrayInputStream bat = null; // 宣告位元組陣列流物件  
    bat = new ByteArrayInputStream(str.getBytes());  
    push = new PushbackInputStream(bat); // 建立回退流物件,將拆解的位元組陣列流傳入  
    int temp = 0;  
    while ((temp = push.read()) != -1) { // push.read()逐位元組讀取存放在temp中,如果讀取完成返回-1  
       if (temp == ',') { // 判斷讀取的是否是逗號  
          push.unread(temp); //回到temp的位置  
          temp = push.read(); //接著讀取位元組  
          System.out.print("(回退" + (char) temp + ") "); // 輸出回退的字元  
       } else {  
          System.out.print((char) temp); // 否則輸出字元  
       }  
    }  
}  
}  
6.SequenceInputStream:有些情況下,當我們需要從多個輸入流中向程式讀入資料。此時,可以使用合併流,將多個輸入流合併成一個SequenceInputStream流物件。SequenceInputStream會將與之相連線的流集組合成一個輸入流並從第一個輸入流開始讀取,直到到達檔案末尾,接著從第二個輸入流讀取,依次類推,直到到達包含的最後一個輸入流的檔案末尾為止。 合併流的作用是將多個源合併合一個源。其可接收列舉類所封閉的多個位元組流物件。
package com.hxw.io;  
   
import java.io.*;  
import java.util.Enumeration;  
import java.util.Vector;  
   
public class SequenceInputStreamTest {  
  /** 
   * @param args 
   *            SequenceInputStream合併流,將與之相連線的流集組合成一個輸入流並從第一個輸入流開始讀取, 
   *            直到到達檔案末尾,接著從第二個輸入流讀取,依次類推,直到到達包含的最後一個輸入流的檔案末尾為止。 
   *            合併流的作用是將多個源合併合一個源。可接收列舉類所封閉的多個位元組流物件。 
   */  
  public static void main(String[] args) {  
     doSequence();  
  }  
   
  private static void doSequence() {  
     // 建立一個合併流的物件  
     SequenceInputStream sis = null;  
     // 建立輸出流。  
     BufferedOutputStream bos = null;  
     try {  
        // 構建流集合。  
        Vector<InputStream> vector = new Vector<InputStream>();  
        vector.addElement(new FileInputStream("D:\text1.txt"));  
        vector.addElement(new FileInputStream("D:\text2.txt"));  
        vector.addElement(new FileInputStream("D:\text3.txt"));  
        Enumeration<InputStream> e = vector.elements();  
   
        sis = new SequenceInputStream(e);  
   
        bos = new BufferedOutputStream(new FileOutputStream("D:\text4.txt"));  
        // 讀寫資料  
        byte[] buf = new byte[1024];  
        int len = 0;  
        while ((len = sis.read(buf)) != -1) {  
           bos.write(buf, 0, len);  
           bos.flush();  
        }  
     } catch (FileNotFoundException e1) {  
        e1.printStackTrace();  
     } catch (IOException e1) {  
        e1.printStackTrace();  
     } finally {  
        try {  
           if (sis != null)  
              sis.close();  
        } catch (IOException e) {  
           e.printStackTrace();  
        }  
        try {  
           if (bos != null)  
              bos.close();  
        } catch (IOException e) {  
           e.printStackTrace();  
        }  
     }  
  }  
}  

7.PrintStream 說這個名字可能初學者不熟悉,如果說System.out.print()你肯定熟悉,System.out這個物件就是PrintStream,這個我們不做過多示例
三、字元流(顧名思義,就是操作字元檔案的流)
1.java 使用Unicode儲存字串,在寫入字元流時我們都可以指定寫入的字串的編碼。前面介紹了不用拋異常的處理位元組型資料的流ByteArrayOutputStream,與之對應的操作字元類的類就是CharArrayReader,CharArrayWriter類,這裡也會用到緩衝區,不過是字元緩衝區,一般講字串放入到操作字元的io流一般方法是
CharArrayReaderreader=mew CharArrayReader(str.toCharArray()); 一旦會去到CharArrayReader例項就可以使用CharArrayReader訪問字串的各個元素以執行進一步讀取操作。不做例子
2.我們用FileReader ,PrintWriter來做示範

package com.hxw.io;  
   
import java.io.FileNotFoundException;  
import java.io.FileReader;  
import java.io.IOException;  
import java.io.PrintWriter;  
import java.nio.CharBuffer;  
   
public class Print {  
   
/** 
 * @param args 
 */  
public static void main(String[] args) {  
    // TODO自動生成的方法存根  
    char[] buffer=new char[512];   //一次取出的位元組數大小,緩衝區大小  
    int numberRead=0;  
    FileReader reader=null;        //讀取字元檔案的流  
    PrintWriter writer=null;    //寫字元到控制檯的流  
     
    try {  
       reader=new FileReader("D:/David/Java/java 高階進階/files/copy1.txt");  
       writer=new PrintWriter(System.out);  //PrintWriter可以輸出字元到檔案,也可以輸出到控制檯  
       while ((numberRead=reader.read(buffer))!=-1) {  
          writer.write(buffer, 0, numberRead);  
       }  
    } catch (IOException e) {  
       // TODO自動生成的 catch 塊  
       e.printStackTrace();  
    }finally{  
       try {  
          reader.close();  
       } catch (IOException e) {  
          // TODO自動生成的 catch 塊  
          e.printStackTrace();  
       }  
       writer.close();       //這個不用拋異常  
    }  
        
}  
   
}  
3.相對我們前面的例子是直接用FileReader開啟的檔案,我們這次使用連結流,一般比較常用的都用連結流,所謂連結流就是就多次對流的封裝,這樣能更好的操作個管理資料,(比如我們利用DataInputStream(BufferedInputStream(FileInputStream))將位元組流層層包裝後,我們可以讀取readByte(),readChar()這樣更加具體的操作,注意,該流屬於位元組流對字元進行操作,)字元流用CharArrayReader就可以了。下面的示例我們將用到j2se 5中的一個可變引數進行一個小度擴充套件。使用BufferedWriter 和BufferedReader用檔案級聯的方式進行寫入,即將多個檔案寫入到同一檔案中(自帶緩衝區的輸出輸出流BufferedReader和BufferedWriter,該流最常用的屬readLine()方法了,讀取一行資料,並返回String)。
package com.hxw.io;  
   
import java.io.BufferedReader;  
import java.io.BufferedWriter;  
import java.io.FileReader;  
import java.io.FileWriter;  
import java.io.IOException;  
import java.util.Iterator;  
   
public class FileConcatenate {  
   
  /** 
   * 包裝類進行檔案級聯操作 
   */  
  public static void main(String[] args) {  
     // TODO自動生成的方法存根  
     try {  
        concennateFile(args);  
     } catch (IOException e) {  
        // TODO自動生成的 catch 塊  
        e.printStackTrace();  
     }  
  }  
  public static voidconcennateFile(String...fileName) throws IOException{  
     String str;  
     //構建對該檔案您的輸入流  
     BufferedWriter writer=new BufferedWriter(new FileWriter("D:/David/Java/java 高階進階/files/copy2.txt"));  
     for(String name: fileName){  
        BufferedReader reader=new BufferedReader(new FileReader(name));  
         
        while ((str=reader.readLine())!=null) {  
           writer.write(str);  
           writer.newLine();  
        }  
     }  
  }  
   
}  
4.Console類,該類提供了用於讀取密碼的方法,可以禁止控制檯回顯並返回char陣列,對兩個特性對保證安全有作用,平時用的不多,瞭解就行。
5.StreamTokenizer 類,這個類非常有用,它可以把輸入流解析為標記(token), StreamTokenizer 並非派生自InputStream或者OutputStream,而是歸類於io庫中,因為StreamTokenizer只處理InputStream物件。

首先給出我的文字檔案內容:

'水上漂'
青青草
"i love wyhss"
{3211}
23223 3523
i love wyh ,。
. ,

package com.hxw.io;  
import java.io.BufferedReader;  
import java.io.FileReader;  
import java.io.IOException;  
import java.io.StreamTokenizer;  
   
/** 
 * 使用StreamTokenizer來統計檔案中的字元數 
 * StreamTokenizer 類獲取輸入流並將其分析為“標記”,允許一次讀取一個標記。 
 * 分析過程由一個表和許多可以設定為各種狀態的標誌控制。 
 * 該流的標記生成器可以識別識別符號、數字、引用的字串和各種註釋樣式。 
 * 
 *  預設情況下,StreamTokenizer認為下列內容是Token: 字母、數字、除C和C++註釋符號以外的其他符號。 
 *  如符號"/"不是Token,註釋後的內容也不是,而"\"是Token。單引號和雙引號以及其中的內容,只能算是一個Token。 
 *  統計文章字元數的程式,不是簡單的統計Token數就萬事大吉,因為字元數不等於Token。按照Token的規定, 
 *  引號中的內容就算是10頁也算一個Token。如果希望引號和引號中的內容都算作Token,應該呼叫下面的程式碼: 
 *    st.ordinaryChar('\''); 
 * st.ordinaryChar('\"'); 
 */  
public class StreamTokenizerExample {  
   
    /** 
     * 統計字元數 
     * @param fileName 檔名 
     * @return    字元數 
     */  
public static void main(String[] args) {  
        String fileName = "D:/David/Java/java 高階進階/files/copy1.txt";  
        StreamTokenizerExample.statis(fileName);  
    }  
    public static long statis(String fileName) {  
   
        FileReader fileReader = null;  
        try {  
            fileReader = new FileReader(fileName);  
            //建立分析給定字元流的標記生成器  
            StreamTokenizer st = new StreamTokenizer(new BufferedReader(  
                    fileReader));  
   
            //ordinaryChar方法指定字元引數在此標記生成器中是“普通”字元。  
            //下面指定單引號、雙引號和註釋符號是普通字元  
            st.ordinaryChar('\'');  
            st.ordinaryChar('\"');  
            st.ordinaryChar('/');  
   
            String s;  
            int numberSum = 0;  
            int wordSum = 0;  
            int symbolSum = 0;  
            int total = 0;  
            //nextToken方法讀取下一個Token.  
            //TT_EOF指示已讀到流末尾的常量。  
            while (st.nextToken() !=StreamTokenizer.TT_EOF) {  
                //在呼叫 nextToken 方法之後,ttype欄位將包含剛讀取的標記的型別  
                switch (st.ttype) {  
                //TT_EOL指示已讀到行末尾的常量。  
                case StreamTokenizer.TT_EOL:  
                    break;  
                //TT_NUMBER指示已讀到一個數字標記的常量  
                case StreamTokenizer.TT_NUMBER:  
                    //如果當前標記是一個數字,nval欄位將包含該數字的值  
                    s = String.valueOf((st.nval));  
                    System.out.println("數字有:"+s);  
                    numberSum ++;  
                    break;  
                //TT_WORD指示已讀到一個文字標記的常量  
                case StreamTokenizer.TT_WORD:  
                    //如果當前標記是一個文字標記,sval欄位包含一個給出該文字標記的字元的字串  
                    s = st.sval;  
                    System.out.println("單詞有: "+s);  
                    wordSum ++;  
                    break;  
                default:  
                    //如果以上3中型別都不是,則為英文的標點符號  
                    s = String.valueOf((char) st.ttype);  
                    System.out.println("標點有: "+s);  
                    symbolSum ++;  
                }  
            }  
            System.out.println("數字有 " + numberSum+"個");  
            System.out.println("單詞有 " + wordSum+"個");  
            System.out.println("標點符號有: " + symbolSum+"個");  
            total = symbolSum + numberSum +wordSum;  
            System.out.println("Total = " + total);  
            return total;  
        } catch (Exception e) {  
            e.printStackTrace();  
            return -1;  
        } finally {  
            if (fileReader != null) {  
                try {  
                    fileReader.close();  
                } catch (IOException e1) {  
                }  
            }  
        }  
    }  
}  

執行結果為
標點有: '
單詞有: 水上漂
標點有: '
單詞有: 青青草
標點有: "
單詞有: i
單詞有: love
單詞有: wyh
單詞有: ss
標點有: "
標點有: {
數字有:3211.0
標點有: }
數字有:23223.0
數字有:35.23
單詞有: i
單詞有: love
單詞有: wyh
單詞有: ,。
數字有:0.0
標點有: ,
數字有 4個
單詞有 10個
標點符號有: 7個
Total= 21

我們從其中可以看到很多東西:

1.一個單獨的小數點“.”是被當做一個數字來對待的,數字的值為0.0;
2.一串漢字只要中間沒有符號(空格回車 分號等等)都是被當做一個單詞的。中文的標點跟中文的漢字一樣處理
3.如果不對引號化成普通字元,一個引號內的內容不論多少都被當做是一個標記。
4.該類能夠識別英文標點。

相關文章