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, 0, b.length);
public int read(byte b[]) throws IOException { return readBytes(b, 0, b.length); }
繼續查看readBytes(b, 0, 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 off, 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 off,int len) throws IOException
源碼如下
public int read(byte b[], int off, 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)較為直觀和簡便,因此項目中可以此方法為主進行數據讀取
以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。
推薦閱讀:
- Java中IO流解析及代碼實例詳解
- Java進階核心之InputStream流深入講解
- 圖文詳解Java中的字節輸入與輸出流
- idea中使用Inputstream流導致中文亂碼解決方法
- Java異常的處理機制