一 I/O記憶體緩衝區
使用者空間:常規進程所在地區,JVM就是常規進程,該地區執行的代碼不能直接存取硬體裝置
核心空間:作業系統所在地區。核心代碼它能與裝置控制器通訊,控制著使用者地區進程的運行狀態,等等。最重要的是,所有的I/O 都直接或間接通過核心空間
資料互動:當使用者(java)進程進行I/O操作的時候,它會執行一個系統調用將控制權移交給核心,核心代碼負責找到請求的資料,並將資料傳送到使用者空間內的指定緩衝區
核心空間緩衝區:核心代碼讀寫資料要通過磁碟的I/O操作,由於磁碟I/O操作的速度比直接存取記憶體慢了好幾個數量級,所以核心代碼會對資料進行快取或預讀取到核心空間的緩衝區(減少磁碟I/O,提高效能)
使用者空間緩衝區:同上,java I/O進程通過系統調用讀寫資料的速度,要比直接存取虛擬機器記憶體慢好幾個數量級,所以可以執行一次系統調用時,預讀取大量資料,緩衝在虛擬機器記憶體中。
二 輸入資料流InputStream、輸出資料流OutputStream
FileInputStream類實現了InputStream的讀取單個位元組read()、讀取位元組數組read(byte b[], int off, int len)的方法。FileOutputStream類實現了OutputStream的寫入單個位元組write(int b)、寫入位元組數組write(byte b[], int off, int len)的方法(後面對Out介紹略過,參考In)
/** * 檔案輸出資料流構造器 * name 路徑名 * append 參數為true,資料將被添加到檔案末尾, * 而具有相同名字的已有檔案不會被刪除,否則這個方法刪除所有具有相同名字的檔案 */ public FileOutputStream(String name, boolean append) throws FileNotFoundException { this(name != null ? new File(name) : null, append); }
因為InputStream的實作類別,只能讀取位元組,而且每次read都會執行一次系統調用,所以對其實作類別進行擴充是必需的,過濾器FilterInputStream繼承自InputStream,是一些擴充類(DataInputStream、BufferedInputStream)的基類。
public class FilterInputStream extends InputStream {protected volatile InputStream in;/** * 構造方法受保護的,只能通過子類調用(初始化) * 參數是InputStream的實作類別,例:FileInputStream */ protected FilterInputStream(InputStream in) { this.in = in; }
read()方法每次都會發起一次系統調用,而系統調用的代價是非常高的,所以為了減少系統調用的次數,就需要通過裝飾器BufferedInputStream一次read()大量位元組緩衝在位元組數組中。這裡的緩衝並不是為了減少磁碟IO操作次數,因為這個作業系統已經幫我們做了
// 預設緩衝8192位元組=8kb,可以通過參數進行設定InputStream in = new BufferedInputStream(new FileInputStream("路徑"), 8192);// 每次write()的位元組,都會緩衝到長度8912的位元組數組中,直到寫滿才進行系統調用一次性寫入OutputStream out = new BufferedOutputStream(new FileOutputStream("路徑"), 8192);// 同理BufferedReader、BufferedWriter也是一樣
資料在讀取和寫入時都是位元組狀態,適配器DataInputStream 實現了DataInput介面可以將讀取的位元組,在後台轉換成java的基本類型然後進行讀取,例:readInt每次讀取4個位元組
/** * DataInputStream類方法 * 轉換成int類型,int4位元組組成,一個位元組8位 * 高位在前,進行數值還原 */ public final int readInt() throws IOException { int ch1 = in.read(); int ch2 = in.read(); int ch3 = in.read(); int ch4 = in.read(); if ((ch1 | ch2 | ch3 | ch4) < 0) throw new EOFException(); return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0)); }
DataOutputStream、DataInputStream兩者一般結合使用,如果DataOutputStream 寫入資料,java保證我們可以使用DataInputStream準確的讀取資料。還有writeUTF()和readUTF()方法,可以對字串進行寫入、讀取。 用到這兩個類的來讀寫檔案的資料格式一般比較固定
DataInputStream buff = new DataInputStream( new BufferedInputStream( new FileInputStream("路徑")));
read和write方法在執行時將阻塞,直到位元組被讀入或者寫出。不過通常是因為網路繁忙。available()方法“在沒有阻塞的情況下所能讀取的位元組數”,即當前可讀取入的位元組數量,那麼下面這樣就不會 阻塞
// 對於檔案,這意味著整個檔案都可讀取int bytesAvailable = in.available();if (bytesAvailable > 0) {byte[] bytes = new byte[bytesAvailable];in.read(bytes);}
輸入資料流InputStream、輸出資料流OutputStram的幾個方法
public static void main(String[] args) throws IOException {// ByteArrayInputStream 可以將一個位元組數組轉成輸入資料流InputStream in = new ByteArrayInputStream("abcdefghi".getBytes());// 判斷是否支援標記功能in.markSupported(); // true// 跳過2個位元組in.skip(2);/** * 在當前位置打標記,非所有流都支援(這個流支援) * 如果從輸入資料流中已經讀的位元組數大於5個,則這個流允許忽略這個標記(這個流不支援) */in.mark(5);// 跳過2個位元組in.skip(2);System.out.println((char) in.read());// 輸出:e// 返回到最後一個標記,隨後read將重新讀入這些位元組。如果沒有標記不被重設in.reset();System.out.println((char) in.read());// 輸出:c// 超過5個位元組被讀取in.skip(4);// mark不失效in.reset();System.out.println((char) in.read());// 輸出:cbyte[] b = new byte[10];// 向b中塞4個位元組,從b[5]開始in.read(b, 5, 4);// 關閉流in.close();// OutputStramOutputStream out = new ByteArrayOutputStream();// 沖刷輸出資料流,也就是將所有緩衝的資料送到目的地out.flush();// 沖刷並關閉流out.close();}
三 字元輸入資料流Reader、字元輸出資料流Writer
不管磁碟還是網路傳輸,最小的儲存單位都是位元組,所以抽象類別InputStream和OutputStream構成了輸入/輸出(I/O)類層次的基礎。
基礎的輸出輸入資料流僅支援8位的位元組流,並且不能很好的處理16為的Unicode字元,由於Unicode用於字元國際化(java本身的char也是16的Unicode,即包含兩個位元組), 所以適配器InputStreamReader、OutputStreamWriter出現了,用於位元組和字元之間的 轉換。
// ByteArrayInputStream 可以將一個位元組數組轉成輸入資料流InputStream in = new ByteArrayInputStream("你好".getBytes());// 先轉換成字元,然後用BufferedReader進行緩衝BufferedReader br = new BufferedReader(new InputStreamReader(in));
字元就是大家都能看懂的文字(包括其它國家文字)、符號之類的,比如txt儲存的都是字元。無法轉換成字元的如圖片MP3
以字元格式設定寫出資料,可以使用PrintWriter,還可以設定是否每次寫入都沖刷輸出資料流。print/println方法可以寫入字元和java的基本類型
/** * 其中的一個私人構造器方法 * @param charset 以選定的字元編碼,將Unicode字元轉換成位元組進行儲存 * @param file 檔案路徑 * @throws FileNotFoundException */ private PrintWriter(Charset charset, File file) throws FileNotFoundException { this(new BufferedWriter(//對字元緩衝 new OutputStreamWriter( new FileOutputStream(file), charset)),false); }
以字元格式設定讀取資料,可以使用BufferedReader,有一個readLine()方法,每次讀取一行。沒有按java基本類型讀取資料功能。下面是將位元組按照GBK字元集格式轉換成字元。
BufferedReader buff = new BufferedReader( new InputStreamReader( new FileInputStream(path), "GBK"));
java.util.Scanner類可以按行讀取資料,也可以按java基本類型讀取資料。
RandomAccessFile類可以在檔案中任何位置尋找和寫入資料,適合大小已知的記錄組成的檔案。類中有一個表示下一個讀入或寫出的位元組所處的位置,用seek()方法,可以設定指標在檔案中的任意位置。其實現了DataOutput, DataInput介面,可以對 java的基本類型進行讀寫。在使用該類時,一般要知道檔案的排版。還有其大多功能都可以用nio取代
//只能進行讀入訪問RandomAccessFile in = new RandomAccessFile(path, "r");//可以進行讀入、寫出訪問RandomAccessFile inOut = new RandomAccessFile(path, "rw");
四 ZipInputStream和ZipOutputStream
//壓縮:寫出到ZIP檔案ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream("壓縮檔路徑.zip"));//壓縮檔中的每一項都是一個ZipEntry對象ZipEntry entry = new ZipEntry("檔案名稱字");//將ZipEntry添加到壓縮檔中zipOut.putNextEntry(entry);//可以通過緩衝流寫入資料BufferedOutputStream outbuf = new BufferedOutputStream(zipOut);//還可以繼續嵌套DataOutputStream dou = new DataOutputStream(outbuf);//解壓:將檔案從壓縮檔中讀出ZipInputStream zipIn = new ZipInputStream(new FileInputStream("壓縮檔路徑.zip"));//返回下一個ZipEntry對象,沒有更多項返回nullzipIn.getNextEntry();//可以通過緩衝流讀取資料BufferedInputStream inbuf = new BufferedInputStream(zipIn);//還可以繼續嵌套DataInputStream din = new DataInputStream(inbuf);
ZipInputStream不能一次解壓整個壓縮檔,只能一次一個ZipEntry 的解壓。
ZipOutputStream不能一次壓縮一個檔案夾,只能一次一個ZipEntry 的壓縮。
ZipInputStream的read方法再碰到當前ZipEntry結尾時返回-1,然後必須調用closeEntry來讀入下一個ZipEntry 。寫入時同理。
五 對象序列化
java的對象序列化將那些實現了Serializable介面的對象轉換成一個位元組序列,並可以將轉換的位元組序列恢複為原來的對象,這種機制可以彌補不同作業系統之間的差異,可以在windows上序列化傳到unix恢複
java的遠程方法調用RMI(Remote Mthod Invocation)就是通過序列化實現的,當遠程對象發送資訊時,需要通過對象序列化來傳輸參數和傳回值
對象序列化不緊可以儲存對象的全景,還能追蹤對象內所包含的全部引用,並儲存那些對象
序列化時每個對象的引用都會關聯一個序號,相同對象的重複序列化,將被儲存為對這個對象序號的引用。還原序列化時過程相反。
在還原序列化一個對象時,會拿其序號與它所屬類的當前序號進行對比,不匹配,說明這個類在序列化後發生過變化,這涉及“版本管理”就不概述了
public static void main(String[] args) throws IOException, ClassNotFoundException {// 序列化ByteArrayOutputStream out = new ByteArrayOutputStream();// 創一個ObjectOutputStream 寫出到指定的OutputStreamObjectOutputStream oos = new ObjectOutputStream(out);// 這個方法將儲存指定對象的類、類的簽名,以及這個類及其超類中所有非靜態和非transient(瞬時)修飾的域的值oos.writeObject("實現Serializable介面的對象");oos.writeObject("可以序列化多個對象");// 還原序列化byte[] bs = out.toByteArray();InputStream is = new ByteArrayInputStream(bs);// 建立一個ObjectInputStream 用於從指定的InputStream讀回對象資訊ObjectInputStream ois = new ObjectInputStream(is);// 返回對象的類、簽名、以及這個類及其超類中所有非靜態和非瞬時域的值String str1 = (String) ois.readObject();String str2 = (String) ois.readObject();System.out.println(str1 + str2);}
實現Externalizable介面,它繼承自Serializable。在其序列化時會調用writeExternal方法,還原序列化時通過調用無參構造器,然後調用readExternal進行對象的初始化。
public class Test implements Externalizable {private String str;private int i;// 還原序列化時調用public Test() {System.out.println("無參構造器");}public Test(String str, int i) {System.out.println("構造器");this.str = str;this.i = i;}@Overridepublic String toString() {return str + i;}// 序列化時調用@Overridepublic void writeExternal(ObjectOutput out) throws IOException {out.writeObject("writeExternal-");out.writeInt(1);}// 還原序列化時調用@Overridepublic void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {this.str = (String) in.readObject();this.i = in.readInt();}public static void main(String[] args) throws IOException, ClassNotFoundException {// 初始化Test test = new Test("test", 0);// 序列化ByteArrayOutputStream outputStream = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(outputStream);oos.writeObject(test);oos.close();// 還原序列化ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());ObjectInputStream ois = new ObjectInputStream(inputStream);Test te = (Test) ois.readObject();System.out.println(te);inputStream.close();ois.close();}}