關于java中的io流知識總結詳解
一、Java io的開始:文件
1. 我們主要講的是流,流的本質也是對文件的處理,我們循序漸進一步一步從文件將到流去。
2. java 處理文件的類 File,java提供了十分詳細的文件處理方法,舉了其中幾個例子,其余的可以去
packagecom.hxw.io; importjava.io.*; publicclassFileExample{publicstaticvoidmain(String[] args) { createFile(); } /** * 文件處理示例 */publicstaticvoidcreateFile() { File f= newFile( “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)。
packagecom.hxw.io; importjava.io.File; importjava.io.FileInputStream;importjava.io.IOException; importjava.io.InputStream; publicclassFileCount{/** * 我們寫一個檢測文件長度的小程序,別看這個程序挺長的,你忽略try catch塊后發現也就那么幾行而已。 */publicstatic voidmain(String[] args) { //TODO 自動生成的方法存根 intcount= 0; //統計文件字節長度 InputStreamstreamReader = null; //文件輸入流 try{ streamReader=newFileInputStream( newFile( “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( finalIOException 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.)
packagecom.hxw.io; importjava.io.*; /** * Java學習交流QQ群:589809992 我們一起學Java! */publicclassFileCopy{publicstaticvoidmain(String[] args) { // TODO自動生成的方法存根 byte[] buffer= newbyte[ 512]; //一次取出的字節數大小,緩沖區大小 intnumberRead= 0; FileInputStream input= null; FileOutputStream out = null; try{ input= newFileInputStream(“D:/David/Java/java 高級進階/files/tiger.jpg”); out= newFileOutputStream(“D:/David/Java/java 高級進階/files/tiger2.jpg”); //如果文件不存在會自動創建while((numberRead=input.read(buffer))!=- 1) { //numberRead的目的在于防止最后一次讀取的字節小于buffer長度, out.write(buffer, 0, numberRead); //否則會自動被填充0 } } catch(finalIOException e) { // TODO自動生成的 catch 塊 e.printStackTrace(); } finally{ try{ input.close(); out.close(); } catch(IOException e) { // TODO自動生成的 catch 塊e.printStackTrace(); } } } }
3.讀寫對象:ObjectInputStream 和ObjectOutputStream ,該流允許讀取或寫入用戶自定義的類,但是要實現這種功能,被讀取和寫入的類必須實現Serializable接口,其實該接口并沒有什么方法,可能相當于一個標記而已,但是確實不合缺少的。實例代碼如下:
packagecom.hxw.io; importjava.io.*; publicclassObjetStream{/** *@paramargs */publicstaticvoidmain(String[] args) { // TODO自動生成的方法存根 ObjectOutputStream objectwriter= null; ObjectInputStream objectreader= null; try{ objectwriter=newObjectOutputStream( newFileOutputStream( “D:/David/Java/java 高級進階/files/student.txt”)); objectwriter.writeObject( newStudent( “gg”, 22)); objectwriter.writeObject( newStudent( “tt”, 18)); objectwriter.writeObject( newStudent( “rr”,17)); objectreader= newObjectInputStream( newFileInputStream( “D:/David/Java/java 高級進階/files/student.txt”)); for( inti = 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{ privateString name; privateintage; publicStudent(String name, intage) { super(); this.name = name;this.age = age; } @OverridepublicString 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()方法就可以正確地讀入完整的類型數據。)
packagecom.hxw; /** * Java學習交流QQ群:589809992 我們一起學Java! */publicclassMember{privateString name; privateintage; publicMember() { }publicMember(String name, intage) { this.name = name; this.age = age; }publicvoidsetName(String name){ this.name = name; } publicvoidsetAge( intage) { this.age = age; } publicString getName() { returnname; } publicintgetAge() { returnage; } }
打算將Member類實例的成員數據寫入文件中,并打算在讀入文件數據后,將這些數據還原為Member對象。下面的代碼簡單示范了如何實現這個需求。
packagecom.hxw; importjava.io.*; /** * Java學習交流QQ群:589809992 我們一起學Java! */publicclassDataStreamDemo{publicstaticvoidmain(String[]args) { Member[] members = {newMember( “Justin”, 90), newMember( “momor”, 95), newMember( “Bush”,88)}; try{ DataOutputStreamdataOutputStream = newDataOutputStream(newFileOutputStream(args[ 0])); for(Member member:members) { //寫入UTF字符串dataOutputStream.writeUTF(member.getName()); //寫入int數據dataOutputStream.writeInt(member.getAge()); } //所有數據至目的地dataOutputStream.flush(); //關閉流 dataOutputStream.close(); DataInputStreamdataInputStream = newDataInputStream( newFileInputStream(args[ 0]));//讀出數據并還原為對象 for(inti= 0;i《members.length;i++) { //讀出UTF字符串 String name =dataInputStream.readUTF(); //讀出int數據 intscore =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類的作用就是能夠在讀取緩沖區的時候提前知道下一個字節是什么,其實質是讀取到下一個字符后回退的做法,這之間可以進行很多操作,這有點向你把讀取緩沖區的過程當成一個數組的遍歷,遍歷到某個字符的時候可以進行的操作,當然,如果要插入,能夠插入的最大字節數是與推回緩沖區的大小相關的,插入字符肯定不能大于緩沖區吧!下面是一個示例。
packagecom.hxw.io; importjava.io.ByteArrayInputStream; //導入ByteArrayInputStream的包 importjava.io.IOException; importjava.io.PushbackInputStream; /** * 回退流操作 * */publicclassPushBackInputStreamDemo{publicstaticvoidmain(String[] args)throwsIOException { String str = “hello,rollenholt”; PushbackInputStream push = null; // 聲明回退流對象 ByteArrayInputStream bat = null; // 聲明字節數組流對象 bat =newByteArrayInputStream(str.getBytes()); push = newPushbackInputStream(bat); // 創建回退流對象,將拆解的字節數組流傳入 inttemp = 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會將與之相連接的流集組合成一個輸入流并從第一個輸入流開始讀取,直到到達文件末尾,接著從第二個輸入流讀取,依次類推,直到到達包含的最后一個輸入流的文件末尾為止。 合并流的作用是將多個源合并合一個源。其可接收枚舉類所封閉的多個字節流對象。
packagecom.hxw.io; importjava.io.*; importjava.util.Enumeration; importjava.util.Vector;publicclassSequenceInputStreamTest{/** *@paramargs * SequenceInputStream合并流,將與之相連接的流集組合成一個輸入流并從第一個輸入流開始讀取, * 直到到達文件末尾,接著從第二個輸入流讀取,依次類推,直到到達包含的最后一個輸入流的文件末尾為止。 * 合并流的作用是將多個源合并合一個源。可接收枚舉類所封閉的多個字節流對象。 */publicstaticvoidmain(String[] args) { doSequence(); } privatestaticvoiddoSequence() { // 創建一個合并流的對象 SequenceInputStream sis = null; // 創建輸出流。BufferedOutputStream bos = null; try{ // 構建流集合。 Vector《InputStream》 vector =newVector《InputStream》(); vector.addElement( newFileInputStream( “D:\text1.txt”)); vector.addElement( newFileInputStream( “D:\text2.txt”)); vector.addElement(newFileInputStream( “D:\text3.txt”)); Enumeration《InputStream》 e = vector.elements(); sis = newSequenceInputStream(e); bos = newBufferedOutputStream( newFileOutputStream(“D:\text4.txt”)); // 讀寫數據 byte[] buf = newbyte[ 1024]; intlen = 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來做示范
packagecom.hxw.io; importjava.io.FileNotFoundException; importjava.io.FileReader;importjava.io.IOException; importjava.io.PrintWriter; importjava.nio.CharBuffer;publicclassPrint{/** *@paramargs */publicstaticvoidmain(String[] args) { // TODO自動生成的方法存根 char[] buffer= newchar[ 512]; //一次取出的字節數大小,緩沖區大小intnumberRead= 0; FileReader reader= null; //讀取字符文件的流 PrintWriter writer= null; //寫字符到控制臺的流 try{ reader= newFileReader( “D:/David/Java/java 高級進階/files/copy1.txt”); writer= newPrintWriter(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)。
packagecom.hxw.io; importjava.io.BufferedReader; importjava.io.BufferedWriter;importjava.io.FileReader; importjava.io.FileWriter; importjava.io.IOException;importjava.util.Iterator; publicclassFileConcatenate{/** * 包裝類進行文件級聯操作 */publicstaticvoidmain(String[] args) { // TODO自動生成的方法存根 try{ concennateFile(args); } catch(IOException e) { // TODO自動生成的 catch 塊e.printStackTrace(); } } publicstaticvoidconcennateFile(String.。.fileName)throwsIOException{ String str; //構建對該文件您的輸入流 BufferedWriter writer=newBufferedWriter( newFileWriter( “D:/David/Java/java 高級進階/files/copy2.txt”));for(String name: fileName){ BufferedReader reader= newBufferedReader(newFileReader(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 ,。
。 ,
下面是代碼:
packagecom.hxw.io; importjava.io.BufferedReader; importjava.io.FileReader;importjava.io.IOException; importjava.io.StreamTokenizer; /** * 使用StreamTokenizer來統計文件中的字符數 * StreamTokenizer 類獲取輸入流并將其分析為“標記”,允許一次讀取一個標記。 * 分析過程由一個表和許多可以設置為各種狀態的標志控制。 * 該流的標記生成器可以識別標識符、數字、引用的字符串和各種注釋樣式。 * * 默認情況下,StreamTokenizer認為下列內容是Token: 字母、數字、除C和C++注釋符號以外的其他符號。 * 如符號“/”不是Token,注釋后的內容也不是,而“\”是Token。單引號和雙引號以及其中的內容,只能算是一個Token。 * 統計文章字符數的程序,不是簡單的統計Token數就萬事大吉,因為字符數不等于Token。按照Token的規定, * 引號中的內容就算是10頁也算一個Token。如果希望引號和引號中的內容都算作Token,應該調用下面的代碼: * st.ordinaryChar(‘\’‘); * st.ordinaryChar(’\“‘); */publicclassStreamTokenizerExample{/** * 統計字符數 *@paramfileName 文件名 *@return字符數 */publicstaticvoidmain(String[] args) { String fileName = ”D:/David/Java/java 高級進階/files/copy1.txt“; StreamTokenizerExample.statis(fileName); } publicstaticlongstatis(String fileName) { FileReader fileReader = null; try{ fileReader = newFileReader(fileName); //創建分析給定字符流的標記生成器 StreamTokenizer st = newStreamTokenizer( newBufferedReader( fileReader)); //ordinaryChar方法指定字符參數在此標記生成器中是“普通”字符。 //下面指定單引號、雙引號和注釋符號是普通字符 st.ordinaryChar( ’\‘’); st.ordinaryChar( ‘\”’); st.ordinaryChar( ‘/’); String s; intnumberSum = 0; intwordSum = 0; intsymbolSum = 0;inttotal = 0; //nextToken方法讀取下一個Token. //TT_EOF指示已讀到流末尾的常量。while(st.nextToken() !=StreamTokenizer.TT_EOF) { //在調用 nextToken 方法之后,ttype字段將包含剛讀取的標記的類型 switch(st.ttype) { //TT_EOL指示已讀到行末尾的常量。caseStreamTokenizer.TT_EOL: break; //TT_NUMBER指示已讀到一個數字標記的常量caseStreamTokenizer.TT_NUMBER: //如果當前標記是一個數字,nval字段將包含該數字的值 s = String.valueOf((st.nval)); System.out.println( “數字有:”+s); numberSum ++;break; //TT_WORD指示已讀到一個文字標記的常量 caseStreamTokenizer.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); returntotal; } 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.該類能夠識別英文標點
5.java io里面還有其他接口類似Serializable接口的子接口Externalizable接口,比Serializable復雜一些,這里不再介紹。還有關于java對象版本化的東西感興趣的可以百度。java nio的東西這里沒有涉及,后續會結合到線程再發一篇文章專門解析這個東西。
非常好我支持^.^
(0) 0%
不好我反對
(0) 0%