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