Java?I/O?(Input/Output)文件字節(jié)流舉例詳解
Java I/O 簡(jiǎn)介
Java I/O(輸入/輸出)是 Java 程序中用于處理數(shù)據(jù)輸入和輸出的重要部分。
輸入流(Input Streams):用于從數(shù)據(jù)源讀取數(shù)據(jù)。常見的輸入流包括FileInputStream
(從文件讀?。?、BufferedInputStream
(提高讀取效率)等。
輸出流(Output Streams):用于將數(shù)據(jù)寫入到目的地。例如FileOutputStream
(向文件寫入)、BufferedOutputStream
(提高寫入效率)。
字符流(Reader 和 Writer):處理字符數(shù)據(jù),更適合處理文本。如FileReader
和FileWriter
。
緩沖流(Buffered Streams):通過緩沖區(qū)來(lái)減少實(shí)際的 I/O 操作次數(shù),提高性能。
對(duì)象流(Object Streams):用于實(shí)現(xiàn)對(duì)象的序列化和反序列化,如ObjectInputStream
和ObjectOutputStream
。
在實(shí)際編程中,根據(jù)具體的需求選擇合適的 I/O 流可以提高程序的效率和可讀性。
計(jì)算機(jī)總線結(jié)構(gòu):
那么為什么會(huì)有I/O呢?其實(shí)I/O無(wú)時(shí)無(wú)刻不在我們身邊,比如讀取硬盤上的文件,網(wǎng)絡(luò)文件的傳輸,鼠標(biāo)鍵盤輸入,也可以是接受單片機(jī)發(fā)回的數(shù)據(jù),而能夠支持這些操作的設(shè)備就是I/O設(shè)備。
我們可以大致看一下整個(gè)計(jì)算機(jī)的總線結(jié)構(gòu):
最核心的是CPU,CPU像計(jì)算機(jī)的大腦一樣,是計(jì)算機(jī)的核心部件,幾乎所有的計(jì)算都是靠這個(gè)CPU來(lái)進(jìn)行的,CPU懂的比較多,它可以對(duì)各種類型進(jìn)行計(jì)算,但是隨著時(shí)代的發(fā)展對(duì)圖形的要求越來(lái)越高,CPU就略顯乏力;于是就出現(xiàn)了GPU(顯卡),顯卡就是專門對(duì)于圖形進(jìn)行計(jì)算。
通過北橋芯片連接到內(nèi)存,這樣CPU就可以對(duì)內(nèi)存進(jìn)行操作;南橋芯片是用于讀取U盤或者硬盤內(nèi)的數(shù)據(jù) 。
常見的I/O設(shè)備一般是鼠標(biāo)、鍵盤這類通過USB進(jìn)行傳輸?shù)耐庠O(shè)或者是通過Sata接口或是M.2連接的硬盤。一般情況下,這些設(shè)備是由CPU發(fā)出指令通過南橋芯片間接進(jìn)行控制,而不是由CPU直接操作。
而我們?cè)诔绦蛑?,想要讀取這些外部連接的!O設(shè)備中的內(nèi)容,就需要將數(shù)據(jù)傳輸?shù)絻?nèi)存中。而需要實(shí)現(xiàn)這樣的操作,單單憑借一個(gè)小的程序是無(wú)法做到的,而操作系統(tǒng)(如:Windows/inux/MacOS)就是專門用于控制和管理計(jì)算機(jī)硬件和軟件資源的軟件,我們需要讀取一個(gè)IO設(shè)備的內(nèi)容時(shí),就可以向操作系統(tǒng)發(fā)出請(qǐng)求,由操作系統(tǒng)幫助我們來(lái)和底層的硬件交互以完成我們的讀取/寫入請(qǐng)求。
JDK提供了一套用于IO操作的框架,為了方便我們開發(fā)者使用,就定義了一個(gè)像水流一樣,根據(jù)流的傳輸方向和讀取單位,分為字節(jié)流InputStream和OutputStream以及字符流Reader和Writer的IO框架,當(dāng)然,這里的流指的是數(shù)據(jù)流,通過流,我們就可以一直從流中讀取數(shù)據(jù),直到讀取到盡頭,或是不斷向其中寫入數(shù)據(jù),直到我們寫入完成,而這類IO就是我們所說(shuō)的BIO。
文件字節(jié)流:
字節(jié)流一次讀取一個(gè)字節(jié),也就是一個(gè) byte 的大小,而字符流顧名思義,就是一次讀取一個(gè)字符,也就是一個(gè) char 的大小(在讀取純文本文件的時(shí)候更加適合)。
文件輸入流:
在 Java 中,文件輸入流(FileInputStream)用于從文件中讀取數(shù)據(jù)。FileInputStream 允許程序以字節(jié)為單位讀取文件的內(nèi)容。
創(chuàng)建方式:
通常通過指定要讀取的文件路徑來(lái)創(chuàng)建文件輸入流對(duì)象。例如:
try { FileInputStream fis = new FileInputStream("your_file_path"); // 后續(xù)的讀取操作 } catch (FileNotFoundException e) { e.printStackTrace(); }
但是這種方式需要處理各種可能的異常,比如 FileNotFoundException 異常和 IOException 異常,并且需要手動(dòng)關(guān)閉文件,完整代碼如下:
public class Hello_World { public static void main(String[] args) { // 想讀取一個(gè)文件 創(chuàng)建一個(gè)文件輸入流 使用完把流關(guān)閉掉 釋放掉 close FileInputStream stream = null; try { stream = new FileInputStream("絕對(duì)路徑/相對(duì)路徑"); // stream.close(); } catch (FileNotFoundException e) { throw new RuntimeException(e); } finally { if (stream != null) { try { stream.close(); } catch (IOException e) { throw new RuntimeException(e); } } } } }
僅僅是取得文件就如此費(fèi)勁,不合乎常理。所以有了try-with-resources 語(yǔ)句這種簡(jiǎn)便方式,try-with-resources 語(yǔ)句是一種用于更方便、更安全地管理資源(如輸入流、輸出流、數(shù)據(jù)庫(kù)連接等)的機(jī)制。
優(yōu)點(diǎn):
- 自動(dòng)資源管理:無(wú)需顯式地調(diào)用
close
方法來(lái)關(guān)閉資源,避免了因忘記關(guān)閉資源而導(dǎo)致的資源泄漏問題。 - 簡(jiǎn)潔的代碼:減少了樣板代碼,使代碼更簡(jiǎn)潔、更易讀。
語(yǔ)法格式:
try (Resource res = new Resource()) { // 使用資源的操作 } catch (Exception e) { // 異常處理 }
對(duì)于以上示例的完整代碼轉(zhuǎn)成try-with-resources
語(yǔ)句如下:
public class Hello_World { public static void main(String[] args) { try(FileInputStream inputStream = new FileInputStream("路徑")){ // 直接在try()中定義要在完成之后釋放的資源 } catch (IOException e){ // 這里變成IOException是因?yàn)檎{(diào)用close()可能會(huì)出現(xiàn),而FileNotFoundException是繼承自IOException的 e.printStackTrace(); }// 無(wú)需再編寫finally語(yǔ)句塊,因?yàn)樵谧詈笞詣?dòng)幫我們調(diào)用了close()。 } }
由此可見,try-with-resources
語(yǔ)句極大地提高了資源管理的便利性和可靠性,使代碼更加健壯和易于維護(hù)。
數(shù)據(jù)的傳遞:
如圖所示,在計(jì)算機(jī)數(shù)據(jù)由文件向內(nèi)存進(jìn)行傳遞的形式是以二進(jìn)制01串進(jìn)行的,一次一個(gè)字節(jié),就像水流一樣源源不斷的傳輸,直至文件傳輸結(jié)束。
數(shù)據(jù)不斷傳輸過來(lái),那我們?nèi)绾稳プx取數(shù)據(jù)呢?
調(diào)用read()方法是必要的,但是read()方法的調(diào)用方式也有很多種,這里主要列出來(lái)常見的三種。
1.直接讀取
try(FileInputStream inputStream = new FileInputStream("C:\\Users\\Xxy63\\Desktop\\無(wú)限彈窗代碼.txt")){ // 直接在try()中定義要在完成之后釋放的資源 int i = inputStream.read(); System.out.println((char)i); int x = inputStream.read(); // 當(dāng)沒有內(nèi)容后,會(huì)返回-1 System.out.println((char)x); } catch (IOException e){ // 這里變成IOException是因?yàn)檎{(diào)用close()可能會(huì)出現(xiàn),而FileNotFoundException是繼承自IOException的 e.printStackTrace(); }// 無(wú)需再編寫finally語(yǔ)句塊,因?yàn)樵谧詈笞詣?dòng)幫我們調(diào)用了close()。
由于讀取數(shù)據(jù)返回的是int類型的一個(gè)數(shù)據(jù),所以我們用int i 去接收它,然后利用強(qiáng)制類型轉(zhuǎn)換把i 轉(zhuǎn)為char類型進(jìn)行輸出。調(diào)用一次讀取一個(gè)字符,當(dāng)讀取完之后會(huì)返回-1.這樣效率較為低下,所以有下面第二種讀取方法。
2.循環(huán)讀取
由于讀取完之后會(huì)返回?cái)?shù)字-1,所以可以利用這一性質(zhì)進(jìn)行while循環(huán)進(jìn)行讀取,直到返回-1時(shí)結(jié)束循環(huán),代碼如下:
try(FileInputStream inputStream = new FileInputStream("C:\\Users\\Xxy63\\Desktop\\無(wú)限彈窗代碼.txt")){ // 直接在try()中定義要在完成之后釋放的資源 int i; while ((i = inputStream.read()) != -1) { System.out.print((char)i); } } catch (IOException e){ // 這里變成IOException是因?yàn)檎{(diào)用close()可能會(huì)出現(xiàn),而FileNotFoundException是繼承自IOException的 e.printStackTrace(); }// 無(wú)需再編寫finally語(yǔ)句塊,因?yàn)樵谧詈笞詣?dòng)幫我們調(diào)用了close()。
通過這種方式就可以一次性對(duì)文件內(nèi)的內(nèi)容全部讀取。但是由于不夠靈活,可變性較差,所以還可以用下面第三種方法進(jìn)行讀取。
3.區(qū)間讀取
區(qū)間讀取,顧名思義就是定義一個(gè)固定長(zhǎng)度的區(qū)間,將文件內(nèi)的內(nèi)容按照這個(gè)區(qū)間大小進(jìn)行讀取,當(dāng)文件未讀內(nèi)容小于區(qū)間長(zhǎng)度時(shí)會(huì)以小于區(qū)間長(zhǎng)度的形式進(jìn)行最后一次讀取,若沒有元素可讀取時(shí),一樣會(huì)返回-1。具體代碼如下:
try(FileInputStream inputStream = new FileInputStream("C:\\Users\\Xxy63\\Desktop\\無(wú)限彈窗代碼.txt")){ // 直接在try()中定義要在完成之后釋放的資源 System.out.println(inputStream.available()); // 獲取有多少個(gè)數(shù)據(jù)可讀 byte [] bytes = new byte[inputStream.available()]; // 一次讀x個(gè)數(shù)據(jù) while (inputStream.read(bytes) != -1) // 當(dāng)最后不足x個(gè)或者已經(jīng)沒有時(shí),會(huì)返回少于x個(gè)的數(shù)據(jù)或者-1 System.out.println(new String(bytes)); } catch (IOException e){ // 這里變成IOException是因?yàn)檎{(diào)用close()可能會(huì)出現(xiàn),而FileNotFoundException是繼承自IOException的 e.printStackTrace(); }// 無(wú)需再編寫finally語(yǔ)句塊,因?yàn)樵谧詈笞詣?dòng)幫我們調(diào)用了close()。
讀取過程中可使用available()方法查詢可讀數(shù)量,在上面的案例中,我將區(qū)間長(zhǎng)度x設(shè)置為了可讀長(zhǎng)度,這樣也可以一次性讀取完文件內(nèi)數(shù)據(jù)。也可以設(shè)置其他int類型的x作為長(zhǎng)度參數(shù)。
這種方法在文件輸出流常用,一個(gè)字節(jié)一個(gè)字節(jié)的讀取出來(lái)并一個(gè)字節(jié)一個(gè)字節(jié)的寫入另一個(gè)文件,相當(dāng)于文件的拷貝操作。
跳過操作:skip()方法。給skip(x)傳人參數(shù)x,可以設(shè)置跳過前幾個(gè)字節(jié)進(jìn)行讀取其下一個(gè)字節(jié)。
文件輸出流:
文件輸出流(FileOutputStream)用于將數(shù)據(jù)寫入到文件中。文件輸出流允許您以字節(jié)為單位向文件寫入數(shù)據(jù)。
在寫入之前您需要提供要寫入的文件的路徑和名稱。如果文件不存在,它將被創(chuàng)建;如果文件已存在,默認(rèn)情況下,新寫入的數(shù)據(jù)會(huì)覆蓋原有的內(nèi)容。
try { FileOutputStream fos = new FileOutputStream("your_file.txt"); } catch (IOException e) { e.printStackTrace(); }
stream.flush()方法的主要作用是將輸出流緩沖區(qū)中的數(shù)據(jù)強(qiáng)制刷新并輸出。通常,當(dāng)我們使用輸出流(如 FileOutputStream
、BufferedOutputStream
等)寫入數(shù)據(jù)時(shí),數(shù)據(jù)并不是立即被發(fā)送到目的地(如文件),而是先被存儲(chǔ)在緩沖區(qū)中。緩沖區(qū)的目的是減少實(shí)際的 I/O 操作次數(shù),從而提高性能。
然而,在某些情況下,我們希望確保數(shù)據(jù)能夠立即被發(fā)送出去,而不是等到緩沖區(qū)填滿或者輸出流被關(guān)閉。這時(shí)就可以使用 flush
方法。
默認(rèn)情況下(append的參數(shù)默認(rèn)是false),寫入的內(nèi)容會(huì)直接取代原文件內(nèi)的內(nèi)容,即覆蓋掉。代碼如下:
public class Hello_World { public static void main(String[] args) { try(FileOutputStream stream = new FileOutputStream("C:\\Users\\Xxy63\\Desktop\\無(wú)限彈窗代碼.txt")){ stream.write("Hello World".getBytes()); // 直接取代原內(nèi)容 stream.flush(); }catch (IOException e){ e.printStackTrace(); } } }
如果想接著文件的內(nèi)容往后繼續(xù)寫(追加模式),那么只需要把a(bǔ)ppend的參數(shù)改為true即可,代碼如下:
public class Hello_World { public static void main(String[] args) { try(FileOutputStream stream = new FileOutputStream("C:\\Users\\Xxy63\\Desktop\\無(wú)限彈窗代碼.txt",true)){ // 加上true 變成追加模式 stream.write("Hello World".getBytes()); // 直接取代原內(nèi)容 stream.flush(); }catch (IOException e){ e.printStackTrace(); } } }
至此,我們就完成了輸出流操作,那么,就可以結(jié)合輸入流和輸出流進(jìn)行拷貝操作了。
文件的拷貝:
文件拷貝是將一個(gè)文件的內(nèi)容完整地復(fù)制到另一個(gè)文件的操作。相關(guān)的類有:
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException;
文件的拷貝操作一般使用讀取數(shù)據(jù)的第三種方法,區(qū)間讀取。因?yàn)檫@種方法可以設(shè)置足夠大的區(qū)間,讀取速度較快,不需要一個(gè)字節(jié)一個(gè)字節(jié)的去讀取。下面是兩個(gè)拷貝的示例代碼:
// 文件拷貝+進(jìn)度條 public class Hello_World { public static void main(String[] args) { File file = new File("C:\\Users\\Xxy63\\Desktop\\文章.md"); try(FileInputStream in = new FileInputStream(file); FileOutputStream out = new FileOutputStream("XXX.txt")){ byte [] bytes = new byte[400]; int len; long total = file.length() , sum = 0; while ((len = in.read(bytes))!=-1){ out.write(bytes, 0, len); sum += len; System.out.println("文件已拷貝" + (sum*100/total) + '%'); } }catch (IOException e){ e.printStackTrace(); } } }
public class Hello_World { public static void main(String[] args) { try(FileInputStream in = new FileInputStream("C:\\Users\\Xxy63\\Desktop\\無(wú)限彈窗代碼.txt"); FileOutputStream out = new FileOutputStream("C:\\Users\\Xxy63\\Desktop\\copy.txt")){ byte[] bytes = new byte[1024]; int len; while ((len = in.read(bytes)) != -1) { out.write(bytes, 0, len); } // 拷貝速度大大提升 }catch (IOException e){ e.printStackTrace(); } } }
在上述代碼中,通過創(chuàng)建輸入流 FileInputStream
從源文件讀取數(shù)據(jù),創(chuàng)建輸出流 FileOutputStream
向目標(biāo)文件寫入數(shù)據(jù)。使用一個(gè)緩沖區(qū)來(lái)提高拷貝效率,每次讀取一定數(shù)量的字節(jié)到緩沖區(qū),然后將緩沖區(qū)中的數(shù)據(jù)寫入目標(biāo)文件,直到讀取完源文件的所有內(nèi)容。
文件拷貝在很多場(chǎng)景中都很有用,比如:
- 數(shù)據(jù)備份:將重要文件復(fù)制一份以防止數(shù)據(jù)丟失。
- 共享文件:將文件拷貝到多個(gè)位置以便不同的程序或用戶使用。
例如,如果您有一個(gè)包含重要配置信息的文件,為了安全起見,可以定期進(jìn)行備份拷貝。又或者在一個(gè)文件處理系統(tǒng)中,需要將原始文件拷貝到多個(gè)不同的目錄下以供不同的模塊處理。
總結(jié)
到此這篇關(guān)于Java I/O (Input/Output)文件字節(jié)流的文章就介紹到這了,更多相關(guān)Java I/O文件字節(jié)流內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot+dynamicDataSource動(dòng)態(tài)添加切換數(shù)據(jù)源方式
這篇文章主要介紹了springboot+dynamicDataSource動(dòng)態(tài)添加切換數(shù)據(jù)源方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01使用PageHelper插件實(shí)現(xiàn)Service層分頁(yè)
這篇文章主要為大家詳細(xì)介紹了使用PageHelper插件實(shí)現(xiàn)Service層分頁(yè),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04Java中靜態(tài)類型檢查是如何進(jìn)行的實(shí)例思路詳解
這篇文章主要介紹了Java中靜態(tài)類型檢查是如何進(jìn)行的實(shí)例思路詳解的相關(guān)資料,需要的朋友可以參考下2016-05-05java管道piped輸入流與輸出流應(yīng)用場(chǎng)景案例分析
這篇文章主要介紹了java管道流PipedInputStream與PipedOutputStream(輸入流與輸出流)的應(yīng)用場(chǎng)景案例分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2022-02-02SpringSecurity獲取當(dāng)前登錄用戶的信息的幾種方法實(shí)現(xiàn)
本文主要介紹了SpringSecurity中獲取當(dāng)前登錄用戶信息的多種方式,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2025-03-03