1. 程式人生 > >Java筆記:IO流

Java筆記:IO流

IO概述

資料的傳輸,可以看作一種資料的流動,按照流動的放心,以記憶體為基準,分為輸入input和輸出output,即流向記憶體是輸入流,流出記憶體是輸出流

java中I/O操作,主要是指使用java.io包下的內容,進行輸入輸出操作,輸入也被叫做讀取資料,輸出也被叫做寫出資料

IO分類

根據資料的流向分為:輸入流和輸出流

  • 輸入流:把資料從其他裝置上讀取到記憶體的流
  • 輸出流:把資料從記憶體中寫出到其他裝置上的流

根據資料的型別分為:位元組流和字元流

  • 位元組流:以位元組為單位,讀寫資料的流
  • 字元流:以字元為單位,讀寫資料的流

頂級父類

輸入流 輸出流
位元組流 位元組輸入流InputStream 位元組輸出流OutputStream
字元流 字元輸入流Reader 字元輸出流Writer

分類概述

OutputStream:輸出流的抽象類
FileOutputStream:位元組輸出流
ObjectOutputStream:序列化流
FilterOutputStream :自帶過濾器的輸出流
———- BufferedOutputStream:位元組緩衝輸出流
———- PrintStream:列印流
InputStream:輸入流的抽象類
FileInputStream:位元組輸入流
ObjectInputStream:反序列化流
FilterInputStream:自帶過濾器的輸入流 —–> BufferedInputStream:位元組緩衝輸入流
Writer:字元輸出流的抽象類
BufferedWriter:字元緩衝輸出流
OutputStreamWriter:轉換編碼的字元輸出流 —–> FileWriter :字元輸出流
Reader:字元輸入流的抽象類
BufferedReader:字元緩衝輸入流
InputStreamReader:轉換編碼的字元輸入流 —–> FileReader:字元輸入流

位元組流

一切文字資料在儲存時,都是以二進位制數字的形式儲存,都是一個一個的位元組,在傳輸時也一樣如此,所以,位元組流可以傳輸任意檔案資料,在操作流的時候,要注意無論使用任何流物件,底層傳輸的始終為二進位制資料

位元組輸出流【OutputStream】

java.io.OutputStream 抽象類是所有位元組輸出流的超類,將指定的位元組資訊寫出到目的地,它定義了位元組輸出流的基本共性功能方法

  • public void close() :關閉此輸出流並釋放與此流相關聯的所有系統資源
  • public void flush() :重新整理此輸出流並強制任何緩衝的輸出位元組被寫出
  • public void write(byte[] b) :將b.length位元組從指定的位元組陣列寫入此輸出流
  • public void write(byte[] b,int off,int len) :從指定的位元組陣列寫入len位元組,從偏移量off開始輸出到此輸出流
  • public abstract void write(int b) :將指定的位元組輸出流

close方法,當完成流的操作時,必須呼叫此方法,釋放系統資源

FileOutputStream類

java.io.FileOutputStream類是檔案輸出流,用於將資料寫出到檔案
構造方法

  • public FileOutputStream(File file) :建立檔案輸出流以寫入由指定的File物件表示的檔案
  • public FileOutputStream(String name) :建立檔案輸出流以指定的名稱寫入檔案
    當建立一個流物件的時候,必須傳入一個檔案路徑,若在該路徑下不存在這個檔案,便會建立該檔案,如果有這個檔案會清空這個檔案的資料
    程式碼:
public class FileOutputStreamConstructor throws IOException{
    public static void main(String[] args){
        //使用File物件建立流物件
        File file = new File("a.txt");
        FileOutputStream fos = new FileOutputStream(file);
        //使用檔名稱建立流物件
        FileOutputStream fos1 = new FileOutputStream("b.txt");
    }
}

寫出位元組資料

1.寫出位元組:write(int b)方法,每次可以寫出一個位元組資料:
public class FOSWrite{
    public static void main(String[] args) throws IOException{
        //使用檔名稱建立流物件
        FileOutputStream fos = new FileOutputStream("fos.txt");
        //寫出資料
        fos.write(97);
        fos.write(97);
        fos.write(97);
        //關閉資源
        fos.close();
        //輸出abc
    }
}

雖然引數為int型別四個位元組,但是隻會保留一個位元組的資訊寫出
流操作完畢後必須呼叫close方法釋放系統資源
2.寫出位元組陣列:write(byte[] b) 每次可以寫出陣列中的資料,程式碼:

public class FOSwrite{
    public static void main(Srting[] args) throws IOException{
        //使用檔名稱建立流物件
        FileOutputStream fos = new FileOutputStream("fos.txt");
        //字串轉換為位元組陣列
        byte[] b = "張三丰".getBytes();
        //寫出位元組陣列資料
        fos.write(b);
        //關閉資源
        fos.close();
        //輸出張三丰
    }
}
3.寫出指定長度位元組陣列:write(byte[] b,int off,int len),每次寫出從off索引開始len個位元組,程式碼:
public class FOSwrite{
    public static void main(Srting[] args) throws IOException{
        //使用檔名稱建立流物件
        FileOutputStream fos = new FileOutputStream("fos.txt");
        //字串轉換為位元組陣列
        byte[] b = "abcde".getBytes();
        //寫出從索引2開始,2個位元組長度
        fos.write(b,2,2);
        //關閉資源
        fos.close();
        //輸出cd
    }
}

資料追加續寫

以上程式碼每次程式執行都會建立輸出流物件,清空目標檔案中的資料,其實還有保留目標檔案中的資料,新增新資料的方法
  • public FileOutputStream(File file,boolean append) :建立檔案輸出流以寫入由指定的File物件表示的檔案
  • public FileOutputStream(String name,boolean append) :建立檔案輸出流以以指定的名稱寫入檔案

這兩個構造方法引數中都需要傳入一個布林值,true表示追加資料,flase表示清空原有資料,這樣建立的輸出流物件就可以指定是否追加續寫了,程式碼:

public class FOSwrite{
    public static void main(Srting[] args) throws IOException{
        //使用檔名稱建立流物件
        FileOutputStream fos = new FileOutputStream("fos.txt"true);
        //字串轉換為位元組陣列
        byte[] b = "abcde".getBytes();
        fos.write(b);
        //關閉資源
        fos.close();
        //輸出cdabcde
    }
}

寫出換行

在windows系統中換行符號是\r\n,可以指定追加續寫

  • 回車符\r和換行符\n
    回車符:回到一行的開頭
    換行符:下一行
  • 系統中的換行
    windows系統裡,每行結尾是回車加換行即\r\n
    Linux系統裡每行結尾只有換行即\n
    Mac系統裡,每行結尾是回車即\r,從Mac OS X開始於Linux統一

位元組輸入流【InputStream】

java.io.InputStream 抽象類是表示位元組輸入流的所有類的超類,可以讀取位元組資訊到記憶體中,它定義了位元組輸入流的基本共性功能方法

  • public void close() :關閉此輸入流並釋放與此流相關聯的所有系統資源
  • public abstract int read() :從輸入流讀取資料的下一個位元組
  • public int read(byte[] b) :從輸入流中讀取一些位元組數,並將它們儲存到位元組陣列b中
    close方法,當完成流的操作時,必須呼叫此方法,釋放系統資源

FileInputStream類

java.io.FileInputStream 類是檔案輸入流類,從檔案中讀取位元組 **構造方法** - `public FileInputStream(File file)` :通過開啟與實際檔案的連線來建立一個FileInputStream,該檔案由檔案系統中的File物件file命名 - `public FileInputStream(String name)` :通過開啟與實際檔案的連線來建立一個FileInputStream,該檔案由檔案系統中的路徑名name命名 當建立一個流物件時,必須傳入一個檔案路徑,此路徑下沒有存在該檔案就會丟擲FileNotFoundException 構造舉例:
public class FileInputStreamConstructor{
    public static void main(String[] args)throws IOException{
        //使用File物件建立流物件
        File file = new File("a.txt");
        FileInputStream fos = new FileInputStream(file);
    }
}

讀取位元組資料

1.讀取位元組:read方法,每次可以讀取一個位元組的資料,提升為int型別,讀取到檔案末尾時返回-1,程式碼使用:
public class FISRead{
    public static void main(String[] args) throws IOException{
        FileInputStream fis = new FileInputStream("read.txt");
        int read = fis.read();
        System.out.println((char)read);
        read = fis.read();
        System.out.println((char)read);
        read = fis.read();
        System.out.println((char)read);
        read = fis.read();
        System.out.println((char)read);
        read = fis.read();
        System.out.println((char)read);
        //末尾處返回-1
        read = fis.read();
        System.out.println((char)read);
        fis.close();
    }
]
迴圈改進讀取方式:
public class FISRead{
    public static void main(String[] args) throws IOException{
        FileInputStream fis = new FileInputStream("read.txt");
        int b;
        while((b = fis.read()) != -1){
            System.out.println((char)b);
        }
        fis.close();
    }
}

雖然讀取了一個位元組,但是會自動提升為int型別
流操作完畢之後必須執行close方法

2.使用位元組陣列讀取:read(byte[] b),每次讀取b的長度位元組到陣列中,返回讀取到的有效位元組個數,讀取到末尾時返回-1,程式碼使用:
public class FISRead{
    public static void main(String[] args) throws IOException{
        FileInputStream fis = new FileInputStream("read.txt");
        int len;
        byte[] b = new byte[2];
        while((len = fis.read(b)) != -1){
            System.out.println(new String(b,0,len));
        }
        fis.close();
    }
}

使用陣列讀取,每次讀取多個位元組,減少了系統間IO操作次數,從而提高了讀寫效率,建議開發中使用
流的關閉原則,先開後關,後開先關

字元流

使用位元組流讀取文字檔案的時候,可能會遇到一些問題,例如遇到中文字元時可能不會顯示完整的字元,因為一箇中文字元可能佔用多個位元組儲存,所以java提供了一些字元流類,以字元為單位讀寫資料,專門用於處理文字檔案

字元輸入流【Reader】

java.io.Reader 抽象類時表示用於讀取字元流的所有類的超類,可以讀取字元資訊到記憶體中,它定義了字元輸入流的基本共性功能方法
  • public void close() :關閉此流以及相關聯的任何系統資源
  • public int read() :從輸入流讀取一個字元
  • public int read(char[] cbuf) :從輸入流中讀取一些字元,並存儲到字元陣列cbuf中

FileReader類

java.io.FileReader 類就是讀取字元檔案的便利類,構造時使用系統預設的字元編碼和預設位元組緩衝區

字元編碼:位元組與字元的對應規則,windows系統的中文編碼預設時GBK編碼表,idea中為UTF-8
位元組緩衝區:一個位元組陣列,用來臨時儲存位元組資料

構造方法

  • FileReader(File file) :建立一個新的FileReader,給定要讀取的File物件
  • FileReader(String fileName) :建立一個新的FileReader,給定要讀取的檔案的名稱

當建立一個流物件的時候,必須傳入一個檔案路徑
構造:

public class FileReaderConstructor{
    public static void main(String[] args) throws IOException{
        File file = new File("a.txt");
        FileReader fr = new FileReader(file);

        FileReader fr1 = new FileReader("b.txt");
    }
}

讀取字元資料
1.讀取字元:使用read方法每次可以讀取一個字元,並且提升為int型,讀取到檔案末尾時返回-1,可以利用迴圈來讀取

public class FRRead{
    public static void main(String[] args) throws IOException{
        //建立物件
        FileReader fr = new FileReader("read.txt");
        //定義一個區域性變數儲存資料
        int b;
        //迴圈讀取
        while((len = fr.read()) != -1){
            System.out.println((char)b);
        }
        fr.close();
    }
}

2.使用字元陣列讀取:read(char[] cbuf):每次讀取多個字元到陣列中,返回讀取到的有效字元個數,讀取到末尾時返回-1

public class FRRead{
    public static void main(String[] args) throws IOException{
        FileReader fr = new FileReader("read.txt");
        int len;
        char[] cbuf = new char[1024];
        while((len = fr.read(cbuf)) != -1){
            System.out.println(new String(cbuf,0,len))
        }
        fr.close();
    }
}

字元輸出流【Writer】

java.io.Writer 抽象類是表示用於寫出字元流的所有類的超類,將指定的字元資訊寫出到目的地,它定義了位元組輸出流的基本共性功能方法

  • void write(int c) :寫入單個字元
  • void writer(char[] cbuf) :寫入字元陣列
  • abstract void write(char[] cbuf,int off,int len) :寫入字元陣列的某一部分,off陣列的開始索引,off陣列的開始索引,len寫的字元個數
  • void write(String str) 寫入字串
  • void write(String ,int off,int len) :寫入字串的某一部分,off字串的開始索引,len寫的字元個數
  • void flush() :重新整理該流的緩衝
  • void close() :關閉此流,並先重新整理

FileWriter類

java.io.FileWriter 類是寫出字元到檔案的便利類,構造時使用系統預設的字元編碼和預設位元組緩衝區
構造方法

  • FileWriter(File file) :建立一個新的FileWriter,給定要讀取的File物件
  • FileWriter(String fileName) :建立一個新的FileWriter,給定要讀取的檔案的名稱

當你建立一個流物件的時候,必須傳入一個檔案路徑
舉例

public class FileWriterConstructor{
    public static void main(String[] args){
        File file = new File("a.txt");
        FileWriter fw = new FileWriter(file);

        FileWriter fw1 = new FileWriter("b.txt"); 
    }
}

基本寫出資料
使用write方法可以一次寫出一個位元組,字元陣列或者字串
關閉和重新整理
因為內建緩衝區的原因,如果不關閉輸出流,就無法寫出字元到檔案中,但是關閉流物件,就無法繼續寫出資料,那麼就要使用flush方法

  • flush:重新整理緩衝區,並可以繼續使用流物件
  • close:先重新整理緩衝區,然後通知系統釋放資源,不可繼續使用流物件
    即便呼叫了flush方法,在操作的最後還是要呼叫close方法釋放資源
    字元流只能操作文字檔案,不能操作圖片,視訊等非文字檔案
    當只是單純讀寫文字檔案的時候使用字元流,其他情況使用位元組流

IO異常的處理

**JDK7之前的處理方式** 實際開發中不能通過丟擲異常的方式來處理異常,建議使用try..catch…finally程式碼
public class HandleExcepetion1{
    public static void main(String[] args){
        //宣告變數
        FileWriter fw = null;
        try{
            //建立流物件
            fw = new FileWriter("fw.txt");
            //寫出資料
            fw.write("123456");
        } catch (IOException e){
            e.printStackTrace();
        } finally{
            try{
                if(fw != null){
                    fw.close();
                }
            } catch (IOException e){
                e.printStackTrace();
            }
        }
    }
}
**JDK7的處理** 可以使用JDK7的優化後的try-with-resource語句,該語句確保了每個資源在語句結束時關閉,所謂的資源是指程式完成後必須關閉的物件 格式:
try(建立流物件,多個物件之間使用;分隔){
    //讀寫資料
} catch (IOException e){
    e.printStackTrace();
}
演示
public class HandleException2{
    public static void main(String[] args){
        //建立流物件
        try(FileWriter fw= new FileWriter("fw.txt");){
            //寫出資料
            fw.write("123456");
        } catch (IOException e){
            e.printStackTrace();
        }
    }
}

緩衝流

除了以上一些基本流以外,還有其他功能更強大的流,比如能夠高效讀寫的緩衝流,轉換編碼的轉換流,持久儲存物件的序列化流等等,而這些流都是在基本的流物件基礎之上建立而來的,相當於對基本流物件的一種增強。

概述

緩衝流,也叫高效流,是對基本的四個Filexxx流的增強,所以也是四個流,按照資料型別分類:
  • 位元組緩衝流:BufferedInputStream,BufferedOutputStream
  • 字元緩衝流:BufferedReader,BufferedWriter

緩衝流的基本原理,是在建立流物件的時候,建立一個內建的預設大小的緩衝區陣列,通過緩衝區讀寫,減少系統IO次數,從而提高讀寫的效率

位元組緩衝流

構造方法:
- public BufferedInputStream(InputStream in) :建立一個新的緩衝輸入流
- public BufferedOutputStream(OutputStream out) :建立一個新的緩衝輸出流
構造舉例:

//建立位元組緩衝輸入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("bis.txt"));
//建立位元組緩衝輸出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("bos.txt"));

字元緩衝流

構造方法

  • public BufferedReader(Reader in) :建立一個新的緩衝輸入流
  • public BufferedWriter(Writer out) :建立一個新的緩衝輸出流

構造舉例

//建立字元緩衝輸入流
BufferedReader br = new BufferedReader(new FileReader("br.txt"));
//建立字元緩衝輸出流
BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt"));

特有方法
字元緩衝流的基本方法與普通字元流的呼叫方法一致,但是還有一些特有方法

  • BufferedReader:public String readLine() :讀一行文字
  • BufferedWriter:public void newLine() :寫一行行分隔符

轉換流

編碼引出的問題
在IDEA中使用FileReader讀取文字檔案時,預設使用的是UTF-8編碼,而windows系統建立檔案時,預設使用GBK編碼,那麼讀取的時候會產生亂碼

InputStreamReader類

轉換流java.io.InputStream,作為Reader得子類是從位元組流到字元流得橋樑,它能夠讀取位元組並使用指定得字符集將其解碼為字元,它得字符集可以由名稱指定,也可以接收平臺得預設字符集
構造方法

  • InputStreamReader(InputStream in) :建立一個使用預設字符集得字元流
  • InputStreamReader(InputStream in,String charsetName) :建立一個使用預設字符集的字元流

構造舉例:

InputStreamReader isr = new InputStreamReader(new FileInputStream("in.txt"));
InputStreamReader isr1 = new InputStreamReader(new FileInputStream("in.txt"),"GBK");

OutputStreamWriter類

轉換流java.io.OutputStreamWriter,作為Writer的子類,是字元流到位元組流的橋樑,使用指定的字符集將字元編碼為位元組,它的字符集可以由名稱指定,也可以接受平臺的預設字符集
構造方法

  • OutputStreamWriter(OutputStream out) :建立一個使用預設字符集的字元流
  • OutputStreamWriter(OutputStream out,String charsetName) :建立一個使用指定字符集的字元流

構造舉例

OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("out.txt"));
OutputStreamWriter osw1 = new OutputStreamWriter(new FileOutputStream("out.txt"),"GBK");

序列化

java提供了一種物件序列化的機制,用一個位元組序列可以表示一個物件,該位元組序列包含該物件的資料,物件的型別和物件中儲存的屬性等資訊。位元組序列寫出到檔案之後,相當於檔案中持久儲存了一個物件的資訊
反之,該位元組序列還可以從檔案中讀取回來,重構物件,對它進行反序列化,讀取資訊並且在記憶體中建立物件

ObjectOutputStream類

java.io.ObjectOutputStream類,將java物件的原始資料型別寫出到檔案,實現物件的持久儲存
構造方法:

  • public ObjectOutputStream(OutputStream out) :建立一個指定OutputStream的ObjectOutputStream

構造舉例

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("employee.txt"));

序列化操作
一個物件如果想被序列化必須滿足兩個條件

  • 該類必須實現java.io.Serializable介面,該介面是一個標記介面,不實現此介面的類將不會使任何狀態序列化或反序列化,會丟擲NotSerializableException
  • 該類的屬性必須是可序列化的,如果有一個屬性不需要可序列化,則該屬性必須註明是瞬態的,使用transient修飾
public class Employee implements Serializable{
    public String name;
    public transient int age;//瞬態修飾成員無法被序列化
    public void method(){
        System.out.println("===");
    }
}

寫出物件方法

  • public final void writeObject(Obkect obj) :寫出指定的物件
public class SerializeDemo{
    public static void main(String args){
        Employee e = new Employee();
        e.name = "11";
        e.age = 18;
        try{
            //建立序列化物件
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("employee.txt"));
            //寫出物件
            oos.writeObject(e);
            //釋放資源
            oos.close();
        } catch(IOException i){
            i.printStackTrace();
        }
    }
}

ObjectInputStream類

java.io.ObjectInputStream 反序列化流,將之前使用ObjectOutputStream序列化的原始資料恢復成物件
構造方法

  • public ObjectInputStream(InputStream in) :建立一個指定InputStream的ObjectInputStream

反序列化操作1
如果能找到一個物件的class檔案,就可以進行反序列化操作,呼叫ObjectInputStream讀取物件的方法:

  • public final Object readObject() :讀取一個物件
public class DeserializeDemo{
    public static void main(String[] args){
        Employee e = null;
        try{
            //建立反序列化流
            FileInputStream fileIn = new FileInputStream("employee.txt");
            ObjectInputStream ois = new ObjectInputStream(fileIn);
            //讀取一個物件
            e = (Employee) ois.readObject();
            //釋放資源
            in.close();
            fileIn.close();
        } catch(IOException i){
            i.printStackTrace();
            return;
        } catch(ClassNotFoundException c){
            c.printStackTrace();
            return;
        }
        System.out.println("Name:" + e.name);
        System.out.println("Name:" + e.age);
    }
}

對於JVM可以反序列化物件,它必須是能夠找到class檔案的類,如果找不到該類的class檔案,則丟擲一個ClassNotFoundException 異常
反序列化操作2
另外,當JVM反序列物件時,能找到class檔案,但是class檔案在序列化物件之後發生了改變,那麼反序列化操作也會失敗,丟擲InvalidClassException異常,原因如下

  • 該類的序列版本號與從流中讀取的類描述符的版本號不匹配
  • 該類包含未知資料型別
  • 該類沒有可訪問的無引數構造方法

Serializable 介面給需要序列化的類,提供了一個序列版本號,serialVersionUID該版本號的目的在於驗證序列化的物件和對應類是否版本匹配

public class Employee implements Serializable{
    //加入序列版本號
    private static final long serialVersionUID = 1L;
    public String name'
    public int age;
    //新增新的屬性,重新編譯,可以反序列化,該屬性賦為預設值
    public int eid;
    public void method(){
        System.out.println("===");
    }
}

列印流

概述
print 和 println的方法都來自於java.io.PrintStream類

PrintStream類

構造方法

  • public PrintStream(String fileName) :使用指定的檔名建立一個新的列印流
    舉例
PrintStream ps = new PrintStream("ps.txt");

改變列印流向
System.out 就是PrintStream 型別的,只不過它的流向是系統規定的,列印在控制檯上,不過既然是流物件,就可以改變它的流向

public class PrintDemo{
    public static void main(String[] args){
        //呼叫系統列印流,控制檯直接輸出97
        System.out.println(97);

        //建立列印流,指定檔案的名稱
        PrintStream ps = new PrintStream("ps.txt");
        //設定系統的列印流流向,輸出到ps.txt
        System.setOut(ps);
        //呼叫系統的列印流 ps.txt中輸出97
        System.out.println(97);
    }
}