IO

from:廖雪峰IO教程

IO

IO是input&output责嚷,以內(nèi)存為中心:
input就是把數(shù)據(jù)讀到內(nèi)存痕支,output就是把內(nèi)存的數(shù)據(jù)輸出

InputStream/OutputStream
IO流以byte為最小單位,因此也稱為字節(jié)流芒澜。例如仰剿,我們要從磁盤中讀取文件,文件包含六個字節(jié)痴晦,就相當于讀入了六個字節(jié)的數(shù)據(jù)

Reader/Writer
讀取的是字符南吮,并且字符不全是單字節(jié)表示的ASCII字符,那么就按照char來讀取更加方便誊酌,這種流稱為字符流

File對象

File f = new File("/usr/bin/javac");

File對象三種形式表示的路徑

1. getPath() //相對路徑
2. getAbsolutePath() //絕對路徑
3. getCanonicalPath() //規(guī)范路徑

文件/目錄部凑、權(quán)限露乏、大小、創(chuàng)建/刪除文件

boolean isFile()
boolean isDirectory()
boolean canRead()
boolean canWrite()
boolean canExecute()
long length()

File f = new File("/usr/bin/javac");
if(file.createNewFile()){
    //創(chuàng)建文件成功
    if(gile.delete()){
        //刪除文件成功
    }
}

遍歷

File[] fileList  = file.listFiles();

創(chuàng)建目錄和路徑

boolean mkdir() 創(chuàng)建當前File對象表示的目錄
boolean mkdirs() 創(chuàng)建當前File對象表示的目錄涂邀,并且在必要時把不存在的父目錄也創(chuàng)建出來
boolean delete()

在文件路徑需要拼接時使用Path會更方便

InputStream

最重要的方法 read()

public abstract int read() throws IOException;

FileInputStream是InputStream的一個子類瘟仿,下面的代碼演示了如何完整的讀取一個FileInputStream的所有字節(jié):

public void readFile throw IOException{
    InputStream in = null;
    try{
        in = new FileInputStream("src/readme.txt");
        int n;
        while(n = in.read() != -1){
            System.out.println(n);
        }
    }finally{
        if(in != null){
            input.close();
        }
    }
    
}

java7 try(resource):
public void readFile throw IOException{
    try(InputStream in = new FileInputStream("src/readme.txt")){
        int n;
        while(n = in.read() != -1){
            System.out.println(n);
        }
    }
}

緩沖

在讀取流的時候,一次性讀取一個字節(jié)并不是高效的方法比勉,使用緩沖區(qū)一次性讀取多個字節(jié)效率往往高很多劳较,InputStream提供了兩個重載方法來支持讀取多個字節(jié)

int read(byte[] b) 讀取若干字節(jié)到byte[]數(shù)組,返回讀取的字節(jié)數(shù)
int read(byte[] b, int off, int len) 指定byte[]數(shù)組的偏移量和最大偏移數(shù)

使用緩沖區(qū)一次性讀取多個字節(jié)的代碼如下:

public void readFile() throw IOException {
    try(InputStream in = new FileInputStream("src/readme.txt")){
        byte[] buffer = new buffer[1000];
        int n;
        while(n = in.read(buffer) != -1){
            System.out.println("read " + n + " bytes.");
        }
    }
}


阻塞

在調(diào)用InputStream 的 read() 方法讀取數(shù)據(jù)時浩聋,我們說read()是阻塞的观蜗,意思是in.read()后面的代碼必須等待read()返回之后才能執(zhí)行

InputStream實現(xiàn)類

ByteArrayInputStream

byte[] data = { 11, 22, 33, 44, 55 };
try(InputStream in = new ByteArrayInputStream(data)){
    ...
}

ByteArrayInputStream 實際是把一個byte[] 在內(nèi)存中變成一個InputStream

OutputStream

最基本方法

public abstract void write(int b) throw IOException

雖然傳入的是int,但是只會寫入8個字節(jié)衣洁,就是int的最低8位

OutputStream的flush()方法墓捻,write()會自動把字節(jié)寫到緩沖區(qū),緩沖區(qū)滿會自動調(diào)用flush()方法坊夫,close()也會調(diào)用flush()方法

特殊情況就是比如IM的對話砖第,使用OutputStream的write()寫入網(wǎng)絡(luò)流,如果不flush()就會等到緩沖區(qū)滿才發(fā)送环凿,所以需要手動調(diào)用flush()方法

public void writeFile() throw IOException {
    byte[] data;
    try(ByteArrayOutputStream out = new ByteArrayOutputStream("out/readme.txt")){
        out.write("Hello".getBytes("UTF-8"));
        data = out.toByteArray();
    }
    System.out.println(data);
}

Filter 模式

Java IO標準庫的InputStream根據(jù)來源可以包括:

FileInputStream 從文件讀取數(shù)據(jù)源
ServletInputStream 從HTTP請求讀取數(shù)據(jù)源
Socket.getInputStream() 從TCP連接讀取數(shù)據(jù)源
...

如果我們要給FileInputStream增加緩沖功能厂画,則可以從FileInputStream派生一個類

BufferedFileInputStream extends FileInputStream 

添加簽名計算簽名可以DigestFileInputStream,添加加密解密可以CipherFileInputStream

如果要增加功能的組合就需要更多子類

為了解決繼承會導(dǎo)致子類太多的問題拷邢,JDK將InputStream分為兩大類

一類是直接提供數(shù)據(jù)的基礎(chǔ)InputStream袱院,如:

  1. InputStream
  2. FileInputStream
  3. ServletInputStream
    另一類是提供額外附加功能的:上面三個
  4. BufferedFileInputStream
  5. DigestFileInputStream
  6. CipherFileInputStream

當我們需要給一個基礎(chǔ)的"InputStream"添加功能時,比如數(shù)據(jù)來自于文件

InputStream in = new FileInputStream("drc/readme.md");

InputStream buffered = new BufferedInputStream(in);

InputStream gzip = new GzipInputStream(buffered);

OutputStream也是這樣提供功能

讀取classpath資源

String conf = "C:\\conf\\default.properties";
try(InputStream in = new FileInputStream(conf)){

}

直接從File路徑中讀取文件必須在C盤的conf文件夾下有一個default.properties的文件
從classpath讀取文件就可以避免不同環(huán)境下文件路徑不一致的問題:如果我們把default.properties放在classpath中就不用關(guān)心他的實際存放路徑
在classpath中的文件瞭稼,路徑總是以/開頭

try(InputStream in = getClass().getResourceAsStream("/default.properties")){
    //調(diào)用classpath很重要的一點是如果資源文件不存在忽洛,它將返回null,因此我們需要檢查返回的InputStream是否為null
    if(in != null){

    }
}

序列化

序列化是指把一個Java對象變成二進制內(nèi)容环肘,本質(zhì)上就是一個字節(jié)數(shù)組
為什么要把對象序列化呢欲虚?因為序列化之后可以吧byte[]保存到文件之中,或者通過網(wǎng)絡(luò)傳輸?shù)竭h程
有了序列化就又反序列化悔雹,即把一個二進制內(nèi)容(也就是byte數(shù)組)變回Java對象复哆。有了反序列化,保存在文件中的byte[]數(shù)組就能重新變成Java對象腌零,或者從網(wǎng)上讀取byte[]數(shù)組然后把他變成Java對象

一個對象要是能序列化梯找,必須實現(xiàn)一個java.io.Serializable接口

Serializable接口沒有定義任何方法,我們把這樣的接口稱為空接口益涧,這樣的接口時標記接口锈锤,實現(xiàn)標記接口僅僅是給自己貼了個標記,沒有增加任何方法

ObjectOutputStream既可以寫入基本類型,也可以寫入String久免,也可以寫入實現(xiàn)了Serializable接口的Object浅辙,因為寫入Object時需要大量的類型信息,所以寫入的內(nèi)容很大

ByteArrayOutputStream buffer = new ByteArrayOutputstream();
try(ObjeactOutputStream out = new OutputStream(buffer)){
    out.writeInt(123);
    out.writeUTF("Hello");
    out.writeObject(Double.valueOf(123.456));
}

反序列化

與序列化 ObjectOutputStream相反阎姥,ObjectInputStream從一個字節(jié)流讀取Java對象
除了能讀取基本類型和String類型之外记舆,調(diào)用readObject()還可以直接返回一個Object對象,要把它變成一個特定類型呼巴,必須強制轉(zhuǎn)型

readObject()可能拋出的異常有:
classNotFoundException: 沒有找到匹配的class氨淌。沒有找到匹配的class。常見于一個程序的Person對象在接收程序未定義
InvalidClassException: Class不匹配伊磺。常見于發(fā)送方的age和接收方的age不是一個類型,一個是int一個是long

為了避免這種class變動導(dǎo)致的不兼容删咱,Java的序列化允許class定義一個特殊的serialVersionUID靜態(tài)變量屑埋,用于標識Java類的序列化版本,通程底蹋可以由ide自動生成摘能,如果增加或者修改了字段就可以改變serialVersionUID字段,這樣就能避免不匹配的class版本

public class Person implements Serializable {
    private static final long serialVersionUID = 2709425275741743919L;
}

反序列化時敲街,由JVM直接構(gòu)造出Java對象团搞,不調(diào)用構(gòu)造方法,構(gòu)造方法內(nèi)部的代碼多艇,在反序列化時根本不可能執(zhí)行

安全性

由于Java的反序列化機制可以導(dǎo)致一個實例能直接從byte[]數(shù)組構(gòu)建逻恐,而不經(jīng)過構(gòu)造方法,因此他存在一定的安全問題峻黍。一個精心構(gòu)造的byte[]反序列化之后能夠執(zhí)行特定的Java代碼复隆,從而導(dǎo)致嚴重的Java漏洞

Reader

Reader是JavaIO庫提供的另一個輸入流接口。是一個字符流姆涩,即以char為單位讀取

public int read() throw IOException

和InputStream的read()方法的區(qū)別在于int返回值的范圍挽拂,InputStream是0-255,Reader是0-65535

public void readFile() throws IOException{
    try(Reader reader = new FileReader("src/readme.md", StandardCharsets.UTF_8)){

    }
}

Reader還提供了一次性讀取若干字符并填充到char[]數(shù)組的方法:

public int read(char[] c) throws IOException

利用這個方法我們可以先設(shè)置一個緩沖區(qū)骨饿。然后每次盡力的填充緩沖區(qū):

public void readFile() throws IOException{
    try(Reader reader = new FileReader("src/readme.md", StandardCharsets.UTF_8)){
        char[] c = new char[10000];
        int n;
        while(n = reader.read(buffer) != -1){
            System.out.println("read" + n + "chars.");
        }
    }
}

InputStreamReader

Reader 和 InputStream 有什么關(guān)系

除了特殊的CharArrayReader和StringReader亏栈,普通的Reader實際上是基于InputStream構(gòu)造的,因為Reader要從InputStream讀取字節(jié)流然后在轉(zhuǎn)化為char就形成了字符流宏赘。
如果我們查看FileReader的源碼绒北,其中持有一個FileInputStream

既然Reader本質(zhì)上是一個基于InputStream的byte到char的轉(zhuǎn)換器,那么如果我們有一個InputStream察署,想把它轉(zhuǎn)化為一個Reader镇饮,是完全可行的。InputStreamReader本質(zhì)上就是這樣一個轉(zhuǎn)換器,他可以吧任何一個InputStream轉(zhuǎn)換為Reader

try(FileReader reader = new InputStreamReader(new InputStream("src/readme.md"), "UTF_8")){

}

上述代碼實際上就是FileReader的一種實現(xiàn)方式

PrintStream

PrintStream擴展了OutputStream接口
PrintStream相比于OutputStream,有兩個變化:

  1. 增加了一組方法:print()/println()
  2. 不會拋出IOException

PrintWriter

PrintWriter擴展了Writer接口储藐,增加了print()/println()方法俱济,最終輸出的是char數(shù)據(jù)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市钙勃,隨后出現(xiàn)的幾起案子蛛碌,更是在濱河造成了極大的恐慌,老刑警劉巖辖源,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蔚携,死亡現(xiàn)場離奇詭異,居然都是意外死亡克饶,警方通過查閱死者的電腦和手機酝蜒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來矾湃,“玉大人亡脑,你說我怎么就攤上這事⊙荆” “怎么了霉咨?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長拍屑。 經(jīng)常有香客問我途戒,道長,這世上最難降的妖魔是什么僵驰? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任喷斋,我火速辦了婚禮,結(jié)果婚禮上蒜茴,老公的妹妹穿的比我還像新娘继准。我一直安慰自己,他們只是感情好矮男,可當我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布移必。 她就那樣靜靜地躺著,像睡著了一般毡鉴。 火紅的嫁衣襯著肌膚如雪崔泵。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天猪瞬,我揣著相機與錄音憎瘸,去河邊找鬼。 笑死陈瘦,一個胖子當著我的面吹牛幌甘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼锅风,長吁一口氣:“原來是場噩夢啊……” “哼酥诽!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起皱埠,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤肮帐,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后边器,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體训枢,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年忘巧,在試婚紗的時候發(fā)現(xiàn)自己被綠了恒界。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡砚嘴,死狀恐怖十酣,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情枣宫,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布吃环,位于F島的核電站也颤,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏郁轻。R本人自食惡果不足惜翅娶,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望好唯。 院中可真熱鬧竭沫,春花似錦、人聲如沸骑篙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽靶端。三九已至谎势,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間杨名,已是汗流浹背脏榆。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留台谍,地道東北人须喂。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親坞生。 傳聞我的和親對象是個殘疾皇子仔役,可洞房花燭夜當晚...
    茶點故事閱讀 44,724評論 2 354