1. 程式人生 > >File與IO流的學習

File與IO流的學習

IO流

1 字元輸入流

字元流(Java IO的Reader和Writer)功能與InputStream和OutputStream非常類似,InputStream和OutputStream基於位元組處理,而字元流(Reader和Writer)是基於字元處理。主要用於讀寫文字。

1.1 Reader類的常用方法

Reader類是Java IO中所有Reader的基類。子類包括FileReader,BufferedReader,InputStreamReader,StringReader和其他Reader。

  1. read() ; 讀取字元輸入流。讀取字元輸入流的下一個字元,返回一個字元,意味著這個返回值的範圍在0到65535之間(當達到流末尾時,同樣返回-1)。這並不意味著Reader只會從資料來源中一次讀取2個位元組,Reader會根據文字的編碼,一次讀取一個或者多個位元組。
  2. read(char cbuf[]);讀取字元輸入流。讀取多個字元,存入字元陣列cbuf,返回實際讀入的字元數。
  3. read(char cbuf[], int off, int len); 方法,讀取字元輸入流。每次讀取len個字元,存入字元陣列cbuf,從off下標開始儲存。
  4. close(); 關閉當前流,釋放與該流相關的資源,防止資源洩露。在帶資源的try語句中將被自動呼叫。關閉流之後還試圖讀取字元,會出現IOException異常。

1.2 Reader類的子類:FileReader

FileReader類從InputStreamReader類繼承而來(間接繼承了Reader)。該類按字元讀取流中資料。

1.3 FileReader構造方法和常用方法

1.3.1 構造方法

  1. FileReader(File file);通過開啟一個到實際檔案的連線來建立一個FileReader,該檔案通過檔案系統中的 File 物件 file 指定。

  2. FileReader(String fileName) ; 通過開啟一個到實際檔案的連線來建立一個FileReader,該檔案通過檔案系統中的路徑名 name 指定。

  3. (瞭解)FileReader(FileDescriptor fd) ; 在給定從中讀取資料的FileDescriptor 的情況下建立一個新 FileReader。

    提示:FileDescriptor 是“檔案描述符”。

    其中有三個屬性:

    1. in 標準輸入(鍵盤)的描述符(從鍵盤輸入讀取流)
    2. out 標準輸出(螢幕)的描述符(講流輸出到控制檯上)
    3. err 標準錯誤輸出(螢幕)的描述符(將流以紅色的字型輸出到控制檯上)

    程式碼示例:

    try {
       FileWriter fw = new FileWriter(FileDescriptor.out);
       fw.write("我是愛你的。");
       fw.flush();
       fw.close();
    }...

    控制檯輸出:

1.3.2 常用方法

test.txt 檔案內容(字元長度為17)

  1. read();讀取字元輸入流。讀取字元輸入流的下一個字元,返回一個字元。

    try {
    File file = new File("test.txt");
    FileReader fileReader = new FileReader(file);
    int read = fileReader.read();//預設第一次讀取第一個字元
    System.out.println((char)read);
    }...

    結果:

  2. read(char cbuf[]);讀取字元輸入流。讀取多個字元,存入字元陣列cbuf,返回實際讀入的字元數。

    try {
    File file = new File("test.txt");
    FileReader fileReader = new FileReader(file);
    
    char c [] = new char[20];
    int len = fileReader.read(c);//
    System.out.println("讀取的字元長度為:"+len);
    
    for (char d : c) {
        System.out.print(d);
    }
    }...

    結果:

  3. read(char cbuf[], int off, int len);讀取字元輸入流。每次讀取len個字元,存入字元陣列cbuf,從off下標開始儲存。

    try {
    File file = new File("test.txt");
    FileReader fileReader = new FileReader(file);
    
    char c [] = new char[20];
    int len = fileReader.read(c,2,8);//讀取8個字元存入c陣列,從下標2開始儲存
    System.out.println("讀取的字元長度為:"+len);
    
    for (char d : c) {
        System.out.print(d);
    }
    
    }

    結果:

  4. close();關閉當前流,釋放與該流相關的資源,防止資源洩露。在帶資源的try語句中將被自動呼叫。關閉流之後還試圖讀取字元,會出現IOException異常。

    try {
    File file = new File("test.txt");
    FileReader fileReader = new FileReader(file);
    int read = fileReader.read();//
    System.out.println((char)read);
    fileReader.close();//通過close()來關閉流,以釋放系統資源。
    }...
    //或者在這裡關閉
     ...finally {
        if(fileReader!=null)
            fileReader.close();
     }

    注意:

    1. 通常不使用close會導致記憶體洩露,垃圾回收機制會回收,但是最好自己顯式關閉
    2. OutputStream的作用是如FileOutStream,當不呼叫close的時候,不會將快取刷入檔案中。

    所以:一般使用完IO流之後都要通過close()來關閉,以釋放系統資源

2 字元輸出流

2.1 Writer類的常用方法

  1. write (String str); 將指定的字串寫入此輸出流。
  2. write(char[] cbuf, int off, int len); 將指定 char 陣列中從偏移量 off 開始的 len 個字元寫入此輸出流。
  3. flush(); 用於清空快取裡的資料,並通知底層去進行實際的寫操作。(強制把快取區裡面的資料寫入到檔案)
  4. close();關閉當前流,釋放與該流相關的資源。

2.2 Writer類的子類:FileWriter

FileWriter類從OutputStreamReader類繼承而來(間接繼承Writer類)。該類按字元向流中寫入資料。

2.3 FileWriter構造方法和常用方法

2.3.1 構造方法

  1. FileWriter(File file);通過開啟一個到實際檔案的連線來建立一個FileWriter,該檔案通過檔案系統中的 File 物件 file 指定。
  2. FileWriter(File file, boolean append);通過開啟一個到實際檔案的連線來建立一個FileWriter,該檔案通過檔案系統中的 File 物件 file 指定。 如果第二個引數為true,則將字元寫入檔案末尾處,而不是寫入檔案開始處。
  3. FileWriter(String fileName);通過開啟一個到實際檔案的連線來建立一個FileWriter,該檔案通過檔案系統中的路徑名 name 指定。
  4. FileWriter(String fileName, boolean append);通過開啟一個到實際檔案的連線來建立一個FileWriter,該檔案通過檔案系統中的路徑名 name 指定。如果第二個引數為true,則將字元寫入檔案末尾處,而不是寫入檔案開始處。
  5. FileWriter(FileDescriptor fd);在給定從中寫入資料的FileDescriptor 的情況下建立一個新 FileReader。(可以向控制檯輸出文字流)。

2.3.2 常用方法

  1. write (String str); 將指定的字串寫入此輸出流。

    try {
    File file = new File("test.txt");
    Writer fileWriter = new FileWriter(file);
    fileWriter.write("十年之前,我不認識你。");
    fileWriter.flush();
    fileWriter.close();
    }...

    結果:

  2. write(int c );將指定的字元寫入此輸出流。

    try {
    File file = new File("test.txt");
    Writer fileWriter = new FileWriter(file);
    fileWriter.write('育');
    fileWriter.flush();
    fileWriter.close();
    }...

    結果:

  3. write(char[] cbuf);將 cbuf 字元陣列寫入此輸出流。

    try {
    File file = new File("test.txt");
    Writer fileWriter = new FileWriter(file);
    char[] charArray = "字串轉字元陣列".toCharArray();
    fileWriter.write(charArray);
    fileWriter.flush();
    fileWriter.close();
    }

    結果:

  4. write(char[] cbuf, int off, int len);將 cbuf 字元陣列,按偏移量 off 開始的 len 個字元寫入此輸出流。

    try {
    File file = new File("test.txt");
    Writer fileWriter = new FileWriter(file);
    char[] charArray = "字串轉字元陣列".toCharArray();
    fileWriter.write(charArray, 1, 5);//從偏移量 1 開始,寫入5個字元。
    fileWriter.flush();
    fileWriter.close();
    }

    結果:

  5. write(String str, int off, int len);

    try {
    File file = new File("test.txt");
    Writer fileWriter = new FileWriter(file);
    String str ="字串也可以制定寫的內容";
    fileWriter.write(str, 3, 5);
    fileWriter.flush();
    fileWriter.close();
    } 

    結果:

3 轉換流(重點掌握)

位元組流轉字元流,稱作轉換流,包括:

  1. InputStreamReader—> 將位元組流轉換為字元流。是位元組流通向字元流的橋樑。如果不指定字符集編碼,該解碼過程將使用平臺預設的字元編碼,如:GBK/UTF-8。
  2. OutputStreamWriter—> 將位元組流轉換為字元流。是位元組流通向字元流的橋樑。如果不指定字符集編碼,該解碼過程將使用平臺預設的字元編碼,如:GBK/UTF-8。

3.1 InputStreamReader的構造方法

  1. InputStreamReader(InputStream in);//構造一個預設編碼集的InputStreamReader類。

  2. InputStreamReader(InputStream in,String charsetName);構造一個指定編碼集的InputStreamReader類。

3.2 InputStreamReader的使用

try {
    File file = new File("test.txt");
    FileInputStream fis = new FileInputStream(file);
    InputStreamReader isr = new InputStreamReader(fis);
    int read = isr.read();
    //char cbuf[]=new char[512];
    //isr.read(cbuf);
    System.out.println((char)read);
    isr.close();
}...

3.3 OutputStreamWriter的構造方法

  1. OutputStreamWriter(OutputStream out);構造一個預設編碼集的OutputStreamWriter類
  2. OutputStreamWriter(OutputStream out,String charsetName);構造一個指定編碼集的OutputStreamWriter類。

3.4 OutputStreamWriter的使用

try {
    File file = new File("test.txt");
    FileOutputStream fos = new FileOutputStream(file);
    OutputStreamWriter osw = new OutputStreamWriter(fos);
    String str="育知同創";
    osw.write(str);//直接寫入字串
    //osw.write(str.toCharArray());//寫入 char 陣列
    osw.flush();
    osw.close();
}...

4 字元快取流(BufferedReader/BufferedWriter)

(BufferedReader/BufferedWriter) 是帶緩衝區的字元流,預設緩衝區大小是8Kb,能夠減少訪問磁碟的次數,提高檔案讀取效能;並且可以一次性讀取一行字元。(類似管道套管道一樣,不帶緩衝的流只能一滴一滴流,套了管道後,可以讓一滴一滴留到外面的管道後一次性流出。)

4.1 字元快取流構造方法

4.1.1 BufferedReader

  1. BufferedReader(Reader in);建立一個預設緩衝區大小 8Kb 的字元緩衝輸入流;
  2. BufferedReader(Reader in, int sz);建立一個字元緩衝輸入流;並分配 sz/byte 大小的緩衝區。

4.1.1 BufferedWriter

  1. BufferedWriter(Writer out); 建立一個預設緩衝區大小 8Kb 的字元緩衝輸出流;
  2. BufferedWriter(Writer out, int sz); 建立一個字元緩衝輸出流;並分配 sz/byte 大小的緩衝區。

4.2 字元快取流的常用方法:readLine(), newLine()

  1. BufferedReader.readLine();在字元緩衝輸入流讀取字元的時候,可以一次性讀取一行,並將遊標指向下一行。

    try {
    File file = new File("test.txt");
    FileInputStream fis = new FileInputStream(file);
    InputStreamReader isr = new InputStreamReader(fis);
    BufferedReader br = new BufferedReader(isr);
    String str;
    while ((str = br.readLine())!=null) {
        System.out.println(str);
    }
    }...
  2. BufferedWriter.newLine();在字串緩衝輸出流寫入字元的時候,預設是在一行寫入,當需要換行的時候,呼叫 newLine() 實現文字換行。

    try {
    File file = new File("test.txt");
    FileOutputStream fos = new FileOutputStream(file);
    OutputStreamWriter osw = new OutputStreamWriter(fos);
    BufferedWriter bw = new BufferedWriter(osw);
    bw.write("寫入一行的文字");
    bw.newLine();//換行
    bw.write("寫入第二行的文字");
    bw.flush();//重新整理緩衝區,強制寫入檔案中
    bw.close();
    }...

2.1 記憶體流(重點掌握)

2.1.1 什麼是記憶體流

當輸出流的目的,和輸入流的源是記憶體時,這樣的流稱之為記憶體流。(就是將資料寫入RAM)

2.1.2 記憶體流的構造方法

  1. ByteArrayInputStream(byte buf[]); 建立一個 ByteArrayInputStream 並把指定該輸入流的資料來源buf[]。
  2. ByteArrayOutputStream(); 建立一個 ByteArrayOutputStream 並把分配一個32位元組(預設大小)的緩衝區。
  3. ByteArrayOutputStream(int size); 建立一個 ByteArrayOutputStream 並分配自定 size 位元組的緩衝區。

2.1.3 讀取記憶體資料和寫入到記憶體資料

2.1.3.1 讀取記憶體資料

try {
    String testContent = "ABCDEFG";//程式執行的時候 這資料本身就在記憶體,
    ByteArrayInputStream bais = new ByteArrayInputStream(testContent.getBytes());//建立記憶體輸入流,指定要讀取的資料 byte[]
    int read;
    while ((read = bais.read()) != -1) {//和普通流讀取位元組是一樣的(也可以巢狀管道)
        System.out.println((char) read);
    }
    bais.close();//關閉流,釋放記憶體資源
}...

2.1.3.2 寫入資料到記憶體

try {
    String testContent = "ABCDEFG";
    ByteArrayOutputStream baos = new ByteArrayOutputStream();//建立記憶體輸出流,把資料寫入到記憶體中
    baos.write(testContent.getBytes());//和普通的輸出流寫輸入一樣,(也可以巢狀管道)
    baos.flush();
    baos.close();
}...

2.1.4 ByteArrayOutputStream 常用方法:toByteArray(), toString()

  1. toByteArray() 方法;是將 ByteArrayOutputStream 物件所寫入到記憶體的資料 轉換成 byte[] 返回。
  2. toString() 方法 ;是將 ByteArrayOutputStream 物件所寫入到記憶體的資料 轉換成 String 返回。

提示:記憶體流 除了 ByteArrayInputStream 與 ByteArrayOutputStream 主要處理位元組資料之外,對應的還有:

  • CharArrayReader 與 CharArrayWriter 主要處理字元陣列。
  • StringReader 與 StringWriter 主要處理字串。

使用方式大同小異。

2.3 物件流

2,3,1 物件流

​ ObjectInputStream ObjectOutputStream類分別是InputStream和OutputStream的子類,物件輸出流使用writeObject(Object obj)方法,將一個物件obj寫入到一個檔案,使用readObject()讀取一個物件。

構造方法:

  1. ObjectInputStream (InputStream in)
  2. ObjectOutputStream(OutputStream out)

程式碼示例:

  1. 將物件寫入檔案:

    //'序列化'的物件寫入檔案
    OutputStream outputStream = new FileOutputStream(file);
    ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
    objectOutputStream.writeObject(Object obj);
    objectOutputStream.close();
  2. 從檔案讀取物件:

    //序列化讀取物件
    InputStream inputStream = new FileInputStream(file);
    ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
    Object obj = objectInputStream.readObject();
    objectInputStream.close();

注意:當使用物件流寫入或者讀取物件的時候,必須保證該物件是序列化的,這樣是為了保證物件能夠正確的寫入檔案,並能夠把物件正確的讀回程序。

什麼是物件序列化?

2.3.2 物件序列化

​ 所謂的物件的序列化就是將物件轉換成二進位制資料流的一種實現手段,通過將物件序列化,可以方便的實現物件的傳輸及儲存。在Java中提供了ObejctInputStream 和ObjectOutputStream這兩個類用於序列化物件的操作。用於儲存和讀取物件的輸入輸出流類,要想實現物件的序列化需要實現Serializable介面,但是Serializable介面中沒有定義任何的方法,僅僅被用作一種標記,以被編譯器作特殊處理。

package com.yztc.main;

import java.io.Serializable;
//實現了Serializable介面。序列化
public class Student implements Serializable {
    //由編譯器自動生成,用來解決不同的版本之間的序列化問題。 
    private static final long serialVersionUID = -79485540193100816L;

    private int age;
    private String name;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Student() {
        super();
    }

    public Student(int age, String name) {
        this.age = age;
        this.name = name;
    }

}

2.3.3 transient

  1. 一旦變數被transient修飾,變數將不再是物件持久化(寫到磁盤裡持久儲存)的一部分,該變數內容在序列化後無法獲得訪問。
  2. transient關鍵字只能修飾變數,而不能修飾方法和類。注意,本地變數是不能被transient關鍵字修飾的。變數如果是使用者自定義類變數,則該類需要實現Serializable介面。
  3. 被transient關鍵字修飾的成員變數不再能被序列化。
  4. 靜態變數不管是否被transient修飾,均不能被序列化。

2.4 RandomAccessFile類

RandomAccessFile 類可以說是Java語言中功能最為豐富的檔案訪問類,它提供了眾多的檔案訪問方法。RandomAccessFile 類支援“隨機訪問”方式,可以跳轉到檔案的任意位置處讀寫資料。在要訪問一個檔案的時候,不想把檔案從頭讀到尾,而是希望像訪問一個數據庫一樣地訪問一個文字檔案,這時,使用RandomAccessFile類就是最佳選擇。

RandomAccessFile物件類有個位置指示器,指向當前讀寫處的位置,當讀寫n個位元組後,檔案指示器將指向這n個位元組後的下一個位元組處。剛開啟檔案時,檔案指示器指向檔案的開頭處,可以移動檔案指示器到新的位置,隨後的讀寫操作將從新的位置開始。RandomAccessFile在資料等長記錄格式檔案的隨機(相對順序而言)讀取時有很大的優勢,但該類僅限於操作檔案,不能訪問其它的IO 裝置,如網路、記憶體映像等。

以讀寫的方式開啟一個檔案時,如果檔案不存在,程式會自動建立此檔案。

有關RandomAccessFile類中的成員方法及使用說明請參閱JDK文件。常見API如下:

方法名 描述
void close(); 關閉此隨機訪問檔案流並釋放與該流關聯的所有系統資源。
long getFilePointer(); 返回此檔案中的當前偏移量。
long length(); 返回此檔案的長度。
read函式集 從檔案讀
void seek(long pos); 設定到此檔案開頭測量到的檔案指標偏移量,在該位置發生下一個讀取或寫入操作。
int skipBytes(int n) 嘗試跳過輸入的 n 個位元組以丟棄跳過的位元組。
write函式集 往檔案寫

2.4.1 RandomAccessFile 類的構造方法

  1. new RandomAccessFile(f, “rw”); // 讀寫方式
  2. new RandomAccessFile(f, “r”); // 只讀方式

2.4.2 向檔案中記憶寫入資料

    File file = new File("accessFile");
    RandomAccessFile raf = new RandomAccessFile(file, "rw");  
      // 以下向 raf 檔案中寫資料  
    raf.writeInt(20);// 佔4個位元組  
    raf.writeDouble(8.236598);// 佔8個位元組  
    raf.writeShort(395);// 佔2個位元組  
    raf.writeUTF("這是一個UTF字串");// 這個長度寫在當前字串指標的前兩個位元組處,可用readShort()讀取  
    raf.writeBoolean(true);// 佔1個位元組  
    raf.writeLong(2325451l);// 佔8個位元組  
    raf.writeUTF("又是一個UTF字串哈哈");  
    raf.writeFloat(35.5f);// 佔4個位元組  
    raf.writeChar('a');// 佔2個位元組  
    raf.close();

2.4.3 從檔案中讀取隨機記憶的檔案內容

    File file = new File("accessFile");
    RandomAccessFile raf = new RandomAccessFile(file, "rw");
    System.out.println(raf.readInt());//讀取Int資料,指標會往後移動4位元組
    System.out.println(raf.readDouble());//讀取Double資料,指標會往後移動8位元組
    System.out.println(raf.readUTF());//讀取字串,指標會移到該字串後
    raf.skipBytes(3);//跳過3個位元組,也就是跳過上面例子的 boolen 和 short 值。
    System.out.println(raf.readLong());//讀取long值
    short readShort = raf.readShort();//讀取字串的長度
    System.out.println("目前指標處的字串長度為=" + readShort);
    raf.skipBytes(readShort);//跳過該字串
    System.out.println(raf.readFloat());//讀取float值
    System.out.println(raf.readChar());//讀取char值

    //long length = raf.length();
    //System.out.println("檔案的總位元組數為:"+length);

    //long filePointer = raf.getFilePointer();//當前指標的位置,定位到哪個位元組了。
    //System.out.println("目前位元組指標定位在:"+filePointer);

    //raf.seek(4);//直接定位到第4個位元組處。

2.5 裝飾者模式

2.5.1 裝飾者模式的定義

​ 擴充套件類功能,(繼承也能做到)。但是相比繼承,裝飾者模式是動態地將責任(擴充套件功能)附加到物件上,比繼承更有彈性。

2.5.2裝飾者模式的特點

  1. 裝飾者和被裝飾物件有相同的超型別
  2. 可以用一個或多個裝飾者包裝一個物件。
  3. 因為裝飾者和被裝飾者具有相同的型別,所以任何需要原始物件的場合,可以用裝飾過的物件代替。
  4. 裝飾者可以在所委託被裝飾者的行為之前/或之後,加上自己的行為,以達到特定的目的。
  5. 物件可以在任何時候被裝飾,所以可以在執行時動態地、不限量地用你喜歡的裝飾者來裝飾物件。

2.5.3 裝飾者模式缺點

​ 會在設計中加入大量的小類,如果過度使用,會讓程式變得複雜。

2.5.4 裝飾者模式在JDK中的運用

Java當中的IO是運用了裝飾者模式的最典型的例子。
下面是一個簡單的例子,通過BufferedReader物件來裝飾InputStreamReader物件:

BufferedReader input=new BufferedReader(new InputStreamReader(System.in));
//System.in 是一個 InputStream 物件