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袱院,如:
- InputStream
- FileInputStream
- ServletInputStream
另一類是提供額外附加功能的:上面三個 - BufferedFileInputStream
- DigestFileInputStream
- 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,有兩個變化:
- 增加了一組方法:print()/println()
- 不會拋出IOException
PrintWriter
PrintWriter擴展了Writer接口储藐,增加了print()/println()方法俱济,最終輸出的是char數(shù)據(jù)