I/O作為作為人機(jī)交互的核心問(wèn)題建峭,很多web應(yīng)用系統(tǒng)的瓶頸都是I/O瓶頸。本文主要總結(jié)java的I/O類(lèi)庫(kù)基本架構(gòu)以及磁盤(pán)I/O、網(wǎng)絡(luò)I/O工作機(jī)制裂明。
1. 基礎(chǔ)概念
編程語(yǔ)言I/O類(lèi)庫(kù)常使用流這個(gè)抽象概念,流是一個(gè)數(shù)據(jù)序列太援,有順序的闽晦,有起點(diǎn)和終點(diǎn)的字節(jié)集合,是對(duì)數(shù)據(jù)傳輸?shù)目偝苫虺橄筇岵怼仙蛉!傲鳌逼帘瘟藢?shí)際I/O設(shè)備中處理數(shù)據(jù)的細(xì)節(jié)。
流可不同維度進(jìn)行分類(lèi):
按數(shù)據(jù)傳輸單位:
字節(jié)流:每次讀到一個(gè)字節(jié)就返回一個(gè)字節(jié)碱蒙,可以處理所有類(lèi)型的數(shù)據(jù)對(duì)應(yīng)基類(lèi)InputStream和OutputStream
字符流:每次讀到一個(gè)字符(1個(gè)或多個(gè)字節(jié):中文對(duì)應(yīng)的字節(jié)數(shù)是兩個(gè)荠瘪,在UTF-8碼表中是3個(gè)字節(jié)),先去查指定的編碼表赛惩,將查到的字符返回哀墓。字符流是對(duì)字節(jié)流進(jìn)行了封裝,方便操作喷兼。在底層篮绰,所有的輸入輸出都是字節(jié)形式的,對(duì)應(yīng)基類(lèi)Write和Reader季惯。
按數(shù)據(jù)處理方向:
輸入流:只能從中讀取數(shù)據(jù)吠各,由InputStream(字節(jié)流)和Reader(字符流)作為基類(lèi)
輸出流:只能向其寫(xiě)入數(shù)據(jù),由OutputStream(字節(jié)流)和Writer(字符流)作為基類(lèi)
2. java I/O類(lèi)庫(kù)
功能劃分:
- File關(guān)聯(lián)類(lèi):FileReader, FileWriter, FileInputStream and FileOutputStream.
- Array關(guān)聯(lián)類(lèi):ByteArrayInputStream, ByteArrayOutputStream / CharArrayReader, CharArrayWriter.
- String關(guān)聯(lián)類(lèi):StringReader and StringWriter
- Buffer關(guān)聯(lián)類(lèi):BufferedInputStream, BufferedOutputStream, BufferedReader, BufferedWriter
- 字節(jié)流和字符流轉(zhuǎn)換橋梁:**InputStreamReader ** 和 OutputStreamWriter
IO流操作的基本步驟:
- open(創(chuàng)建一個(gè)輸入或輸出流關(guān)聯(lián)具體設(shè)備(網(wǎng)絡(luò)星瘾、磁盤(pán)等))
- read/write(從輸入流讀取數(shù)據(jù)或像輸出流寫(xiě)入數(shù)據(jù))
- close(關(guān)閉輸入或輸出流)
磁盤(pán)IO工作機(jī)制示意圖:
上述流程示例代碼
String src = "/Users/chenchen/code/java/Person.txt";
String dest = "/Users/chenchen/code/java/file.txt";
BufferedInputStream in = null;
BufferedOutputStream out = null;
try {
in = new BufferedInputStream(new FileInputStream(src));
out = new BufferedOutputStream(new FileOutputStream(dest));
byte[] files = new byte[1024];
int tmp = 0;
while ((tmp = in.read(files)) != -1){
out.write(files, 0, tmp);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
in.close();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
建議所有磁盤(pán)相關(guān)IO操作使用Buffer
磁盤(pán)設(shè)備由操作系統(tǒng)管理走孽,所以磁盤(pán)相關(guān)讀取和寫(xiě)入IO操作都是調(diào)用操作系統(tǒng)提供的接口。系統(tǒng)調(diào)用就可能存在內(nèi)核空間地址和用戶(hù)空間地址切換的問(wèn)題琳状,這個(gè)操作在保證安全性同時(shí)導(dǎo)致多余的耗時(shí)磕瓷。讀寫(xiě)單個(gè)byte就顯得很耗費(fèi)時(shí)間,IO類(lèi)庫(kù)提供了Buffer相關(guān)的類(lèi)來(lái)加速I(mǎi)O操作。
3. 序列化
java序列化就是將一個(gè)對(duì)象轉(zhuǎn)換成一串二進(jìn)制表示的字節(jié)數(shù)組困食,通過(guò)轉(zhuǎn)移或者保存這些數(shù)據(jù)達(dá)到持久化的目的边翁。反序列化必須有該類(lèi)的原始模板,才能達(dá)到還原該對(duì)象的目的硕盹。需要進(jìn)行序列化的對(duì)象只需要實(shí)現(xiàn)Seriaizable接口
3.1 什么時(shí)候需要序列化
- 當(dāng)你想把的內(nèi)存中的對(duì)象保存到一個(gè)文件中或者數(shù)據(jù)庫(kù)中時(shí)候符匾;
- 當(dāng)你想用套接字在網(wǎng)絡(luò)上傳送對(duì)象的時(shí)候;
- 當(dāng)你想通過(guò)RMI傳輸對(duì)象的時(shí)候瘩例;
3.2 關(guān)于java序列化幾點(diǎn)說(shuō)明
- 當(dāng)一個(gè)父類(lèi)實(shí)現(xiàn)序列化啊胶,子類(lèi)自動(dòng)實(shí)現(xiàn)序列化,不需要顯式實(shí)現(xiàn)Serializable接口垛贤;
- 如果序列化的屬性是對(duì)象焰坪,則這個(gè)對(duì)象也必須實(shí)現(xiàn)Serializable接口,否則會(huì)報(bào)錯(cuò)聘惦;
- static,transient后的變量不能被序列化某饰;
- 反序列化過(guò)程中,如果對(duì)象的屬性有修改或刪減善绎,相應(yīng)屬性的值會(huì)丟失黔漂。
serialVersionUID說(shuō)明
如果沒(méi)有明確指定serialVersionUID,序列化的時(shí)候會(huì)根據(jù)字段和特定的算法生成一個(gè)serialVersionUID禀酱,當(dāng)屬性有變化時(shí)這個(gè)id發(fā)生了變化炬守,所以反序列化的時(shí)候就會(huì)失敗。拋出“本地classd的唯一id和流中class的唯一id不匹配”剂跟。
強(qiáng)烈建議 所有可序列化類(lèi)都顯式聲明 serialVersionUID 值劳较,原因是計(jì)算默認(rèn)的 serialVersionUID 對(duì)類(lèi)的詳細(xì)信息具有較高的敏感性,根據(jù)編譯器實(shí)現(xiàn)的不同可能千差萬(wàn)別浩聋,這樣在反序列化過(guò)程中可能會(huì)導(dǎo)致意外的 InvalidClassException。
同時(shí)強(qiáng)烈建議使用 private 修飾符顯示聲明 serialVersionUID(如果可能)臊恋,原因是這種聲明僅應(yīng)用于直接聲明類(lèi) -- serialVersionUID 字段作為繼承成員沒(méi)有用處
3.3 java序列化對(duì)應(yīng)IO類(lèi)
java.io.ObjectOutputStream:表示對(duì)象輸出流它的writeObject(Object obj)方法可以對(duì)參數(shù)指定的obj對(duì)象進(jìn)行序列化衣洁,把得到的字節(jié)序列寫(xiě)到一個(gè)目標(biāo)輸出流中。
java.io.ObjectInputStream:表示對(duì)象輸入流它的readObject()方法源輸入流中讀取字節(jié)序列抖仅,再把它們反序列化成為一個(gè)對(duì)象坊夫,并將其返回。
示例代碼如下:
TestModel testModel = new TestModel();
Person person = new Person();
person.setName("cc");
person.setAge(12);
testModel.setPerson(person);
testModel.setId(1);
ObjectOutputStream out = null;
ObjectInputStream in = null;
try {
// 序列化
out = new ObjectOutputStream(new FileOutputStream("test.txt"));
out.writeObject(testModel);
// 反序列化
in = new ObjectInputStream(new FileInputStream("test.txt"));
TestModel result = (TestModel)in.readObject();
}catch (Exception e){
e.printStackTrace();
} finally {
try {
out.close();
in.close();
} catch (IOException e) {
}
}
示例中TestModel實(shí)現(xiàn)了Serializable接口撤卢,同時(shí)其引用屬性Person也要實(shí)現(xiàn)Serializable接口否則會(huì)報(bào)錯(cuò)环凿。
參考文檔
https://www.ntu.edu.sg/home/ehchua/programming/java/J5b_IO.html
http://www.devinline.com/2015/10/internal-details-of-java-IO-classes.html