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。

推薦閱讀: