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