更新時(shí)間:2022-10-26 10:32:20 來(lái)源:動(dòng)力節(jié)點(diǎn) 瀏覽1154次
InputStream就是Java標(biāo)準(zhǔn)庫(kù)提供的最基本的輸入流。它位于java.io這個(gè)包里。java.io包提供了所有同步IO的功能。
要特別注意的一點(diǎn)是,InputStream并不是一個(gè)接口,而是一個(gè)Java抽象類,它是所有輸入流的超類。這個(gè)抽象類定義的一個(gè)最重要的方法就是int read(),簽名如下:
public abstract int read() throws IOException;
這個(gè)方法會(huì)讀取輸入流的下一個(gè)字節(jié),并返回字節(jié)表示的int值(0~255)。如果已讀到末尾,返回-1表示不能繼續(xù)讀取了。
FileInputStream是InputStream的一個(gè)子類。顧名思義,F(xiàn)ileInputStream就是從文件流中讀取數(shù)據(jù)。下面的代碼演示了如何完整地讀取一個(gè)FileInputStream的所有字節(jié):
public void readFile() throws IOException {
// 創(chuàng)建一個(gè)FileInputStream對(duì)象:
InputStream input = new FileInputStream("src/readme.txt");
for (;;) {
int n = input.read(); // 反復(fù)調(diào)用read()方法,直到返回-1
if (n == -1) {
break;
}
System.out.println(n); // 打印byte的值
}
input.close(); // 關(guān)閉流
}
在計(jì)算機(jī)中,類似文件、網(wǎng)絡(luò)端口這些資源,都是由操作系統(tǒng)統(tǒng)一管理的。應(yīng)用程序在運(yùn)行的過(guò)程中,如果打開(kāi)了一個(gè)文件進(jìn)行讀寫,完成后要及時(shí)地關(guān)閉,以便讓操作系統(tǒng)把資源釋放掉,否則,應(yīng)用程序占用的資源會(huì)越來(lái)越多,不但白白占用內(nèi)存,還會(huì)影響其他應(yīng)用程序的運(yùn)行。
InputStream和OutputStream都是通過(guò)close()方法來(lái)關(guān)閉流。關(guān)閉流就會(huì)釋放對(duì)應(yīng)的底層資源。
我們還要注意到在讀取或?qū)懭隝O流的過(guò)程中,可能會(huì)發(fā)生錯(cuò)誤,例如,文件不存在導(dǎo)致無(wú)法讀取,沒(méi)有寫權(quán)限導(dǎo)致寫入失敗,等等,這些底層錯(cuò)誤由Java虛擬機(jī)自動(dòng)封裝成IOException異常并拋出。因此,所有與IO操作相關(guān)的代碼都必須正確處理IOException。
仔細(xì)觀察上面的代碼,會(huì)發(fā)現(xiàn)一個(gè)潛在的問(wèn)題:如果讀取過(guò)程中發(fā)生了IO錯(cuò)誤,InputStream就沒(méi)法正確地關(guān)閉,資源也就沒(méi)法及時(shí)釋放。
因此,我們需要用try ... finally來(lái)保證InputStream在無(wú)論是否發(fā)生IO錯(cuò)誤的時(shí)候都能夠正確地關(guān)閉:
public void readFile() throws IOException {
InputStream input = null;
try {
input = new FileInputStream("src/readme.txt");
int n;
while ((n = input.read()) != -1) { // 利用while同時(shí)讀取并判斷
System.out.println(n);
}
} finally {
if (input != null) { input.close(); }
}
}
用try ... finally來(lái)編寫上述代碼會(huì)感覺(jué)比較復(fù)雜,更好的寫法是利用Java 7引入的新的try(resource)的語(yǔ)法,只需要編寫try語(yǔ)句,讓編譯器自動(dòng)為我們關(guān)閉資源。推薦的寫法如下:
public void readFile() throws IOException {
try (InputStream input = new FileInputStream("src/readme.txt")) {
int n;
while ((n = input.read()) != -1) {
System.out.println(n);
}
} // 編譯器在此自動(dòng)為我們寫入finally并調(diào)用close()
}
實(shí)際上,編譯器并不會(huì)特別地為InputStream加上自動(dòng)關(guān)閉。編譯器只看try(resource = ...)中的對(duì)象是否實(shí)現(xiàn)了java.lang.AutoCloseable接口,如果實(shí)現(xiàn)了,就自動(dòng)加上finally語(yǔ)句并調(diào)用close()方法。InputStream和OutputStream都實(shí)現(xiàn)了這個(gè)接口,因此,都可以用在try(resource)中。
在讀取流的時(shí)候,一次讀取一個(gè)字節(jié)并不是最高效的方法。很多流支持一次性讀取多個(gè)字節(jié)到緩沖區(qū),對(duì)于文件和網(wǎng)絡(luò)流來(lái)說(shuō),利用緩沖區(qū)一次性讀取多個(gè)字節(jié)效率往往要高很多。InputStream提供了兩個(gè)重載方法來(lái)支持讀取多個(gè)字節(jié):
int read(byte[] b):讀取若干字節(jié)并填充到byte[]數(shù)組,返回讀取的字節(jié)數(shù)
int read(byte[] b, int off, int len):指定byte[]數(shù)組的偏移量和最大填充數(shù)
利用上述方法一次讀取多個(gè)字節(jié)時(shí),需要先定義一個(gè)byte[]數(shù)組作為緩沖區(qū),read()方法會(huì)盡可能多地讀取字節(jié)到緩沖區(qū), 但不會(huì)超過(guò)緩沖區(qū)的大小。read()方法的返回值不再是字節(jié)的int值,而是返回實(shí)際讀取了多少個(gè)字節(jié)。如果返回-1,表示沒(méi)有更多的數(shù)據(jù)了。
利用緩沖區(qū)一次讀取多個(gè)字節(jié)的代碼如下:
public void readFile() throws IOException {
try (InputStream input = new FileInputStream("src/readme.txt")) {
// 定義1000個(gè)字節(jié)大小的緩沖區(qū):
byte[] buffer = new byte[1000];
int n;
while ((n = input.read(buffer)) != -1) { // 讀取到緩沖區(qū)
System.out.println("read " + n + " bytes.");
}
}
}
在調(diào)用InputStream的read()方法讀取數(shù)據(jù)時(shí),我們說(shuō)read()方法是阻塞(Blocking)的。它的意思是,對(duì)于下面的代碼:
int n;
n = input.read(); // 必須等待read()方法返回才能執(zhí)行下一行代碼
int m = n;
執(zhí)行到第二行代碼時(shí),必須等read()方法返回后才能繼續(xù)。因?yàn)樽x取IO流相比執(zhí)行普通代碼,速度會(huì)慢很多,因此,無(wú)法確定read()方法調(diào)用到底要花費(fèi)多長(zhǎng)時(shí)間。
用FileInputStream可以從文件獲取輸入流,這是InputStream常用的一個(gè)實(shí)現(xiàn)類。此外,ByteArrayInputStream可以在內(nèi)存中模擬一個(gè)InputStream:
public class Main {
public static void main(String[] args) throws IOException {
byte[] data = { 72, 101, 108, 108, 111, 33 };
try (InputStream input = new ByteArrayInputStream(data)) {
int n;
while ((n = input.read()) != -1) {
System.out.println((char)n);
}
}
}
}
ByteArrayInputStream實(shí)際上是把一個(gè)byte[]數(shù)組在內(nèi)存中變成一個(gè)InputStream,雖然實(shí)際應(yīng)用不多,但測(cè)試的時(shí)候,可以用它來(lái)構(gòu)造一個(gè)InputStream。
舉個(gè)栗子:我們想從文件中讀取所有字節(jié),并轉(zhuǎn)換成char然后拼成一個(gè)字符串,可以這么寫:
public class Main {
public static void main(String[] args) throws IOException {
String s;
try (InputStream input = new FileInputStream("C:\\test\\README.txt")) {
int n;
StringBuilder sb = new StringBuilder();
while ((n = input.read()) != -1) {
sb.append((char) n);
}
s = sb.toString();
}
System.out.println(s);
}
}
要測(cè)試上面的程序,就真的需要在本地硬盤上放一個(gè)真實(shí)的文本文件。如果我們把代碼稍微改造一下,提取一個(gè)readAsString()的方法:
public class Main {
public static void main(String[] args) throws IOException {
String s;
try (InputStream input = new FileInputStream("C:\\test\\README.txt")) {
s = readAsString(input);
}
System.out.println(s);
}
public static String readAsString(InputStream input) throws IOException {
int n;
StringBuilder sb = new StringBuilder();
while ((n = input.read()) != -1) {
sb.append((char) n);
}
return sb.toString();
}
}
對(duì)這個(gè)String readAsString(InputStream input)方法進(jìn)行測(cè)試就相當(dāng)簡(jiǎn)單,因?yàn)椴灰欢ㄒ獋魅胍粋€(gè)真的FileInputStream:
public class Main {
public static void main(String[] args) throws IOException {
byte[] data = { 72, 101, 108, 108, 111, 33 };
try (InputStream input = new ByteArrayInputStream(data)) {
String s = readAsString(input);
System.out.println(s);
}
}
public static String readAsString(InputStream input) throws IOException {
int n;
StringBuilder sb = new StringBuilder();
while ((n = input.read()) != -1) {
sb.append((char) n);
}
return sb.toString();
}
}
這就是面向抽象編程原則的應(yīng)用:接受InputStream抽象類型,而不是具體的FileInputStream類型,從而使得代碼可以處理InputStream的任意實(shí)現(xiàn)類。如果大家想了解更多相關(guān)知識(shí),可以關(guān)注一下本站的Java在線學(xué)習(xí),里面的課程內(nèi)容由淺到深,細(xì)致全面,很適合沒(méi)有基礎(chǔ)的小伙伴學(xué)習(xí),希望對(duì)大家能夠有所幫助哦。
相關(guān)閱讀
0基礎(chǔ) 0學(xué)費(fèi) 15天面授
有基礎(chǔ) 直達(dá)就業(yè)
業(yè)余時(shí)間 高薪轉(zhuǎn)行
工作1~3年,加薪神器
工作3~5年,晉升架構(gòu)
提交申請(qǐng)后,顧問(wèn)老師會(huì)電話與您溝通安排學(xué)習(xí)