一、摘要
在之前的文章中,我們了解到在 Java I/O 體系中,File 類是唯一代表磁盤文件本身的對象。
File 類定義了一些與平臺無關的方法來操作文件,包括檢查一個文件是否存在、創建、刪除文件、重命名文件、判斷文件的讀寫權限是否存在、設置和查詢文件的最近修改時間等等操作。
值得注意的地方是,Java 中通常的 File 并不代表一個真實存在的文件對象,當你通過指定一個路徑描時,它就會返回一個代表這個路徑相關聯的一個虛擬對象,這個可能是一個真實存在的文件或者是一個包含多個文件的目錄。
下面我們一起來看看 File 類有哪些操作方法,以及實際使用過程中如何避坑。
二、File 類介紹
大家 JDK 中源代碼,你會發現 File 類沒有無參構造方法,最常用的是使用下面的構造方法來生成 File 對象。
以 windows 操作系統為例,操作文件的方式如下!
// 指定一個完整路徑,獲取文件對象
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 類中定義了很多關于 File 對象的一些操作方法,我們通過一段代碼一起來看看。
public static void main(String[] args) throws Exception {// 指定一個文件完整路徑,獲取文件對象File file = new File("D:\\Files\\test.txt");// 獲取文件父節點目錄對象File parentFile = file.getParentFile();// 判斷指定路徑的文件目錄是否存在if(parentFile.exists()){System.out.println("文件目錄存在");} else {// 創建文件夾,可以自動創建多級文件夾parentFile.mkdirs();System.out.println("文件目錄不存在,創建一個文件目錄");}// 判斷指定父節點路徑的是否是一個目錄if(parentFile.isDirectory()){System.out.println("父節點路徑是一個目錄");}// 判斷指定路徑的文件是否存在if(file.exists()){System.out.println("文件存在");} else {// 創建文件file.createNewFile();System.out.println("文件不存在,創建一個文件");}// 獲取目錄下的所有文件/文件夾(僅該層路徑下)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]的不可讀");// 判斷文件是否可執行System.out.println(files[0].canExecute() ? "file[0]可執行" : "file[0]不可執行");// 判斷文件、文件夾是不是目錄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());// 獲取文件的字節數,如果是一個文件夾則這個值為0System.out.println("files[0]的大小:" + files[0].length() + " Bytes");// 獲取文件路徑URI后的路徑名System.out.println("files[0]的路徑轉換為URI:" + files[0].toURI());// 下面的代碼邏輯,假設目錄下有3個以上文件// 對文件重命名File newfile = new File(file.getParentFile(), "22.txt"); //新的文件名稱files[0].renameTo(newfile);// 刪除指定的文件、文件夾files[1].delete();// 當虛擬機終止時刪除指定的文件、文件夾files[2].deleteOnExit();
}
輸出結果如下:
文件目錄存在
父節點路徑是一個目錄
文件存在
路徑下有文件: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]不可執行
files[0]的不是目錄
files[0]的是文件
files[0]的路徑名是絕對路徑
files[0]的最后修改時間:1686814709000
files[0]的大小:8 Bytes
files[0]的路徑轉換為URI:file:/D:/Files/1.txt
示例代碼中,基本比較全面地演示了 File 的一些基本用法,比如文件或者文件夾的新增、重命名、刪除,以及獲取文件或者文件夾相關信息等操作。
其中有兩點地方,值得注意:
- 第一個就是分隔符的問題。不同的操作系統,路徑分隔符是不一樣的,這個可以通過
File.separator
解決,具體實現看下面 - 第二個就是刪除的如果是一個文件夾的話,文件夾下還有文件/文件夾,是無法刪除成功的
關于不同操作系統下的路徑符號問題解決辦法!(windows->“\”;Linux->“/”)
在實際的編程過程中,我們不可能為了區分操作系統,然后又單獨寫一份文件路徑。
可以通過File.separator
來實現跨平臺的編程邏輯,File.separator
會根據不同的操作系統取不同操作系統下的分隔符。
以上面的示范代碼為例,我們可以對寫法進行如下改造!
// windows 系統下的文件絕對路徑定義方式
String path = "d:"+File.separator +"Files"+File.separator+"text.txt";
File file = new File(path);
文件的路徑結果會與預期一致!
三、文件的讀寫操作
對文件的讀寫,可以通過字節流或者字符流接口來完成,但不管哪種方式,大致分以下幾個步驟完成。
- 第一步:獲取一個文件 file 對象
- 第二步:通過 file 對象,獲取一個字節流或者字符流接口的對象,進行讀寫操作
- 第三步:關閉文件流
具體的代碼實踐如下!
3.1、通過字節流接口寫入
字節流接口的文件寫入,可以通過OutputStream
下的子類FileOutputStream
來實現文件的數據寫入操作。
具體實例如下:
// 創建一個 readWriteDemo.txt 文件
File file = new File("readWriteDemo.txt");
if(!file.exists()){file.createNewFile();
}// 向文件中寫入數據(這種方式會覆蓋原始數據)
OutputStream outputStream = new FileOutputStream(file);
String str = "我們一起學習Java";
outputStream.write(str.getBytes(StandardCharsets.UTF_8));
outputStream.close();
上面的操作方式會覆蓋原始數據,如果想在已有的文件里面,進行追加寫入數據,可以如下方式實現。
// 追加數據寫入(這種方式不會覆蓋原始數據)
OutputStream appendOutputStream = new FileOutputStream(file, true);
String str = "-----這是追加的內容------";
appendOutputStream.write(str.getBytes(StandardCharsets.UTF_8));
appendOutputStream.close();
3.2、通過字節流接口讀取
字節流方式的文件讀取,可以通過InputStream
下的子類FileInputStream
來實現文件的數據讀取操作。
具體實例如下:
// 獲取 readWriteDemo.txt 文件
File file = new File("readWriteDemo.txt");
if(file.exists()){// 獲取文件流InputStream input = new FileInputStream(file);// 臨時區byte[] buffer = new byte[1024];// 分次讀取數據,每次最多讀取1024個字節,將數據讀取到臨時區之中,同時返回讀取的字節個數,如果遇到文件末尾,會返回-1int len;while ((len = input.read(buffer)) > -1) {// 字節轉為字符串String msg = new String(buffer, 0, len, StandardCharsets.UTF_8);System.out.println(msg);}// 數據讀取完畢之后,關閉輸入流input.close();
}
3.3、通過字符流接口寫入
在之前的文章中,我們了解到為了簡化字符的數據傳輸操作,JDK 提供了 Writer 與 Reader 字符流接口。
字符流方式的文件寫入,可以通過Writer
下的子類FileWriter
來實現文件的數據寫入操作。
具體實例如下:
// 創建一個 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 方法底層本質調用的是 write 方法
out.append("我們一起來學習Java");// 關閉輸出流
out.close();
3.4、通過字符流接口讀取
字符流方式的文件讀取,可以通過Reader
下的子類FileReader
來實現文件的數據讀取操作。
具體實例如下:
// 創建一個 newReadWriteDemo.txt 文件
File file = new File("newReadWriteDemo.txt");
if(file.exists()){// 實例化輸入流Reader reader = new FileReader(file);// 臨時區char[] buffer = new char[1024];// 分次讀取數據,每次最多讀取1024個字符,將數據讀取到臨時區之中,同時返回讀取的字節個數,如果遇到文件末尾,會返回-1int len;while ((len = reader.read(buffer)) > -1) {// 字符轉為字符串String msg = new String(buffer, 0, len);System.out.println(msg);}// 關閉輸入流reader.close();
}
3.5、文件拷貝
在實際的軟件開發過程中,避免不了文件拷貝。通過以上的接口方法,我們可以很容易的寫出一個文件復制的方法。
比如以字節流操作為例,具體實例如下:
// 1. 創建一個字節數組作為數據讀取的臨時區
byte[] buffer = new byte[1024];
// 2. 創建一個 FileInputStream 對象用于讀取文件
InputStream input = new FileInputStream(new File("input.txt"));
// 3. 創建一個 FileOutputStream 對象用于寫入文件
OutputStream output = new FileOutputStream(new File("output.txt"));
// 4. 循環讀取文件內容到臨時區,并將臨時區中的數據寫入到輸出文件中
int length;
while ((length = input.read(buffer)) != -1) {output.write(buffer, 0, length);
}
// 5. 關閉輸入流
input.close();
// 6. 關閉輸出流
output.close();
除此之外,JDK 也支持采用緩存流讀寫技術來實現數據的高效讀寫。
之所為高效,是因為字節緩沖流內部維護了一個緩沖區,讀寫時先將數據存入緩沖區中,當緩沖區滿時再將數據一次性讀取出來或者寫入進去,這樣可以減少與磁盤實際的 I/O 操作次數,可以顯著提升讀寫操作的效率。
比如以字節流緩沖流為例,包裝類分別是:BufferedInputStream(字節緩存輸入流) 和 BufferedOutputStream(字符緩存輸入流)。
采用緩沖流拷貝文件,具體實例如下:
// 1. 創建一個字節數組作為數據讀取的臨時區
byte[] buffer = new byte[1024];
// 2. 創建一個 BufferedInputStream 緩存輸入流對象用于讀取文件
InputStream bis = new BufferedInputStream(new FileInputStream(new File("input.txt")));
// 3. 創建一個 BufferedOutputStream 緩存輸出流對象用于寫入文件
OutputStream bos = new BufferedOutputStream(new FileOutputStream(new File("output.txt")));// 4. 循環讀取文件內容到臨時區,并將緩沖區中的數據寫入到輸出文件中
int length;
while ((length = bis.read(buffer)) != -1) {bos.write(buffer, 0, length);
}
// 5. 關閉輸入流
bis.close();
// 6. 關閉輸出流
bos.close();
在大文件的拷貝中,使用緩存流比不使用緩存流技術至少快 10 倍,耗時是很明顯的,大家可以親自試一下。
四、字節流與字符流的互轉
在之前的文章中,我們了解到字節流與字符流,兩者其實是可以互轉的。
其中 InputStreamReader 和 OutputStreamWriter 就是轉化橋梁。
4.1、字節流轉字符流的操作
字節流轉字符流的操作,主要體現在數據的讀取階段,轉化過程如下圖所示:
以上文中的字節流接口讀取文件為例,如果我們想要轉換字符流接口來讀取數據,具體的操作方式如下:
// 獲取 readWriteDemo.txt 文件
File file = new File("readWriteDemo.txt");
if(file.exists()){// 獲取字節輸入流InputStream inputStream = new FileInputStream(file);// 轉字符流輸入流,指定 UTF_8 編碼規則,讀取數據Reader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);// 緩沖區char[] buffer = new char[1024];// 分次讀取數據,每次最多讀取1024個字符,將數據讀取到緩沖區之中,同時返回讀取的字節個數int len;while ((len = reader.read(buffer)) > -1) {// 字符轉為字符串String msg = new String(buffer, 0, len);System.out.println(msg);}// 關閉輸入流reader.close();inputStream.close();
}
當讀取數據的時候,先通過字節流讀取,再轉成字符流讀取。
字節流轉字符流,需要指定編碼規則,如果沒有指定,會取當系統默認的編碼規則。
4.2、字符流轉字節流的操作
字符流轉字節流的操作,主要體現在數據的寫入階段,轉化過程如下圖所示:
以上文中的字節流接口寫入文件為例,如果我們想要轉換字符流接口來寫入數據,具體的操作方式如下:
// 創建一個 newReadWriteDemo.txt 文件
File file = new File("readWriteDemo.txt");
if(!file.exists()){file.createNewFile();
}// 獲取字節輸出流
OutputStream outputStream = new FileOutputStream(file);
// 轉字符流輸出流,指定 UTF_8 編碼規則,寫入數據
Writer out = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8);
// 輸出字符串
out.write("Hello");
// 輸出換行
out.write("\n");
// 追加信息,append 方法底層本質調用的是 write 方法
out.append("我們一起來學習Java");// 關閉輸出流
out.close();
outputStream.close();
同樣的,當寫入數據的時候,先通過字符流寫入,再轉成字節流輸出。
字符流轉字節流,也需要指定編碼規則,如果沒有指定,會取當系統默認的編碼規則。
五、小結
本文主要圍繞 Java 對磁盤文件的讀取和寫入數據的方式做了一次簡單的總結。
內容難免有所遺漏,歡迎網友留言指出!
六、參考
1、博客園 - 五月的倉頡 - IO和File
七、寫到最后
最近無意間獲得一份阿里大佬寫的技術筆記,內容涵蓋 Spring、Spring Boot/Cloud、Dubbo、JVM、集合、多線程、JPA、MyBatis、MySQL 等技術知識。需要的小伙伴可以點擊如下鏈接獲取,資源地址:技術資料筆記。
不會有人刷到這里還想白嫖吧?點贊對我真的非常重要!在線求贊。加個關注我會非常感激!