Java I/O流

Java I/O流

個人博客:https://sgeekioi.github.io/2019/07/25/javaio/#more

一、 Java IO 原理

  • 輸入 input:讀取外部數據(磁盤、光盤等存儲設備的數據)到程序(內存)中。
  • 輸出 output:將程序(內存)數據輸出到磁盤、光盤等存儲設備中。

二、 流的分類

  • 按操作數據單位不同分爲:字節流(8 bit),字符流(16 bit)
  • 按數據流的流向不同分爲:輸入流,輸出流
  • 按流的角色的不同分爲:節點流,處理流
[抽象基類] 字節流 字符流
輸入流 InputStream Reader
輸出流 OutputSreeam Writer

Java的IO流共設計40多個類,實際上非常規則,都是從以上4個抽象基類派生的。

由這四個類派生出來的子類名稱都是以其父類名作爲子類名後綴。

InputStream 和 Reader 是所有輸入流的基類

2.1 InputStream (典型實現:FileInpugStream)

  • int read()
    在輸入流中讀取數據的下一個字節。返回0-255範圍內的int字節值。如果因爲已經到達流末尾而沒有可用的字節,則返回-1.
  • int read(byte[])
    從此輸入流中將最多b.length個字節的數據讀入一個byte數組中。如果因爲已經到達流末尾而沒有可用的字節,則返回值爲-1。否則以整數形式返回時機讀取的字節數
  • int read(byte[],int off,int len)
    將輸入流中最多len個數據字節讀入byte數組。嘗試讀取len個字節,但讀取的字節也可能小於該值。以整數形式返回實際讀取的字節數。如果因爲流位於文件末尾而沒有可用的字節,則返回值爲-1.
  • FileInputStream 從文件系統中的某個文件中獲得輸入字節。FileInputStream用於讀取非文本數據之類的原始字節流。要讀取字符流,需要使用FileReader。

2.2 Reader (典型實現:FileReader)

  • int read()
    讀取單個字符。作爲整數讀取的字符,範圍在0-65535之間(0x00-0xffff)(2個字節的Unicode碼),如果已到達流的末尾,則返回-1
  • int read(char[] c)
    將字符讀入數組。如果已到達流的末尾,則返回-1.否則返回本次讀取的字符數
  • int read(char[] c,int off,int len)
    將字符讀入數組的某一部分。存到數組cbuf中,從off處開始存儲,最多讀len個字符。如果已到達流的末尾,則返回-1。否則飯hi本次讀取的字符數。

OutputStream和Writer 是所有輸出流的基類

2.3 OutPutStream

  • void write(int b)
    將指定的字節寫入此輸出流。write 的常規協定是:向輸出流寫入一個字節。要寫入的字節是參數 b 的八個低位。b 的 24 個高位將被忽略。即寫入0~255範圍的。
  • void write(byte[] b)
    將 b.length 個字節從指定的 byte 數組寫入此輸出流。write(b)的常規協定是:應該 與調用 write(b, 0, b.length) 的效果完全相同。
  • void write(byte[] b,int off,int len)
    將指定 byte 數組中從偏移量 off 開始的 len 個字節寫入此輸出流。

2.4 Writer

  • void write(int c)
    寫入單個字符。要寫入的字符包含在給定整數值的 16 個低位中,16 高位被忽略。即 寫入0 到 65535 之間的Unicode碼。
  • void write(char[] cbuf)
    寫入字符數組。
  • void write(char[] cbuf,int off,int len)
    寫入字符數組的某一部分。從off開始,寫入len個字符
  • void write(String str)
    寫入字符串。
  • void write(String str,int off,int len)
    寫入字符串的某一部分。
  • void flush()
    刷新該流的緩衝,則立即將它們寫入預期目標。

程序中打開的文件IO資源不屬於內存裏的資源,垃圾回收機制無法回收該資源,所以應該顯式關閉文件IO資源

三、節點流(或文件流)

直接從數據源或目的地讀寫數據

3.1讀取文件

//1.建立一個流對象,將已存在的一個我呢見加載進流
FiileReader fr = new FileReader(new File("Test.txt"));

//2.創建有一個臨時存放數據的數組
char[] ch = new char[1024];

//3.調用流對象的讀取方法將流中的數據讀入到數組中。
fr.read(ch);

//4.關閉資源
fr.close();

FileReader fr = null;
try { 
    fr = new FileReader(new File("test.txt"));
    char[] buf = new char[1024]; 
    int len; 
    while ((len = fr.read(buf)) != -1) {
        System.out.print(new String(buf, 0, len)); 
    }
} catch (IOException e) { 
    System.out.println("read-Exception :" + e.getMessage());
} finally { 
    if (fr != null) { 
        try { 
            fr.close(); 
        } catch (IOException e) { 
            System.out.println("close-Exception :" + e.getMessage();       } 
    }
}

3.2 寫入文件


//1. 創建流對象,建立數據存放文件
FileWriter fw = new FileWriter(new File("Test.txt"));

//2.調用流對象的寫入方法,將數據寫入流
fw.write("I hava a dream");

//3.關閉流資源
fw.close();
FileWriter fw = null;
try{
    fw = new FileWrtier(new File("Test.txt"));
    fw.write("I hava a dream");
} catch (IOException e){
    e.printStackTrace();
} finally {
    if (fw != null){
        try{
            fw.close();
        }catch (IOException e){
            e.printStackTrace();
        }    
    }
}

3.3 注意事項

  • 定義文件路徑時,注意:可以用“/”或者“\”
  • 寫入一個文件時,如果使用構造器FileOutputStream(file),則目錄下有同名文件將被覆蓋
  • 如果使用構造器FileOutputStream(file,true),則目錄下的同迷宮文件不會被覆蓋,在文件內容末尾追加內容
  • 讀取文件時,必須保證該文件已存在,否則報異常
  • 字節流操作字節,比如:.mp3、.avi、.mp4、.jpg、.doc等
  • 字符流操作字符,只能操作普通文本文件。最常見的文本文件:txt、java、c、cpp等語言的額源代碼。尤其注意doc、excel、ppt這些不是文本文件

四、處理流

不直接連接到數據源或目的地,而是“連接”在已存在的流(節點流或處理流)之上,通過對數據的處理爲程序 提供更爲強大的讀寫功能。

4.1 緩衝流(處理流之一)

  • 爲了提高數據讀寫的速度,Java API提供了帶緩衝功能的流類,在使用這些流類時,會創建一個內部緩衝區數組,缺省使用 8192個字節(8kb)的緩衝區
    在這裏插入圖片描述
public
class BufferedInputStream extends FilterInputStream {

    private static int DEFAULT_BUFFER_SIZE = 8192;
  • 緩衝流要“套接”在相應的節點流之上,根據數據操作單位可以把緩衝流分爲
    • BufferedInputStream 和 BufferedOutputStream
    • BufferedReader 和 BufferedWriter
  • 當讀取數據時,數據按塊讀入緩衝區,其後的讀操作直接訪問緩衝區
  • 當使用BufferedInputStream讀取字節文件時,BufferedInputStream會一次性從文件讀取8192(8kb),存在緩衝區中,直到緩衝區裝滿了,才重新從文件中讀取下一個8192個字節數組。
  • 向流中寫入字節時,不會直接寫道文件,先寫道緩衝區中直到緩衝區寫滿,BufferedOutputStream纔會把緩衝區中的數據一次性寫到文件裏。使用方法**flush()**可以強制將緩衝區的內容全部寫入輸入流
  • 關閉流的順序和打開流的順序相反。只要關閉最外層流即可,關閉最外城流也會相應關閉內層節點流
  • flush()方法的使用:手動將buffer中內容寫入文件
  • 如果是帶緩衝區的流對象close()方法,不但會關閉流,還會在關閉流之前刷新緩衝區,關閉不能再寫出

BufferedReader br = null;
BufferedWriter bw = null;
try {
      // 創建緩衝流對象:它是處理流,是對節點流的包裝
      br = new BufferedReader(new FileReader("d:\\IOTest\\source.txt")); 
      bw = new BufferedWriter(new FileWriter("d:\\IOTest\\d est.txt"));
      String str;
      while ((str = br.readLine()) != null) { // 一次讀取字符文本文件的一行字符
        bw.write(str); // 一次寫入一行字符串
        bw.newLine(); // 寫入行分隔符
      } 
      bw.flush(); // 刷新緩衝區
} catch (IOException e) {
        e.printStackTrace(); 
    } finally {
    // 關閉IO流對象
       try {
           if(bw != null) {
               bw.close(); // 關閉過濾流時,會自動關閉它所包裝的底層節點流
           }
       } catch (IOException e) {
           e.printStackTrace();
       } 
       
       try {
           if (br != null) { 
               br.close();
           } 
       } catch (IOException e) { 
           e.printStackTrace();
       } 
}

4.2 轉換流(處理流之二)

轉換流提供了字節流和字符流之間的轉換
在這裏插入圖片描述
Java API提供了兩個轉換流:
- InputStreamReader:將InputStream轉換爲Reader
實現將字節的的輸入流按指定字符集轉換爲字符的輸入流。
- 需要和InputStream“套接”
- 構造器
- public InputStreamReader(InputStream in)
- public InputStreamReader(InputStream in,String charsetName)
- OutputStreamWriter:將Writer轉換爲OutputStream
實現將字符的輸出流按指定字符集轉換爲字節的輸出流
- 需要和OutputStream“套接”
- 構造器
- public OutputStreamWriter(OutputStream out)
- public OutputStreamWriter(OutputStream out,String charsetName)

  • 字節流中的數據都是字符時,轉成字符流操作更高效。
  • 很多時候我們使用轉換流來處理文件亂碼問題。實現編碼和節碼的功能。

4.3 打印流(標準流之三)

實現將基本數據類型的數據格式轉換爲字符串
打印流:PrintStream和PrintWriter

  • 提供了一系列重載的print()和pringln()方法,用於多種數據類型的輸出
  • PrintStream和PrintWriter的輸出不會拋出IOException異常
  • PrintStream和PrintWriter有自動flush功能
  • PrintStream打印的所有字符都使用平臺的默認字符編碼轉換爲字節。在需要寫入字符而不是寫入字節的情況下,應使用PrintWriter類。
  • System.out返回的是PrintStream的實例

4.4 對象流

ObjectInputStream和ObjectOutputStream

  • 用於存儲和讀取基本數據類型數據或對象的處理流。它的強大之處就是可以把Java中的對象寫入到數據源中,也能把對象從數據源中還原回來。

  • 序列化:用ObjectOutputStream類保存基本類型數據或對象的機制

  • 反序列化:用ObjectInputStream類讀取基本數據類型或對象的機制

  • ObjectOutStream和ObjectInputStream不能序列化statictransient修飾的成員變量

4.5對象的序列化

  • 對象的序列化機制允許把內存中的Java對象轉化稱平臺無關的二進制流,從而把允許這種二進制流持久的保存在磁盤上,或通過網絡將這中二進制流傳輸到另一個網絡節點。當其他程序獲取了這種二進制流,就可以恢復成原來的Java對象
  • 序列化的好處在於可將任何實現了Serializable接口的對象轉化爲字節數據,使其在保存和傳輸時可被還原
  • 序列化時RMI(Remote Method Invoke-遠程方法調用)過程的參數和返回值都必須實現的機制,而RMI是JavaEE的基礎。因此序列化機制是JavaEE平臺的基礎
  • 如果需要讓兩個對象支持序列化機制,則必須讓對象所屬的類及其屬性是可序列化的,爲了讓某個類是可序列化的,則該類必須實現如下兩個接口之一
    • Serializable
    • Externlizable
  • 凡是實現Serializable接口的類都有一個表示序列化版本標識符的靜態變量:
    • private static final long serialVersionUID;
    • serialVersionUID 用來表明類的不同版本間的兼容性。簡言之,其目的是以序列化對象進行版本控制,有關各版本反序列化時是否兼容。
    • 如果類沒有顯示定義這個靜態常量,它的值是Java運行時環境根據類的內部細節自動生成的。**若類的實例變量做了修改,serialVersionUID可能發生變化。**故建議,顯示聲明。
  • 簡單來說,Java的序列化機制是通過在運行是判斷類的serialVersionUID來驗證版本一致性的。在進行反序列化時,JVM會把傳來的字節流中serialVersionUID與本地相應實體類的serialVersionUID進行比較,如果相同就認爲是一致的,可以進行反序列化,否則就會出現序列化版本不一致的異常(InvalidcastException)
4.5.1 使用對象流序列化對象
  • 若某個類實現了Serializable接口,該類的對象就是可序列化的:
    • 創建一個ObjectOututStream
    • 調用ObjectoutputStream對象的writeObject(對象)方法輸出可序列化對象
    • 注意寫一次,操作一次flush()
  • 反序列化
    • 創建一個ObjectInputStream
    • 調用readObject()方法讀取流中的對象

如果某個類的屬性不是基本數據類型或String類型,而是另一個引用類型,那麼這個引用類型必須是可序列化的,否則擁有該類型的Field的類也不能序列化

序列化:將對象寫入到磁盤或者進行網絡傳輸。要求對象必須實現序列化

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("data.txt"));
Persion p = new Per("張三",18,"北京",new Pet());
oos.writeObject(p);
oos.flush();
oos.close();

反序列化:將磁盤中的對象數據源讀出。

ObjectInputStream ois = new ObjectInputStream(new FileInputStream("data.txt");
Persion p1 = (Persion)ois.readObject();
System.out.println(p1.toString());
ois.close();

面試題
**談談你對java.io.Serializable接口的理解,我們知道它用於序列化,是空方法接口,還有其他人是嗎?

  • 實現了Serializable接口的對象,可將它們轉換成一系列字節,並可在以後完全恢復回原來的樣子。這一過程亦可通過網絡進行。這意味着序列化機制能自動補償操作系統間的差異。換句話說,可以在Windows機器上創建一個對象,對其序列化 ,然後通過網絡發給一臺Unix機器,然後在那裏準確無誤地重新裝配。不必關心數據在不同機器上如何表示,也不必關心字節地順序或者其他任何細節。
  • 由於大部分作爲參數地類如String、Integer等都實現了Serializable的接口,也可以利用多態的性質,作爲參數使接口更靈活。

4.5.2隨機存取文件流 RadnomAccessFile類

我們可以用RandomAccessFile這個類,來實現一個多線程斷點下載的功能,用過下載工具的朋友們都知道,下載前都會建立兩個臨時文件,一個是與被下載文件大小相同的空文件,另一個是記錄文件指針的位置文件,每次暫停的時候,都會保存上一次的指針,然後斷點下載的時候,會繼續從上次的地方下載,從而是按斷點下載或上傳的功能。

  • 讀取文件內容
RandomAccessFile raf = new RandomAccessFile(“test.txt”, “rw”);
raf.seek(5); 
byte [] b = new byte[1024];
int off = 0;
int len = 5;
raf.read(b, off, len);
String str = new String(b, 0, len); System.out.println(str);
raf.close();

  • 寫入文件內容
RandomAccessFile raf = new RandomAccessFile("test.txt", "rw"); raf.seek(5);
//先讀出來 String temp = raf.readLine();
raf.seek(5); raf.write("xyz".getBytes()); 
raf.write(temp.getBytes());
raf.close();

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章