Java Language——IO 机制
Java 的 IO 流使用了一種裝飾器設計模式,它將 IO 流分為底層節點流和上層處理流。本篇重點在如何訪問文件與目錄、如何以二進制格式和文本格式來讀寫數據、對象序列化機制、還有 Java7 的 “NIO.2”。
裝飾設計模式:當想要對已有的對象進行功能增強時,可以定義類,將已有對象傳入,基于已有的功能,并提供加強功能。那么自定義的該類稱為裝飾類。
裝飾類通常會通過構造方法接收被裝飾的對象。并基于被裝飾的對象的功能,提供更強的功能。
同步?阻塞?
IO 的方式通常分為:
- BIO(同步阻塞):字節流 InputStream、OutputStream,字符流 Reader、Writer;
- NIO(同步非阻塞):多路復用;
- AIO(異步非阻塞):基于事件和回調機制。
首先我們來看一下 File 類,java.io.File 下代表與平臺無關的文件和目錄,程序操作文件和目錄都可以通過 File 類來完成,File 能新建、刪除、重命名文件和目錄,但是不能訪問文件內容本身。如果需要訪問文件內容本身則需要使用輸入/輸出流。File 的常用方法如下:
File file = new File("."); // 以當前路徑來創建一個File對象 String name = file.getName(); // 獲取文件名 String parent = file.getParent(); // 獲取相對路徑的父路徑 file.getAbsoluteFile(); // 獲取絕對路徑 file.getAbsoluteFile().getParent(); // 獲取上一級路徑// 創建臨時文件 File tempFile = File.createTempFile("temp", ".txt", file); // 在當前路徑下創建一個臨時文件 tempFile.deleteOnExit(); // 指定當JVM退出時刪除該文件// 創建新文件 File newFile = new File(System.currentTimeMillis() + ".txt"); // 以系統當前時間作為新文件名來創建新文件 boolean b = newFile.exists(); // 判斷File對象所對應的文件或目錄是否存在,存在返回true boolean b1 = newFile.createNewFile(); // 以指定newFile對象來創建一個文件 boolean b2 = newFile.mkdir(); // 以newFile對象來創建一個目錄,因為newFile已經存在,所以下面方法返回false,即無法創建該目錄下來看一下 IO(輸入/輸出)的思維導圖:
使用處理流的思路:使用處理流包裝節點流,程序通過處理流來執行輸入/輸出功能,讓節點流與底層 IO 設備、文件交互。
使用處理流的好處:簡單;執行效率更高。
注意:處理流的使用必須建立在其他節點流的基礎之上。
Java 輸入/輸出流體系中常用的流分類:
除了這些還有RandomAccessFile,下面將有單獨介紹。
1.字符流
1.FileWriter
字符寫入流 FileWriter 是專門用于操作文件的 Writer 子類對象。在硬盤上創建一個文件并寫入一些數據:
// FileWriter對象一被初始化就必須要明確被操作的文件, 該文件會被創建到指定目錄下, 如果已有同名文件,將被覆蓋 FileWriter fw = new FileWriter("test.txt"); // 調用write方法, 將字符串寫入到流中 fw.write("abcdef"); // 刷新流對象中的緩沖中的數據, 將數據刷到目的地中 //fw.flush(); // 關閉流資源, 關閉之前會刷新一次內部的緩沖中的數據, 將數據刷到目的地中 // 和flush區別: flush刷新后流可以繼續使用, close刷新后會將流關閉 fw.close();對已有文件的數據續寫:
// 傳遞一個true參數, 代表不覆蓋已有的文件并在已有文件的末尾處進行數據續寫 FileWriter fw = new FileWriter("test.txt",true); fw.write("abc\r\def"); fw.close();2.FileReader
字符讀取流 FileReader 讀取文本文件:
// 創建一個文件讀取流對象, 和已存在的文件相關聯, 如不存在會發生FileNotFoundException FileReader fr = new FileReader("test.txt");// 第一種讀取方式: 調用讀取流對象的read方法 // read():一次讀一個字符, 而且會自動往下讀 int ch = 0; while((ch=fr.read())!=-1) {System.out.println("ch="+(char)ch); }// 第二種讀取方式: 通過字符數組進行讀取 // 定義一個字符數組, 用于存儲讀到字符, 該read(char[])返回的是讀到字符個數 char[] buf = new char[1024]; int num = 0; while((num=fr.read(buf))!=-1) {System.out.println(new String(buf,0,num)); }fr.close();3.拷貝文本文件
復制的原理其實就是將 C 盤下的文件數據通過不斷的讀寫存儲到 D 盤的一個文件中。復制過程:
FileWriter fw = null; FileReader fr = null; try {fw = new FileWriter("test_copy.txt"); // 創建目的地fr = new FileReader("test.java"); // 與已有文件關聯char[] buf = new char[1024];int len = 0;while ((len = fr.read(buf)) != -1) {fw.write(buf, 0, len);} } catch (IOException e) {throw new RuntimeException("讀寫失敗"); } finally {if (fr != null)try {fr.close();} catch (IOException e) {}if (fw != null)try {fw.close();} catch (IOException e) {} }4.BufferedWriter
字符流的緩沖流有 BufferedReader 和 BufferedWriter。緩沖區的出現是為了提高流的操作效率而出現的,所以在創建緩沖區之前,必須要先有流對象。字符流緩沖區中提供了一個跨平臺的換行符:newLine();
通過字符寫入流緩沖區 BufferedWriter 創建 buffered.txt 并寫入數據:
// 創建字符寫入流對象 FileWriter fw = new FileWriter("buffered.txt"); // 為了提高字符寫入流效率, 加入了緩沖技術 // 只要將需要被提高效率的流對象作為參數傳遞給緩沖區的構造函數即可 BufferedWriter bufw = new BufferedWriter(fw); for (int x = 1; x < 5; x++) {bufw.write("abcdef" + x);bufw.newLine();bufw.flush(); } //bufw.flush(); // 記住, 只要用到緩沖區, 就要記得刷新 bufw.close(); // 其實關閉緩沖區, 就是在關閉緩沖區中的流對象5.BufferedReader
字符讀取流緩沖區 BufferedReader:該緩沖區提供了一個一次讀一行的方法 readLine,方便于對文本數據的獲取。當返回 null 時,表示讀到文件末尾。readLine 方法返回的時候只返回回車符之前的數據內容。并不返回回車符。
通過字符讀取流緩沖區 BufferedReader 讀取數據:
// 創建字符讀取流對象 FileReader fr = new FileReader("buffered.txt"); // 創建一個讀取流對象和文件相關聯 // 為了提高效率, 加入緩沖技術, 將字符讀取流對象作為參數傳遞給緩沖對象的構造函數 BufferedReader bufr = new BufferedReader(fr); String line = null; while((line=bufr.readLine())!=null) {System.out.print(line); } bufr.close();5.通過緩沖區拷貝文本文件
BufferedReader bufr = null; BufferedWriter bufw = null; try {bufr = new BufferedReader(new FileReader("BufferedWriterTest.java"));bufw = new BufferedWriter(new FileWriter("BufferedWriterTest_copy.txt"));String line = null;while ((line = bufr.readLine()) != null) {bufw.write(line);bufw.newLine();bufw.flush();} } catch (IOException e) {throw new RuntimeException("讀寫失敗"); } finally {try {if (bufr != null)bufr.close();} catch (IOException e) {throw new RuntimeException("讀取關閉失敗");}try {if (bufw != null)bufw.close();} catch (IOException e) {throw new RuntimeException("寫入關閉失敗");} }2.字節流
想要操作字節文件,例如圖片數據,字符流就無法滿足需求了,這時就要用到字節流。
1.拷貝字節文件
通過字節流拷貝一張圖片:
FileOutputStream fos = null; FileInputStream fis = null; try {// 用字節輸出流對象創建一個圖片文件, 用于存儲獲取到的圖片數據fos = new FileOutputStream("d:\\pic_copy.bmp");// 用字節輸入流對象和圖片關聯fis = new FileInputStream("d:\\pic.bmp");// 通過循環讀寫,完成數據的存儲byte[] buf = new byte[1024];int len = 0;while ((len = fis.read(buf)) != -1) {fos.write(buf, 0, len);} } 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("寫入關閉失敗");} }2.通過緩沖區拷貝字節文件
通過字節流的緩沖區拷貝 mp4:
BufferedInputStream bufis = new BufferedInputStream(new FileInputStream("c:\\video.Mp4")); BufferedOutputStream bufos = new BufferedOutputStream(new FileOutputStream("c:\\video_copy.Mp4")); int by = 0; while((by=bufis.read())!=-1){bufos.write(by); } bufos.close(); bufis.close();3.轉換流
IO 體系只提供了將字節流向字符流轉換的轉換流,InputStreamReader 將字節輸入流轉換成字符輸入流,OutputStreamWriter 將字節輸出流轉換成字節輸出流。
下面以獲取鍵盤輸入為例來介紹轉換流的用法:
BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); // 鍵盤的最常見寫法 //BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out)); String line = null; while((line=br.readLine())!=null) {if(line.equals("exit")) {System.exit(1); // 程序退出}System.out.println("輸入內容為:" + line); // 打印輸入內容 } br.close();注意:readLine 是字符流 BufferedReader 中的方法,而鍵盤錄入的 read 方法是字節流 InputStream 的。
3.Properties
Properties 是 Hashtable 的子類,具備 map 集合的特點,它里面存儲的 key-value 都是字符串,是集合和 IO 技術相結合的集合容器。
Properties 的特點:可以用于 key-value 形式的配置文件,那么在加載數據時,需要數據有固定格式:key = value。
設置和獲取元素:
Properties prop = new Properties(); // 設置元素 prop.setProperty("C", "100"); // 獲取元素 String value = prop.getProperty("C"); // 遍歷元素 Set<String> names = prop.stringPropertyNames(); for (String s : names) {String value1 = prop.getProperty(s); }將流中的數據存儲到集合中:
Properties prop = new Properties(); FileInputStream fis = new FileInputStream("test.properties"); // key-value數據// 將流中的數據加載進集合 prop.load(fis); String value = prop.getProperty("serverside.log.path"); //prop.list(System.out); // 將信息輸出到文件 fis.close();test.properties 文本內容:
serverside.log.path=/export/Logs/api.example.com serverside.log.level=INFO4.對象序列化
序列化機制:允許把內存中的 Java 對象轉換成字節序列(與平臺無關的二進制流)。
為了讓某個類是可序列化的,該類必須實現 Serializable 接口,Java 很多類已經實現 Serializable(只是一個標記接口,實現該接口無須實現任何方法)。
1、序列化:
使用 ObjectOutputStream 將一個對象寫入磁盤文件:
try (// 創建一個ObjectOutputStream輸出流ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"))) {oos.writeObject(person); // 將person對象寫入輸出流, person對象必須實現 Serializable 接口 } catch (IOException e) {e.printStackTrace(); }2、反序列化:
采用反序列化恢復 Java 對象必須提供該 Java 對象所屬類的 class 文件,否則引發 ClassNotFoundException 異常。
try (// 創建一個ObjectInputStream輸入流ObjectInputStream ois = new ObjectInputStream(new FileInputStream("personObject.txt"))) {// 從輸入流中讀取一個Java對象, 并將其強制類型轉換為Person類Person p = (Person) ois.readObject(); } catch (Exception e) {e.printStackTrace(); }5.管道流
Java IO 中的管道為運行在同一個 JVM 中的兩個線程提供了通信的能力,所以管道也可以作為數據源以及目標媒介。在 Java 中管道流實現了線程間的數據傳送。
注意:當使用兩個相關聯的管道流時,務必將它們分配給不同的線程,read() 和 write() 調用時會導致流阻塞,這意味著如果你嘗試在一個線程中同時進行讀和寫,可能會導致線程死鎖。
讀和寫分配給不同的線程:
class Read implements Runnable {private PipedInputStream in; // 管道字節輸入流Read(PipedInputStream in) {this.in = in;}public void run() {try {byte[] buf = new byte[1024];System.out.println("讀取前..沒有數據阻塞");int len = in.read(buf);System.out.println("讀到數據..阻塞結束");String s = new String(buf,0,len);System.out.println(s);in.close();} catch (IOException e) {throw new RuntimeException("管道讀取流失敗");}} }class Write implements Runnable {private PipedOutputStream out; // 管道字節輸出流Write(PipedOutputStream out) {this.out = out;}public void run() {try {System.out.println("開始寫入數據,等待5秒后..");Thread.sleep(5000);out.write("piped is here".getBytes());out.close();} catch (Exception e) {throw new RuntimeException("管道輸出流失敗");}} }開啟線程:
PipedInputStream in = new PipedInputStream(); // 管道字節輸入流 PipedOutputStream out = new PipedOutputStream(); // 管道字節輸出流 in.connect(out); // 關聯Read r = new Read(in); Write w = new Write(out); // 開啟兩個線程 new Thread(r).start(); new Thread(w).start();運行結果:
讀取前..沒有數據阻塞 開始寫入數據,等待5秒后.. 讀到數據..阻塞結束 piped is here6.RandomAccessFile
RandomAccessFile 是 Java IO 體系中功能最豐富的文件內容訪問類,RandomAccessFile 不是 IO 體系中的子類,而是直接繼承自 Object,但是它是IO包中成員。
RandomAccessFile 提供了眾多的方法來訪問文件內容,既可以讀取文件內容,也可以向文件輸出數據。與普通 IO 流不同的是 RandomAccessFile 支持 “隨機訪問的方式”(內部封裝了一個數組,而且通過指針對數組的元素進行操作,可以通過 getFilePointer 獲取指針位置,同時可以通過 seek 改變指針的位置),程序可以直接跳轉到文件的任意地方來讀寫數據。所以,如果只需要訪問文件部分內容,使用 RandomAccessFile 是更好的選擇。
RandomAccessFile 完成讀寫的原理就是內部封裝了字節輸入流和輸出流。
讀文件:
RandomAccessFile raf = new RandomAccessFile("ran.txt", "r"); //調整對象中指針 //raf.seek(8*1); //跳過指定的字節數 raf.skipBytes(8); byte[] buf = new byte[4]; raf.read(buf);String name = new String(buf); int age = raf.readInt(); raf.close();寫文件:
RandomAccessFile raf = new RandomAccessFile("ran.txt", "rw"); raf.seek(8 * 1); // 移動raf的文件記錄指針的位置 System.out.println("當前指針的位置是:" + raf.getFilePointer()); raf.write("zhangsan".getBytes()); raf.writeInt(97); // 讀用readInt(),97表示ASCII碼,代表字符“a” raf.close();7.NIO和AIO
1、NIO(同步非阻塞):
前面介紹的 BufferedReader 時提到一個特征,當 BufferedReader 讀取輸入流中的數據時,如果沒有讀到有效數據,程序將會在此處阻塞該進程的執行(使用 InputStream 的 read() 從流中讀取數據時,如果數據源沒有數據,它也會阻塞該線程),也就是說前面介紹的輸入流、輸出流都是阻塞式的輸入、輸出。
從 JDK1.4 開始,Java 提供了很多改進 IO 的新功能,稱為 NIO(New IO),新增了許多用于處理輸入/輸出的類,這些類在 java.nio 包及其子包下。
從 Java7 開始,對 NIO 進行了重大改進,改進主要包括提供了全面的文件 IO 和文件系統訪問支持;基于異步 Channel 的 IO。Java7 把這種改進稱為 NIO.2。
2、AIO(異步非阻塞):
從 Java7 開始,Java 增加了 AIO 新特性,基本上所有的 Java 服務器都重寫了自己的網絡框架以通過 NIO 來提高服務器的性能。目前很多的網絡框架(如 Mina),大型軟件(如 Oracle DB)都宣布自己已經在新版本中支持了 AIO 的特性以提高性能。
AIO 的基本原理:
AIO 主要是針對進程在調用 IO 獲取外部數據時,是否阻塞調用進程而言的,一個進程的 IO 調用步驟大致如下:
1)進程向操作系統請求數據;
2)操作系統把外部數據加載到內核的緩沖區中;
3)操作系統把內核的緩沖區拷貝到進程的緩沖區,進程獲得數據完成自己的功能 。
當操作系統在把外部數據放到進程緩沖區的這段時間(即第2、3步),如果應用進程是掛起等待狀態,那么就是同步 IO,反之,就是異步 IO,也就是 AIO。
總結
以上是生活随笔為你收集整理的Java Language——IO 机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ctf ordinary keyboar
- 下一篇: ubuntu18.04 alt+ctrl