IO流(一)

io思維導向圖

圖片來源于互聯網

一、IO流概述

IO流用于處理設備之間的數據傳輸問題。Java對數據的操作,通過流的形式。操作存儲設備中的數據,流技術實現,程序和設備之間建立連接通道,數據流。I -> Input 輸入 O -> Output輸出

  • 字符流: 開始JDK1.1 字符流專門用于操作文本文件,記事本可以打開的,人可以看懂的文件。 每次操作1個字符 16個二進制,去查詢本機默認的編碼表。方便Java程序操作純文本。( .txt .java .html .xml .css )
  • 字節流:開始JDK1.0 字節流操作文件是任意文件。每次操作1個字節8個二進制。不查詢編碼表 。

二、IO流的分類

  1. 字符輸出流: 寫文本文件的,抽象基類java.io.Writer

    寫的方法 write,很多重載形式,寫字符數組,單個字符,字符串,字符數組一部分,字符串的一部分。 flush刷新流的緩沖 close關閉流對象

  1. 字符輸入流:讀取文本文件的,抽象基類java.io.Reader

    讀的方法 read,很多重載形式,讀取單個字符,字符數組,字符數組一部分。close關閉流對象

  2. 字節輸出流:寫任意的文件,抽象基類java.io.OutputStream

    寫的方法 write,很多重載形式,寫單個字節,字節數組,字節數組一部分。 close關閉流對象

  3. 字節輸入流:讀取任意文件,抽象基類java.io.InputStream

    讀的方法 read,很多重載形式,讀取單個字節,字節數組,字節數組一部分。close關閉流對象

IO流中的所有類的命名法則:后綴都是父類名,前面都是可以操作的文件名,流向名

FileInputStream FileReader ObjectInputStream InputStreamReader

三、書寫注意

  • IO使用,導入java.io包
  • IO流類中的方法,大多數都會拋出異常,調用者try ...throws
  • IO流對象,沒有實際的功能操作文件,依賴操作系統來實現功能,流對象用完后,一定要關閉資源

四、字符流輸出

1. 寫文件

數據輸出是java.io.Writer類(抽象的),具體操作找子類 FileWriter 來寫入字符文件的便捷類。

  1. 創建子類對象,需要使用子類的構造器,傳遞String文件名
  2. 子類對象調用父類方法write()寫文件
  3. 關閉資源(一旦流對象關閉,不能再次使用)

注意:創建對象,調用方法寫,write方法是把數據寫在了內存中,flush刷新該流的緩沖,把數據從內存刷到目的文件。字符流寫文件必須刷新,如果不刷新數據可能會留在內存中,但是只要進行了刷新數據肯定走向目的文件

2. 追加的形式寫文件

當我們不能覆蓋寫入的時候,就需要進行后續的追加。利用FileWriter類的構造方法,傳遞true可以實現文件的追加寫入

三種寫入的方法:

  1. 寫單個的字符 write(int i)
  2. 寫字符數組 write(char[] c)
  3. 寫字符數組一部分 write(char[] c,int start,int length)

demo:

package io;

import java.io.*;

public class FileWriteDemo {
    public static void main(String[] args) throws IOException {
        
        //構造方法拋異常
        FileWriter write = new FileWriter("test.txt");

        //write方法,三種重載形式(string s) (int i ) (char [])
        write.write("this is a test");
        
        write.flush();
        
        write.close();
        
        
        //打開進行追加,文件存在時,不會覆蓋原來的內容
        FileWriter fw = new FileWriter("test.txt",true);
        
        fw.write("ok, append something");
        fw.flush();
        fw.close();
    }
}

五、字符流讀取文件

讀取文件是java.io.Reader類,也是抽象類,具體使用要找子類,字符流讀取使用子類FileReader。

  1. 創建子類對象,需要使用子類構造器,傳遞String文件名
  2. 在調用父類方法read讀取文件
  3. 關閉資源

一般兩種讀取的方式:

  • 讀取單個字符 int read()每次讀取1個字符,返回碼值,read方法每執行一次,自動的向后讀取一個字符,讀取到文件末尾,返回-1。
  • 數組讀取int read(char[]),一次性讀取數組長度個字符,返回讀取的長度,到達文件末尾時返回-1

demo:

package io;
/*
 * 字符輸入流
 *
 * 1. int reader() 讀取單個字符,返回字符對應的int值,每次讀取,指針就會向后移動,指向下一個字符,到文件末尾時,返回-1
 * 2. int reader(char []) 讀取一個數組的長度,具體長度由數組的長度決定,返回讀取的長度,到文件末尾時,返回-1
 *  
 */

import java.io.*;

public class FileReaderDemo {
    public static void main(String[] args) throws IOException {
        
        FileReader fr = new FileReader("test.txt");
        
        System.out.println((char)fr.read());
        System.out.println(fr.read());
        System.out.println(fr.read());
        System.out.println(fr.read());
        
        fr.close();

        method();
    }
    
    //讀取數組
    public static void method ()throws IOException {
        FileReader fe = new FileReader("test.txt");
        
        char[] ch = new char[2];
        
        int i = 0;
        while((i = fe.read(ch)) != -1){
            String s = new String(ch,0,i);
            System.out.print(s);
        }
        
        fe.close();
    }
}

六、緩沖區

緩沖區是為了提高流的讀寫效率而出現,比如BufferedWriter,也是Writer的子類,Writer類的方法都可以用的。但是使用緩沖區對象,必須有一個流對象配合使用。

1. 字符輸出流

BufferedWriter類的構造方法BufferedWriter(Writer w)參數是一個字符輸出流,將輸出流FileWriter類的對象傳遞給BufferedWriter的構造方法 BfferedWriter(new FileWriter),BufferedWriter這個緩沖區,將提高FileWriter的寫如效率。

緩沖區特有方法 void newLine()文本中,寫換行。可以寫\r\n還是用newLine(),如果需要文本的換行,需要使用newLine()實現,這個方法具有跨平臺性。

Windows,換行\r\n Linux換行\n . 如果安裝的JDK是Windows版本的,newLine()方法中寫的就是\r\n, 安裝的JDK是Linux版本,newLine()方法寫的就是\n

Demo:

package io;
/*
 * 緩沖區demo
 */
import java.io.*;
public class BufferedWriterDemo {
    public static void main(String[] args) throws IOException{
        
        //創建字符輸出流
        FileWriter fw = new FileWriter("E:\\test.txt");
        
        BufferedWriter bw = new BufferedWriter(fw);
        
        bw.write("test!\r132");
        
        bw.newLine();//跨平臺性 win下是\r\n  linux下是\n
        
        bw.write("new test!");
        
        bw.flush();
        
        bw.close();

    }
}

2. 字符輸入流

BufferedReader類也是Reader的子類,Reader的方法當然都可以用,需要一個輸入流對象,配合使用。

BufferedReader構造方法BufferedReader(Reader r)參數是一個字符輸入流,方法String readLine()讀取文本一行,返回字符串。

demo:

package io;
import java.io.*;
/*
 *  BufferedReader
 *  String readerLine() 一次讀取一行
 */
public class BufferedReaderDemo {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new FileReader("E:\\test.txt"));
        String s = null;
        while( (s = br.readLine()) != null){
            System.out.println(s.length() +"..." + s);
        }
        br.close();
    }
}

練習demo(復制一個文件,使用緩沖區實現):

package io;
import java.io.*;
/*
 * 一個練習,buffered讀行,寫行,換行
 * 處理異常
 * 目標文件:e:\temp.html
 */
public class CopyTextByBuffered {
    public static void main(String[] args) {
        
        BufferedReader bfr = null;
        BufferedWriter bfw = null;
        
        try{
            //初始化需要用的文件和緩沖區的讀取和寫入的對象
            bfr = new BufferedReader(new FileReader("E:\\temp.html"));
            bfw = new BufferedWriter(new FileWriter("E:\\newTemp.html"));
            
            String s = null;
            while((s = bfr.readLine()) != null){
                bfw.write(s);
                bfw.newLine();
                bfw.flush();
            }
            
        }catch(IOException e){          
            //
            throw new RuntimeException("文件copy失敗");     
        }finally{ //關閉緩沖區
            try{
                bfw.close();
            }catch(IOException e){
                throw new RuntimeException("關閉文件寫入緩沖區異常");
            }
            
            try{
                bfr.close();
            }catch(IOException e){
                throw new RuntimeException("關閉文件讀取緩沖區異常");
            }
            
        }
    }
}

七、裝飾設計模式

裝飾設計模式出現的意義就是增強原有對象的功能。舉個例子,BufferedReader,BufferedWriter 裝飾類的出現,就是增強了原有的流對象Reader和Writer的功能,裝飾設計在java的IO中使用相當廣泛。

說道這里,那繼承覆蓋和裝飾的區別在哪里,舉個例子進行說明。

現在有一個父類Reader,下面有四個子類分別是:

  • 文本類 TextReader
  • 視頻類 VideoReader
  • 音樂類 SoundReader
  • 游戲類 GameReader

如果此時因為這四個類的功能太少要進行擴展,采用繼承的話就需要再寫四個子類來進行繼承和重寫和加強,因此就會有八個類出現。這對開發和新手接觸都是相當麻煩的,要額外的增加相當多的成本。那此時如果采用裝飾類的話,只需要一個BufferedReader裝飾類將繼承自Reader的所有類都裝飾起來,簡單簡潔,適合新手接觸和開發。

八、實現readerLine()練習

依照BufferedReader類的readLine方法原理,自己進行實現,要求功能上和原方法相同。

Demo:

package io;
/*
 * 自己實現一個類似于BufferReader 里面的 readerline方法
 * 只是練習,直接把異常拋出去,沒有做相應的處理
 */
import java.io.*;

public class ReaderLineTest {
    public static void main(String[] args) throws IOException{
        
      //初始化一個FileReader對象
        FileReader fr = new FileReader("E:\\test.txt");
        //講文件內的內容全部打印出來,使用的文件是BufferedWriter中創建的文件
        String s = null;
        while((s = MeReaderLine(fr)) != null){
            System.out.println(s);
        }

        fr.close();
    }
    
    /*
     * 傳入一個流對象,返回一個String
     */
    
    public static String MeReaderLine(FileReader fr) throws IOException{
        
        
        
        StringBuffer bf = new StringBuffer();
        
        if(fr == null)
            return  null;
        int temp = 0;
        
        try{
            while((temp = fr.read()) != -1){
                if(temp == 13)
                    continue;
                if(temp == 10)
                    return bf.toString();

                bf.append((char)temp);
            }
            
            if(bf.length() == 0)
                return null;
            else
                return bf.toString();
        }catch(IOException e){
            throw new RuntimeException("讀取失敗");
            
        }
    }
}

九、字節流輸入輸出

1. 字節輸入流--InputStream

InputStream是字節輸入流,可讀取任意文件。read可 讀取字節數組,可讀單個字節。此類是抽象類,不能直接用,所以找子類 FileInputStream。

  • FileInputStream()構造方法,傳遞String的文件名
  • read()方法讀取文件結尾返回-1
  • read(字節數組)利用數組作為緩沖,提高流的讀取效率

根據字符流的原理,字節流中的read()讀取到的字節存儲到字節數組中,返回讀取一次的字節數中有效字節個數,文件末尾返回-1

  • int available(),返回字節輸入流中封裝的文件的字節數

2. 字節輸出流--OutpuStream

OutpuStream字節輸出流,可以寫入任意文件。write(byte) \write(byte[])寫字節數組或者單個字節。OutpuStream是抽象類,不能直接用,實現找子類 FileOutputStream。FileOutputStream()構造方法,傳遞String的文件名。

字節流寫數據的時候,不需要刷新,但是要關閉

3. 字節流緩沖區(不是很重要,會用就好了)

字節流緩沖區和字符流緩沖區用法很像,注意字節流是沒有行概念的。

  • BufferedOutputStream()構造方法,傳遞字節輸出流,寫入的方法寫單個字節,字節數組,沒有行!!
  • BufferdInputStream() 構造方法,傳遞字節輸入流,讀取的方法,單個字節,字節數組

下面是一個字節流緩沖區復制文件的demo,并沒有使用buffered...:

package io;

/*
 * 使用字節流,實現任意文件的copy
 */
import java.io.*;

public class CopyFileByFileStream {
    public static void main(String[] args){
        
        FileInputStream fis = null;
        FileOutputStream fos = null;
        long  s = System.currentTimeMillis();
        try{
          //初始化字節流輸入輸出對象
            fis = new FileInputStream("E:\\迅雷下載\\VSCodeSetup-stable.exe");
            fos = new FileOutputStream("E:\\vscode.exe");
            //byte[]用來緩存字節流數據,1024剛好1kb,效率和內存占用上都還行
            byte[] by = new byte[1024];
            int len = 0;
            while((len =fis.read(by) )!= -1){
                fos.write(by,0,len);
            }
            //下面為異常處理,在finally中關閉流對象即可
        }catch(IOException e){
            throw new RuntimeException("文件復制失敗");
        }finally{
            
            try{
                if(fis != null)
                    fis.close();
            }catch(IOException e){
                throw new RuntimeException("源文件關閉失敗");              
            }
            try{
                if(fos != null)
                    fos.close();
            }catch(IOException e){
                throw new RuntimeException("新文件關閉失敗");
            }
        }
        long  e = System.currentTimeMillis();
        System.out.println(e-s);
        
    }
}

十、鍵盤輸入

個人感覺java的鍵盤輸入其實和C/C++還是有很大區別的,通過一個demo進行練習和實現,具體的實現思路和方法都在下面的這個demo中,核心利用的是System.in返回的BufferedInputStream進行讀取輸入的字符。

其實字符流的read()方法是阻塞式的,之前一直因為直接使用文件進行讀取,所以程序并不會停下來。現在直接調用read方法,因為沒有任何的輸入源,所以程序就會停下來,等待輸入,鍵盤輸入后才會繼續執行。

package io;
/*
 * 鍵盤輸入練習
 * 使用StringBuffer實現內容的不定長讀取
 * 1.while永真循環,遇到回車結束,判斷\n實現
 * 2.while永真循環,用到規定的字符over結束,用endsWith判斷實現
 * 3.使用轉換流實現 InputStramReader
 */

import java.io.*;

public class TranseDemo {
    public static void main(String[] args) throws IOException{
        
        //getByKeyboard();
        getBygetByKeyboardAndInputStramReader();
    }
    
    /*
     * 第3種、轉換流思路實現
     * 規定:over結尾,結束read
     * 以over結尾的同時,在over之前如果有字符也予以read
     */
    public static void getBygetByKeyboardAndInputStramReader() throws IOException{
        /*
         * System.in返回一個BufferedInputStream對象,也就是字節流的子類
         * 把System.in返回的字節流對象傳給InputStreamReader的構造方法,得到轉換流
         * 把轉換流傳入BufferedReader的構造方法中就得到了BufferedReader的實例對象br
         */
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        
        String line = null;
        while((line = br.readLine()) != null){
            if(line.endsWith("over")){
                if(line.length() != 4){
                    System.out.println(line.substring(0, line.length()-4));
                }
                break;
            }
            System.out.println(line);
        }
    }
    
    /*
     * 1.2思路實現
     */
    public static void getByKeyboard() throws IOException{
        InputStream in = System.in;
        
        StringBuffer sb = new StringBuffer();
        
        int len = 0;
        while(true){
            len = in.read();
            if(len == 13)
                continue;
            if(len == 10){
                
                if(sb.toString().endsWith("over"))
                    break;
                System.out.println(sb.toString());
                sb.delete(0, sb.length());
            }
            else{
                sb.append((char)len);
            }
        }
        
    }
}

十一、轉換流

控制臺輸出System.out 引用類型的成員變量,返回一個PrintStream,這個類的名字后綴是一個Stream,所以是字節流,繼承OutputStream,肯定是一個字節輸出流對象。剛才我們的程序,讀取的控制臺輸入,用的是字符流的readLine讀取的鍵盤輸入,將讀取到的字符,輸出到System.out中去,肯定不行,readLine讀取到的是字符數據, System.out返回的是字節輸出流,所以想將接受到的數據打印回控制臺,還是需要轉換流。

OutputStreamWriter , 繼承Writer,也是一個字符流,字符流向字節的橋梁。

構造方法,傳遞字節輸出流OutputStreamWriter(OutputStream out)構造器中,可以傳遞任意的字節輸出流

下面給出一個demo,實現控制臺的輸入輸出以及其中的轉換流操作等等。

轉換流示例
package io;
/*
 * 轉換流demo:
 * 字節流 -> 字符流 -> 程序處理字符流后 -> 字流節
 * 
 * 字符數據輸出到字節流中,使用OutputStreamWriter
 * 
 * 程序直接全部使用匿名類對象,要改變輸入源和輸出源
 * 只需要更改InputStreamReader構造方法傳入的參數就OK
 * 因為是示例程序,所以直接把IOException扔出去了
 * 
 * 注意:此demo外部的輸入輸出都是字節流,內部使用兩次轉換流
 */

import java.io.*;

public class TranceDemo2 {
    public static void main(String[] args) throws IOException {

        /*
         * 字節流-》字符流
         * System.in返回一個BufferedInputStream對象,也就是字節流的子類
         * 把System.in返回的字節流對象傳給InputStreamReader的構造方法,得到轉換流
         * 把轉換流傳入BufferedReader的構造方法中就得到了BufferedReader的實例對象br
         */
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        
        /* 字符流-》字節流
         * System.out返回PrintStream,是OutputStream的子類,也就是字節輸出流
         * PrintStream傳入OutputStreamWriter的構造方法,得到一個轉換流,此轉換流將輸出的字符流轉為為字節流
         * 最后使用BufferedWriter裝飾類操作
         */
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        
        String line = null;
        while((line = br.readLine()) != null){
            if(line.endsWith("#")){
                if(line.length() != 1){
                    bw.write(line.substring(0, line.length()-1));
                    bw.newLine();
                    bw.flush();
                }
            
                break;
            }
            else{
                bw.write(line);
                bw.newLine();
                bw.flush();
            }
        }
        //關閉流對象
        br.close();
        bw.close();
    }
}

十二、轉換流編碼

當流對象沒有指定查詢哪一個碼表,默認走操作系統的中GBK。

  • 轉換流OutputStreamWriter構造方法中,寫一個字節輸出流,寫編碼表的名字(String)不區分大小寫,轉換流會將文本數據,以指定的編碼形式寫入文件
  • 轉換流InputStreamReader構造方法中,寫一個字節輸讓流,寫編碼表的名字(String)不區分大小寫,轉換流會將文本數據,以指定的編碼形式讀取文件

demo

package io;

/*
 * 轉換流的編碼效果
 * 
 * 1.實現多種編碼輸出到文件
 * 2.實現多種編碼下的轉換流讀取
 */
import java.io.*;

public class TranseEncodingDemo {
    public static void main(String[] args) throws IOException {
        
        writeWithGBK();
        writeWithUTF();
        readWithUTF();
        
    }
    
    /*
     * 在win下GBK實際上是默認的讀取方式
     * 單還是顯示實現進行對比
     * 注意:傳入的編碼String不區分大小寫
     */
    public static void writeWithGBK() throws IOException{
        //初始化一個轉換流對象
        OutputStreamWriter ow = new OutputStreamWriter(new FileOutputStream("E:\\textByGBK.txt"),"GBK");
        
        ow.write("這是一個測試!");
        
        ow.flush();
        ow.close();
    }
    
    /*
     * 實現UTF-8的編碼輸出到文件
     * 注意:傳入的編碼String不區分大小寫
     */
    public static void writeWithUTF() throws IOException{
        //初始化一個轉換流對象
        OutputStreamWriter ow = new OutputStreamWriter(new FileOutputStream("E:\\textByUTF.txt"),"UTF-8");
        ow.write("這是一個測試!");
        ow.flush();
        ow.close();
    }
    
    /*
     * 讀取gbk就不寫了,直接實現讀取utf8
     * 按照默認的gbk編碼讀取輸出:榪欐槸涓?涓祴璇曪紒  (亂碼了)
     * 按照指定的編碼utf-8輸出:這是一個測試!  (正確)
     */
    public static void readWithUTF() throws IOException{
        
        InputStreamReader isr = new InputStreamReader(new FileInputStream("E:\\textByUTF.txt"),"UTF-8");
        
        char[] ch = new char[20];
        int len = isr.read(ch);
        System.out.println(new String(ch).substring(0, len).toString());
        isr.close();
    }
}

十三、字符的編碼和解碼

常見的編碼表有以下幾種:

  • 拉丁文 iso8859-1,用于 java網絡服務器Tomcat
  • GBK 中文編碼表,2個字節 對應1個漢字 20000個漢字
  • UTF-8 3個字節對應1個漢字

漢字進行編碼 :byte[] b = "漢字內容".getBytes("編碼類型");

漢字進行解碼:String s = new String(byte[] ,"編碼類型");

demo

package io;

/*
 * 漢字的編碼和解碼練習
 * 編碼
 * utf-8 : -26 -99 -88 -26 -81 -108 -24 -67 -87 
 * gbk : -47 -18 -79 -56 -48 -7 
 * Unicode: -2 -1 103 104 107 -44 -113 105 
 * 
 * 解碼和編碼會有異常,懶得處理,直接拋了
 */
public class EncodingDemo {
    public static void main(String[] args) throws Exception {
        
        String s = "楊比軒";
        
        byte[] by = s.getBytes("gbk");
        
        for(byte b : by){
            System.out.print(b + " ");
        }
        
        System.out.println("\n" + decoding(by,"gbk"));
        
    }
    
    /*
     * 解碼方法
     */
    public static String decoding(byte[] by, String coding) throws Exception{
        return new String(by,coding);
    }
}

附:
java--IO(二)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容