Java中文件讀寫操作與常用技巧分享
一、摘要
在之前的文章中,我們了解到在 Java I/O 體系中,File 類是唯一代表磁盤文件本身的對象。
File 類定義了一些與平臺無關(guān)的方法來操作文件,包括檢查一個文件是否存在、創(chuàng)建、刪除文件、重命名文件、判斷文件的讀寫權(quán)限是否存在、設置和查詢文件的最近修改時間等等操作。
值得注意的地方是,Java 中通常的 File 并不代表一個真實存在的文件對象,當你通過指定一個路徑描時,它就會返回一個代表這個路徑相關(guān)聯(lián)的一個虛擬對象,這個可能是一個真實存在的文件或者是一個包含多個文件的目錄。
下面我們一起來看看 File 類有哪些操作方法,以及實際使用過程中如何避坑。
二、File 類介紹
大家 JDK 中源代碼,你會發(fā)現(xiàn) File 類沒有無參構(gòu)造方法,最常用的是使用下面的構(gòu)造方法來生成 File 對象。
以 windows 操作系統(tǒng)為例,操作文件的方式如下!
//?指定一個完整路徑,獲取文件對象 File?file?=?new?File("D:\\Files\\test.txt"); System.out.println(file1.getName()); //?指定一個父文件路徑和子文件名稱,獲取文件對象 File?file?=?new?File("D:\\Files",?"test.txt"); System.out.println(file2.getName());
File 類中定義了很多關(guān)于 File 對象的一些操作方法,我們通過一段代碼一起來看看。
public?static?void?main(String[]?args)?throws?Exception?{ ????//?指定一個文件完整路徑,獲取文件對象 ????File?file?=?new?File("D:\\Files\\test.txt"); ????//?獲取文件父節(jié)點目錄對象 ????File?parentFile?=?file.getParentFile(); ????//?判斷指定路徑的文件目錄是否存在 ????if(parentFile.exists()){ ????????System.out.println("文件目錄存在"); ????}?else?{ ????????//?創(chuàng)建文件夾,可以自動創(chuàng)建多級文件夾 ????????parentFile.mkdirs(); ????????System.out.println("文件目錄不存在,創(chuàng)建一個文件目錄"); ????} ????//?判斷指定父節(jié)點路徑的是否是一個目錄 ????if(parentFile.isDirectory()){ ????????System.out.println("父節(jié)點路徑是一個目錄"); ????} ????//?判斷指定路徑的文件是否存在 ????if(file.exists()){ ????????System.out.println("文件存在"); ????}?else?{ ????????//?創(chuàng)建文件 ????????file.createNewFile(); ????????System.out.println("文件不存在,創(chuàng)建一個文件"); ????} ????//?獲取目錄下的所有文件/文件夾(僅該層路徑下) ????File[]?files?=?parentFile.listFiles(); ????System.out.print("路徑下有文件:"); ????for?(File?f?:?files)?{ ????????System.out.print(f?+?";"); ????} ????System.out.println(); ????//?獲取文件名、文件夾名 ????System.out.println("files[0]的文件名:"?+?files[0].getName()); ????//?獲取文件、文件夾路徑 ????System.out.println("files[0]的文件路徑:"?+?files[0].getPath()); ????//?獲取文件、文件夾絕對路徑 ????System.out.println("files[0]的絕對路徑:"?+?files[0].getAbsolutePath()); ????//?獲取文件父目錄路徑 ????System.out.println("files[0]的父文件夾名:"?+?files[0].getParent()); ????//?判斷文件、文件夾是否存在 ????System.out.println(files[0].exists()???"files[0]的存在"?:?"files[0]的不存在"); ????//?判斷文件是否可寫 ????System.out.println(files[0].canWrite()???"files[0]的可寫"?:?"files[0]的不可寫"); ????//?判斷文件是否可讀 ????System.out.println(files[0].canRead()???"files[0]的可讀"?:?"files[0]的不可讀"); ????//?判斷文件是否可執(zhí)行 ????System.out.println(files[0].canExecute()???"file[0]可執(zhí)行"?:?"file[0]不可執(zhí)行"); ????//?判斷文件、文件夾是不是目錄 ????System.out.println(files[0].isDirectory()???"files[0]的是目錄"?:?"files[0]的不是目錄"); ????//?判斷拿文件、文件夾是不是標準文件 ????System.out.println(files[0].isFile()???"files[0]的是文件"?:?"files[0]的不是文件"); ????//?判斷路徑名是不是絕對路徑 ????System.out.println(files[0].isAbsolute()???"files[0]的路徑名是絕對路徑"?:?"files[0]的路徑名不是絕對路徑"); ????//?獲取文件、文件夾上一次修改時間 ????System.out.println("files[0]的最后修改時間:"?+?files[0].lastModified()); ????//?獲取文件的字節(jié)數(shù),如果是一個文件夾則這個值為0 ????System.out.println("files[0]的大?。??+?files[0].length()?+?"?Bytes"); ????//?獲取文件路徑URI后的路徑名 ????System.out.println("files[0]的路徑轉(zhuǎn)換為URI:"?+?files[0].toURI()); ????//?下面的代碼邏輯,假設目錄下有3個以上文件 ????//?對文件重命名 ????File?newfile?=?new?File(file.getParentFile(),?"22.txt");??//新的文件名稱 ????files[0].renameTo(newfile); ????//?刪除指定的文件、文件夾 ????files[1].delete(); ????//?當虛擬機終止時刪除指定的文件、文件夾 ????files[2].deleteOnExit(); }
輸出結(jié)果如下:
文件目錄存在
父節(jié)點路徑是一個目錄
文件存在
路徑下有文件:D:\Files\1.txt;D:\Files\2.txt;D:\Files\3.txt;
files[0]1.txt
files[0]的文件路徑:D:\Files\1.txt
files[0]的絕對路徑:D:\Files\1.txt
files[0]的父文件夾名:D:\Files
files[0]的存在
files[0]的可寫
files[0]的可讀
file[0]不可執(zhí)行
files[0]的不是目錄
files[0]的是文件
files[0]的路徑名是絕對路徑
files[0]的最后修改時間:1686814709000
files[0]的大?。? Bytes
files[0]的路徑轉(zhuǎn)換為URI:file:/D:/Files/1.txt
示例代碼中,基本比較全面地演示了 File 的一些基本用法,比如文件或者文件夾的新增、重命名、刪除,以及獲取文件或者文件夾相關(guān)信息等操作。
其中有兩點地方,值得注意:
- 第一個就是分隔符的問題。不同的操作系統(tǒng),路徑分隔符是不一樣的,這個可以通過
File.separator
解決,具體實現(xiàn)看下面 - 第二個就是刪除的如果是一個文件夾的話,文件夾下還有文件/文件夾,是無法刪除成功的
關(guān)于不同操作系統(tǒng)下的路徑符號問題解決辦法?。╳indows->“\”;Linux->“/”)
在實際的編程過程中,我們不可能為了區(qū)分操作系統(tǒng),然后又單獨寫一份文件路徑。
可以通過File.separator
來實現(xiàn)跨平臺的編程邏輯,File.separator
會根據(jù)不同的操作系統(tǒng)取不同操作系統(tǒng)下的分隔符。
以上面的示范代碼為例,我們可以對寫法進行如下改造!
//?windows?系統(tǒng)下的文件絕對路徑定義方式 String?path?=?"d:"+File.separator?+"Files"+File.separator+"text.txt"; File?file?=?new?File(path);
文件的路徑結(jié)果會與預期一致!
三、文件的讀寫操作
對文件的讀寫,可以通過字節(jié)流或者字符流接口來完成,但不管哪種方式,大致分以下幾個步驟完成。
- 第一步:獲取一個文件 file 對象
- 第二步:通過 file 對象,獲取一個字節(jié)流或者字符流接口的對象,進行讀寫操作
- 第三步:關(guān)閉文件流
具體的代碼實踐如下!
3.1、通過字節(jié)流接口寫入
字節(jié)流接口的文件寫入,可以通過OutputStream
下的子類FileOutputStream
來實現(xiàn)文件的數(shù)據(jù)寫入操作。
具體實例如下:
//?創(chuàng)建一個?readWriteDemo.txt?文件 File?file?=?new?File("readWriteDemo.txt"); if(!file.exists()){ ????file.createNewFile(); } //?向文件中寫入數(shù)據(jù)(這種方式會覆蓋原始數(shù)據(jù)) OutputStream?outputStream?=?new?FileOutputStream(file); String?str?=?"我們一起學習Java"; outputStream.write(str.getBytes(StandardCharsets.UTF_8)); outputStream.close();
上面的操作方式會覆蓋原始數(shù)據(jù),如果想在已有的文件里面,進行追加寫入數(shù)據(jù),可以如下方式實現(xiàn)。
//?追加數(shù)據(jù)寫入(這種方式不會覆蓋原始數(shù)據(jù)) OutputStream?appendOutputStream?=?new?FileOutputStream(file,?true); String?str?=?"-----這是追加的內(nèi)容------"; appendOutputStream.write(str.getBytes(StandardCharsets.UTF_8)); appendOutputStream.close();
3.2、通過字節(jié)流接口讀取
字節(jié)流方式的文件讀取,可以通過InputStream
下的子類FileInputStream
來實現(xiàn)文件的數(shù)據(jù)讀取操作。
具體實例如下:
//?獲取?readWriteDemo.txt?文件 File?file?=?new?File("readWriteDemo.txt"); if(file.exists()){ ????//?獲取文件流 ????InputStream?input?=?new?FileInputStream(file); ????//?臨時區(qū) ????byte[]?buffer?=?new?byte[1024]; ????//?分次讀取數(shù)據(jù),每次最多讀取1024個字節(jié),將數(shù)據(jù)讀取到臨時區(qū)之中,同時返回讀取的字節(jié)個數(shù),如果遇到文件末尾,會返回-1 ????int?len; ????while?((len?=?input.read(buffer))?>?-1)?{ ????????//?字節(jié)轉(zhuǎn)為字符串 ????????String?msg?=?new?String(buffer,?0,?len,?StandardCharsets.UTF_8); ????????System.out.println(msg); ????} ????//?數(shù)據(jù)讀取完畢之后,關(guān)閉輸入流 ????input.close(); }
3.3、通過字符流接口寫入
在之前的文章中,我們了解到為了簡化字符的數(shù)據(jù)傳輸操作,JDK 提供了 Writer 與 Reader 字符流接口。
字符流方式的文件寫入,可以通過Writer
下的子類FileWriter
來實現(xiàn)文件的數(shù)據(jù)寫入操作。
具體實例如下:
//?創(chuàng)建一個?newReadWriteDemo.txt?文件 File?file?=?new?File("newReadWriteDemo.txt"); if(!file.exists()){ ????file.createNewFile(); } //?實例化Writer類對象 Writer?out?=?new?FileWriter(file)?; //?輸出字符串 out.write("Hello"); //?輸出換行 out.write("\n"); //?追加信息,append?方法底層本質(zhì)調(diào)用的是?write?方法 out.append("我們一起來學習Java"); //?關(guān)閉輸出流 out.close();
3.4、通過字符流接口讀取
字符流方式的文件讀取,可以通過Reader
下的子類FileReader
來實現(xiàn)文件的數(shù)據(jù)讀取操作。
具體實例如下:
//?創(chuàng)建一個?newReadWriteDemo.txt?文件 File?file?=?new?File("newReadWriteDemo.txt"); if(file.exists()){ ????//?實例化輸入流 ????Reader?reader?=?new?FileReader(file); ????//?臨時區(qū) ????char[]?buffer?=?new?char[1024]; ????//?分次讀取數(shù)據(jù),每次最多讀取1024個字符,將數(shù)據(jù)讀取到臨時區(qū)之中,同時返回讀取的字節(jié)個數(shù),如果遇到文件末尾,會返回-1 ????int?len; ????while?((len?=?reader.read(buffer))?>?-1)?{ ????????//?字符轉(zhuǎn)為字符串 ????????String?msg?=?new?String(buffer,?0,?len); ????????System.out.println(msg); ????} ????//?關(guān)閉輸入流 ????reader.close(); }
3.5、文件拷貝
在實際的軟件開發(fā)過程中,避免不了文件拷貝。通過以上的接口方法,我們可以很容易的寫出一個文件復制的方法。
比如以字節(jié)流操作為例,具體實例如下:
//?1.?創(chuàng)建一個字節(jié)數(shù)組作為數(shù)據(jù)讀取的臨時區(qū) byte[]?buffer?=?new?byte[1024]; //?2.?創(chuàng)建一個?FileInputStream?對象用于讀取文件 InputStream?input?=?new?FileInputStream(new?File("input.txt")); //?3.?創(chuàng)建一個?FileOutputStream?對象用于寫入文件 OutputStream?output?=?new?FileOutputStream(new?File("output.txt")); //?4.?循環(huán)讀取文件內(nèi)容到臨時區(qū),并將臨時區(qū)中的數(shù)據(jù)寫入到輸出文件中 int?length; while?((length?=?input.read(buffer))?!=?-1)?{ ????output.write(buffer,?0,?length); } //?5.?關(guān)閉輸入流 input.close(); //?6.?關(guān)閉輸出流 output.close();
除此之外,JDK 也支持采用緩存流讀寫技術(shù)來實現(xiàn)數(shù)據(jù)的高效讀寫。
之所為高效,是因為字節(jié)緩沖流內(nèi)部維護了一個緩沖區(qū),讀寫時先將數(shù)據(jù)存入緩沖區(qū)中,當緩沖區(qū)滿時再將數(shù)據(jù)一次性讀取出來或者寫入進去,這樣可以減少與磁盤實際的 I/O 操作次數(shù),可以顯著提升讀寫操作的效率。
比如以字節(jié)流緩沖流為例,包裝類分別是:BufferedInputStream(字節(jié)緩存輸入流) 和 BufferedOutputStream(字符緩存輸入流)。
采用緩沖流拷貝文件,具體實例如下:
//?1.?創(chuàng)建一個字節(jié)數(shù)組作為數(shù)據(jù)讀取的臨時區(qū) byte[]?buffer?=?new?byte[1024]; //?2.?創(chuàng)建一個?BufferedInputStream?緩存輸入流對象用于讀取文件 InputStream?bis?=?new?BufferedInputStream(new?FileInputStream(new?File("input.txt"))); //?3.?創(chuàng)建一個?BufferedOutputStream?緩存輸出流對象用于寫入文件 OutputStream?bos?=?new?BufferedOutputStream(new?FileOutputStream(new?File("output.txt"))); //?4.?循環(huán)讀取文件內(nèi)容到臨時區(qū),并將緩沖區(qū)中的數(shù)據(jù)寫入到輸出文件中 int?length; while?((length?=?bis.read(buffer))?!=?-1)?{ ????bos.write(buffer,?0,?length); } //?5.?關(guān)閉輸入流 bis.close(); //?6.?關(guān)閉輸出流 bos.close();
在大文件的拷貝中,使用緩存流比不使用緩存流技術(shù)至少快 10 倍,耗時是很明顯的,大家可以親自試一下。
四、字節(jié)流與字符流的互轉(zhuǎn)
在之前的文章中,我們了解到字節(jié)流與字符流,兩者其實是可以互轉(zhuǎn)的。
其中 InputStreamReader 和 OutputStreamWriter 就是轉(zhuǎn)化橋梁。
4.1、字節(jié)流轉(zhuǎn)字符流的操作
字節(jié)流轉(zhuǎn)字符流的操作,主要體現(xiàn)在數(shù)據(jù)的讀取階段,轉(zhuǎn)化過程如下圖所示:
以上文中的字節(jié)流接口讀取文件為例,如果我們想要轉(zhuǎn)換字符流接口來讀取數(shù)據(jù),具體的操作方式如下:
//?獲取?readWriteDemo.txt?文件 File?file?=?new?File("readWriteDemo.txt"); if(file.exists()){ ????//?獲取字節(jié)輸入流 ????InputStream?inputStream?=?new?FileInputStream(file); ????//?轉(zhuǎn)字符流輸入流,指定?UTF_8?編碼規(guī)則,讀取數(shù)據(jù) ????Reader?reader?=?new?InputStreamReader(inputStream,?StandardCharsets.UTF_8); ????//?緩沖區(qū) ????char[]?buffer?=?new?char[1024]; ????//?分次讀取數(shù)據(jù),每次最多讀取1024個字符,將數(shù)據(jù)讀取到緩沖區(qū)之中,同時返回讀取的字節(jié)個數(shù) ????int?len; ????while?((len?=?reader.read(buffer))?>?-1)?{ ????????//?字符轉(zhuǎn)為字符串 ????????String?msg?=?new?String(buffer,?0,?len); ????????System.out.println(msg); ????} ????//?關(guān)閉輸入流 ????reader.close(); ????inputStream.close(); }
當讀取數(shù)據(jù)的時候,先通過字節(jié)流讀取,再轉(zhuǎn)成字符流讀取。
字節(jié)流轉(zhuǎn)字符流,需要指定編碼規(guī)則,如果沒有指定,會取當系統(tǒng)默認的編碼規(guī)則。
4.2、字符流轉(zhuǎn)字節(jié)流的操作
字符流轉(zhuǎn)字節(jié)流的操作,主要體現(xiàn)在數(shù)據(jù)的寫入階段,轉(zhuǎn)化過程如下圖所示:
以上文中的字節(jié)流接口寫入文件為例,如果我們想要轉(zhuǎn)換字符流接口來寫入數(shù)據(jù),具體的操作方式如下:
//?創(chuàng)建一個?newReadWriteDemo.txt?文件 File?file?=?new?File("readWriteDemo.txt"); if(!file.exists()){ ????file.createNewFile(); } //?獲取字節(jié)輸出流 OutputStream?outputStream?=?new?FileOutputStream(file); //?轉(zhuǎn)字符流輸出流,指定?UTF_8?編碼規(guī)則,寫入數(shù)據(jù) Writer?out?=?new?OutputStreamWriter(outputStream,?StandardCharsets.UTF_8); //?輸出字符串 out.write("Hello"); //?輸出換行 out.write("\n"); //?追加信息,append?方法底層本質(zhì)調(diào)用的是?write?方法 out.append("我們一起來學習Java"); //?關(guān)閉輸出流 out.close(); outputStream.close();
同樣的,當寫入數(shù)據(jù)的時候,先通過字符流寫入,再轉(zhuǎn)成字節(jié)流輸出。
字符流轉(zhuǎn)字節(jié)流,也需要指定編碼規(guī)則,如果沒有指定,會取當系統(tǒng)默認的編碼規(guī)則。
以上就是Java中文件讀寫操作與常用技巧分享的詳細內(nèi)容,更多關(guān)于Java文件讀寫的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
springboot?ConfigurationProperties的綁定源碼示例解析
這篇文章主要為大家介紹了springboot?ConfigurationProperties的綁定源碼示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-09-09Java調(diào)取創(chuàng)藍253短信驗證碼的實現(xiàn)代碼
這篇文章主要介紹了Java調(diào)取創(chuàng)藍253短信驗證碼的實現(xiàn)代碼,需要的朋友可以參考下2018-04-04Java編程小實例—數(shù)字時鐘的實現(xiàn)代碼示例
正所謂拳不離手曲不離口,java學習的過程中,練習還是要多一點比較好。接下來分享給大家一個Java編程的小實例,供朋友們參考。2017-10-10jeefast和Mybatis實現(xiàn)三級聯(lián)動的示例代碼
這篇文章主要介紹了jeefast和Mybatis實現(xiàn)三級聯(lián)動的示例代碼,代碼簡單易懂,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-10-10圖文講解IDEA中根據(jù)數(shù)據(jù)庫自動生成實體類
這篇文章主要以圖文講解IDEA中根據(jù)數(shù)據(jù)庫自動生成實體類,本文主要以Mysql數(shù)據(jù)庫為例,應該會對大家有所幫助,如果有錯誤的地方,還望指正2023-03-03