Java使用FileInputStream流讀取檔案示例詳解
一、File流概念
JAVA中針對檔案的讀寫操作設定了一系列的流,其中主要有FileInputStream,FileOutputStream,FileReader,FileWriter四種最為常用的流
二、FileInputStream
1)FileInputStream概念
FileInputStream流被稱為檔案位元組輸入流,意思指對檔案資料以位元組的形式進行讀取操作如讀取圖片視訊等
2)構造方法
2.1)通過開啟與File類物件代表的實際檔案的連結來建立FileInputStream流物件
public FileInputStream(File file) throws FileNotFoundException{}
若File類物件的所代表的檔案不存在;不是檔案是目錄;或者其他原因不能開啟的話,則會丟擲FileNotFoundException
/** * * 執行會產生異常並被撲捉--因為不存在xxxxxxxx這樣的檔案 */ public static void main(String[] args) { File file=new File("xxxxxxxx"); //根據路徑建立File類物件--這裡路徑即使錯誤也不會報錯,因為只是產生File物件,還並未與計算機檔案讀寫有關聯 try { FileInputStream fileInputStream=new FileInputStream(file);//與根據File類物件的所代表的實際檔案建立連結建立fileInputStream物件 } catch (FileNotFoundException e) { System.out.println("檔案不存在或者檔案不可讀或者檔案是目錄"); } }
2.2)通過指定的字串引數來建立File類物件,而後再與File物件所代表的實際路徑建立連結建立FileInputStream流物件
public FileInputStream(String name) throws FileNotFoundException
通過檢視原始碼,發現該構造方法等於是在第一個構造方法的基礎上進行延伸的,因此規則也和第一個構造方法一致
public FileInputStream(String name) throws FileNotFoundException { this(name != null ? new File(name) : null); }
2.3)該構造方法沒有理解---檢視api是指使用的fdObj檔案描述符來作為引數,檔案描述符是指與計算機系統中的檔案的連線,前面兩個方法的原始碼中最後都是利用檔案描述符來建立連線的
public FileInputStream(FileDescriptor fdObj)
3)FileInputStream常用API
3.1)從輸入流中讀取一個位元組返回int型變數,若到達檔案末尾,則返回-1
public int read() throws IOException
理解讀取的位元組為什麼返回int型變數
1、方法解釋中的-1相當於是資料字典告訴呼叫者檔案已到底,可以結束讀取了,這裡的-1是Int型
2、那麼當檔案未到底時,我們讀取的是位元組,若返回byte型別,那麼勢必造成同一方法返回型別不同的情況這是不允許的
3、我們讀取的位元組實際是由8位二進位制組成,二進位制檔案不利於直觀檢視,可以轉成常用的十進位制進行展示,因此需要把讀取的位元組從二進位制轉成十進位制整數,故返回int型
4、 因此結合以上3點,保證返回型別一致以及直觀檢視的情況,因此該方法雖然讀取的是位元組但返回int型
read方法讀取例項--最後輸出內容和字元內容一致是123
package com.test; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; public class FileStream { /** * * */ public static void main(String[] args) { //建立檔案物件 File file=new File("C:\\Users\\Administrator\\Desktop\\1.txt"); try { //建立連結 FileInputStream fileInputStream=new FileInputStream(file); int n=0; StringBuffer sBuffer=new StringBuffer(); while (n!=-1) //當n不等於-1,則代表未到末尾 { n=fileInputStream.read();//讀取檔案的一個位元組(8個二進位制位),並將其由二進位制轉成十進位制的整數返回 char by=(char) n; //轉成字元 sBuffer.append(by); } System.out.println(sBuffer.toString()); } catch (FileNotFoundException e) { System.out.println("檔案不存在或者檔案不可讀或者檔案是目錄"); } catch (IOException e) { System.out.println("讀取過程存在異常"); } } }
3.2)從輸入流中讀取b.length個位元組到位元組陣列中,返回讀入緩衝區的總位元組數,若到達檔案末尾,則返回-1
public int read(byte[] b) throws IOException
1. 我們先設定一個緩衝區即位元組陣列用於儲存從流中讀取的位元組資料,該陣列的長度為N
2. 那麼就是從流中讀取N個位元組到位元組陣列中。但是注意返回的是讀入的總位元組數而並不是N,說明有的時候實際讀入的總位元組數不一定等於陣列的長度
3. 檔案的內容是12345.那麼流中一共有5個位元組,但是我們設定的位元組陣列長度為2.那麼會讀取幾次?每次情況是怎麼樣的?
public class FileStream { public static void main(String[] args) { //建立檔案物件 File file=new File("C:\\Users\\Administrator\\Desktop\\1.txt"); try { //建立連結 FileInputStream fileInputStream=new FileInputStream(file); int n=0; byte[] b=new byte[2]; int i=0; while (n!=-1) //當n不等於-1,則代表未到末尾 { n=fileInputStream.read(b);//返回實際讀取到位元組陣列中的位元組數 System.out.println(n); System.out.println(Arrays.toString(b)); //讀取後的位元組陣列內容 i++; System.out.println("執行次數:"+i); } System.out.println(new String(b)); } catch (FileNotFoundException e) { System.out.println("檔案不存在或者檔案不可讀或者檔案是目錄"); } catch (IOException e) { System.out.println("讀取過程存在異常"); } } }
實際執行結果如下:
可以看出,陣列長度為2,因此第一次讀取2個位元組到陣列中,陣列已經被填滿。流中還剩餘3個位元組繼續讀取
第二次讀取,仍然讀取2個位元組到陣列中,陣列內容被替換。此時流中只剩餘1個位元組,根據API說明,讀取陣列長度(2)個位元組到陣列中,但接下來已經無法繼續讀取2個位元組了, 是否就應該停止了?
實際過程中並未停止,而是進行了第三次讀取,只讀取了剩餘1個位元組,並頂替到了陣列的0下標位置中。
接下來第4次讀取,才發現移到末尾,而後返回-1.停止讀取
所以此處存疑-----為什麼當剩餘只有1個位元組,而要求是讀取2個位元組時,還可以繼續讀取?
那麼我們檢視此方法原始碼,發現其本質是呼叫的其它方法readBytes(b,b.length);
public int read(byte b[]) throws IOException { return readBytes(b,b.length); }
繼續檢視readBytes(b,b.length)方法是native方法代表該方法是有實現體的但不是在JAVA語言中實現的導致沒辦法看具體實現
但是可以理解引數b是我們設定的陣列,0是int型,最後一個引數是陣列的長度
private native int readBytes(byte b[],int off,int len) throws IOException;
那麼我們檢視FileInputStream的父類InputStream,發現有關於這個方法的實現,
我們現在考慮第三次讀取的時候方法執行情況,此時b是[51,52].off 是0,len是2。資料流中就只有一個位元組存在了
if else if的這個條件判斷髮現都不符合,繼續往下執行。
read()--該方法代表從流中讀取一個位元組,而流中此時剛好還有一個位元組存在,該方法執行沒有問題。返回值為53
繼續往下執行發現b[0]=(byte)53.也就是將讀取到的int型轉為位元組並存儲在陣列中的第一個位置,此時陣列內容為[53,52]
繼續執行進入for迴圈,此時流中已沒有位元組,那麼read()方法返回未-1退出迴圈。返回變數i的值即是1.
也就是此次方法執行讀取了1個位元組到陣列中。且讀取到了檔案的末尾,因此第4次執行的時候到int c=read()方法時就已經返回-1,並沒有替換陣列中的值了
public int read(byte b[],int len) throws IOException { if (b == null) { throw new NullPointerException(); } else if (off < 0 || len < 0 || len > b.length - off) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return 0; } int c = read(); if (c == -1) { return -1; } b[off] = (byte)c; int i = 1; try { for (; i < len ; i++) { c = read(); if (c == -1) { break; } b[off + i] = (byte)c; } } catch (IOException ee) { } return i; }
讀取過程圖解:
4. 假設流中一共有5個位元組,但是我們設定的位元組陣列長度為10,那麼讀取幾次?每次情況是怎麼樣的?
public class FileStream { public static void main(String[] args) { //建立檔案物件 File file=new File("C:\\Users\\Administrator\\Desktop\\1.txt"); try { //建立連結 FileInputStream fileInputStream=new FileInputStream(file); int n=0; byte[] b=new byte[10]; int i=0; while (n!=-1) //當n不等於-1,則代表未到末尾 { n=fileInputStream.read(b);//返回實際讀取到位元組陣列中的位元組數 System.out.println(n); System.out.println(Arrays.toString(b)); //讀取後的位元組陣列內容 i++; System.out.println("執行次數:"+i); } System.out.println(new String(b)); } catch (FileNotFoundException e) { System.out.println("檔案不存在或者檔案不可讀或者檔案是目錄"); } catch (IOException e) { System.out.println("讀取過程存在異常"); } } }
執行結果如下:
結合上面提到的原始碼我們可以發現,原始碼中的for迴圈,儘管len是10(陣列長度),但是當i=5時,流中的位元組已經讀取完畢,指標移到檔案的末尾,因此不會繼續執行for迴圈。並且返回5,剛好符合結果中第一次實際讀取5個位元組到陣列中。第二次讀取時指標已到末尾。因此int c = read()這裡返回-1。就已經結束了方法,並沒有改變陣列也沒有再次for迴圈
但是這種情況存在一個問題:即陣列中有5個位置被浪費了,並沒有任何資料在裡面
具體讀取圖解:
結合以上兩種情況,那麼發現在使用read(byte b[])方法時的陣列長度至關重要,若長度小於流的位元組長度,那麼最後得出的內容會出現丟失。若大於流的位元組長度,那麼最後陣列的記憶體就浪費了,那麼就需要根據檔案的位元組長度來設定陣列的長度
byte[] b=new byte[(int) file.length()];
3.3)從輸入流中讀取最多len個位元組到位元組陣列中(從陣列的off位置開始儲存位元組),當len為0時則返回0,如果len不為零,則該方法將阻塞,直到某些輸入可用為止--此處存疑
public int read(byte[] b,int len) throws IOException
原始碼如下
public int read(byte b[],int len) throws IOException { if (b == null) { throw new NullPointerException(); } else if (off < 0 || len < 0 || len > b.length - off) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return 0; } int c = read(); if (c == -1) { return -1; } b[off] = (byte)c; int i = 1; try { for (; i < len ; i++) { c = read(); if (c == -1) { break; } b[off + i] = (byte)c; } } catch (IOException ee) { } return i; }
3.4)關閉此輸入流並釋放與該流關聯的所有系統資源---即釋放與實際檔案的連線(檢視原始碼可發現有同步鎖鎖住資源,因此關閉流釋放鎖)
public void close() throws IOException
三、三種read方法效率比較
1、檢視三種read方法原始碼,其本質都是利用for迴圈對內容進行單位元組的讀取
2、從程式碼形式看,使用read(byte[] b)較為直觀和簡便,因此專案中可以此方法為主進行資料讀取
到此這篇關於Java使用FileInputStream流讀取檔案示例詳解的文章就介紹到這了,更多相關Java FileInputStream流讀取檔案內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!