java中表示一個(gè)文件的File類型詳解
前言
從本篇文章開始,我們將開啟對 Java IO 系統(tǒng)的學(xué)習(xí),本質(zhì)上就是對文件的讀寫操作,聽上去簡單,其實(shí)并不容易。Java 的 IO 系統(tǒng)一直在完善和改進(jìn),設(shè)計(jì)了大量的類,也只有理解了這些類型被設(shè)計(jì)出來的意義以及各自的應(yīng)用場景,才能提升文件 IO 的理解。
那么,第一步就是要解決如何表示一個(gè)文件的問題,Java 世界中「萬物皆對象」,如何將一個(gè)實(shí)際磁盤文件或目錄對應(yīng)到一個(gè) Java 對象則是我們首要的問題。
Java 中使用 File 來抽象一個(gè)文件,無論是普通文件或是目錄,都可對應(yīng)于一個(gè) File 對象。我覺得大家對于 File 這個(gè)類型的定位一定要準(zhǔn)確:它只是抽象的代表了磁盤上的某個(gè)文件或目錄,內(nèi)部實(shí)際上是依賴一個(gè)平臺無關(guān)的本地文件系統(tǒng)類,并且 File 無法對其所表示文件內(nèi)容進(jìn)行任何讀寫操作(那是流做的事情)。
構(gòu)建一個(gè) File 實(shí)例
在實(shí)際介紹 File 實(shí)例構(gòu)造方法之前,我們得先看看它的幾種重要的屬性成員。
private static final FileSystem fs = DefaultFileSystem.getFileSystem();
這是 File 類中最核心的成員,它表示為當(dāng)前系統(tǒng)的文件系統(tǒng) API,所有向磁盤發(fā)出的操作都是基于這個(gè)屬性的。
private final String path;
path 代表了當(dāng)前實(shí)例的完整路徑名稱,如果當(dāng)前的 File 實(shí)例表示的是目錄的話,那么 path 的值就是這個(gè)完整的目錄名稱,如果表示的是純文件的話,那么這個(gè) path 的值等于該文件的完整路徑 + 文件名稱。
public static final char separatorChar = fs.getSeparator(); public static final char pathSeparatorChar = fs.getPathSeparator();
separatorChar 表示的是目錄間的分隔符,pathSeparatorChar 表示的是不同路徑下的分隔符,這兩個(gè)值在不同的系統(tǒng)平臺下不盡相同。例如 Windows 下這兩者的值分別為:「」 和 「;」,其中封號用于分隔多個(gè)不同路徑。
File 類提供了四種不同的構(gòu)造器用于實(shí)例化一個(gè) File 對象,但較為常用的只有三個(gè),我們也著重學(xué)習(xí)前三個(gè)構(gòu)造器。
public File(String pathname)
這是最普遍的實(shí)例化一個(gè) File 對象的方法,pathname 的值可以是一個(gè)目錄,也可以是一個(gè)純文件的名稱。例如:
File file = new File("C:\\Users\\yanga\\Desktop");
File file1 = new File("C:\\Users\\yanga\\Desktop\\a.txt");
File file2 = new File("a.txt");
當(dāng)然也可以顯式指定一個(gè)父路徑:
public File(String parent, String child)
在構(gòu)造器的內(nèi)部,程序會(huì)為我們拼接出一個(gè)完整的文件路徑,例如:
File file = new File("C:\\Users\\yanga\\Desktop","a.txt");
File file1 = new File("C:\\Users\\yanga\\Desktop","java");
第三種構(gòu)造器其實(shí)本質(zhì)上和第二種是一樣的,只不過增加了一個(gè)父類 File 實(shí)例的封裝過程:
public File(File parent, String child)
類似的情況,不再舉例說明了。我們這里并沒有深究這些構(gòu)造器的內(nèi)部具體實(shí)現(xiàn)情況,并不是說它簡單,而是 File 過度依賴本地文件系統(tǒng),很多方法的實(shí)現(xiàn)情況都不得直接看到,所以對于 File 的學(xué)習(xí),定位為熟練掌握即可,具體實(shí)現(xiàn)暫時(shí)沒法深入學(xué)習(xí)。
文件名稱或路徑相關(guān)信息獲取
getName 方法可以用于獲取文件名稱:
public String getName() {
int index = path.lastIndexOf(separatorChar);
if (index < prefixLength) return path.substring(prefixLength);
return path.substring(index + 1);
}
還記得我們的 separatorChar 表示的是什么了嗎?
它表示為路徑分隔符,Windows 中為符號「」,path 屬性存儲(chǔ)的當(dāng)前 File 實(shí)例的完整路徑名稱,所以最后一次出現(xiàn)的位置后面所有的字符必然是我們的文件名稱。
當(dāng)然你一定發(fā)現(xiàn)了,對于純文件來說,該方法能夠返回文件的簡單名稱,而對于一個(gè)目錄而言,返回值將會(huì)是最近的目錄名。例如:
File file = new File("C:\\Users\\yanga\\Desktop\\a.txt");
System.out.println(file.getName());
File file1 = new File("C:\\Users\\yanga\\Desktop");
System.out.println(file1.getName());
輸出結(jié)果不會(huì)出乎你的意料:
a.txt Desktop
getParent 方法用于返回當(dāng)前文件的父級目錄,無論你是純文件或是目錄,你終有你的父目錄(當(dāng)然,虛擬機(jī)生成的臨時(shí)文件自然不是)。
public String getParent() {
int index = path.lastIndexOf(separatorChar);
if (index < prefixLength) {
if ((prefixLength > 0) && (path.length() > prefixLength))
return path.substring(0, prefixLength);
return null;
}
return path.substring(0, index);
}
方法的實(shí)現(xiàn)很簡單,不再贅述。
getPath 方法可以返回當(dāng)前 File 實(shí)例的完整文件名稱:
public String getPath() {
return path;
}
以下是一些有關(guān)目錄的相關(guān)操作,實(shí)現(xiàn)比較簡單,此處簡單羅列了:
- public boolean isAbsolute():是否為絕對路徑
- public String getAbsolutePath():獲取當(dāng)前 File 實(shí)例的絕對路徑
- public String getCanonicalPath():返回當(dāng)前 File 實(shí)例的標(biāo)準(zhǔn)路徑
這里我們需要對 getCanonicalPath 做一點(diǎn)解釋,什么叫標(biāo)準(zhǔn)路徑,和絕對路徑有區(qū)別嗎?
一般而言,「../」表示源文件所在目錄的上一級目錄,「../../」表示源文件所在目錄的上上級目錄,并以此類推。getAbsolutePath 方法不會(huì)做這種轉(zhuǎn)換的操作,而 getCanonicalPath 方法則會(huì)將這些特殊字符進(jìn)行識別并取合適的語義。
例如:
File file = new File("..\\a.txt");
System.out.println(file.getAbsolutePath());
System.out.println(file.getCanonicalPath());
輸出結(jié)果:
C:\Users\yanga\Desktop\Java\workspace2017\TestFile\..\a.txt
C:\Users\yanga\Desktop\Java\workspace2017\a.txt
前者會(huì)將「..\a.txt」作為文件路徑名稱的一部分,而后者卻能夠識別「..\a.txt」表示的是「a.txt」位于當(dāng)前目錄的上級目錄中。這就是兩者最大的不同之處,適合不同的情境。
文件的屬性信息獲取
這部分的文件操作其實(shí)很簡單,無非是一些文件權(quán)限的問題,是否可讀,是否可寫,是否為隱藏文件等。下面我們具體看看這些方法:
- public boolean canRead():該抽象的 File 實(shí)例對應(yīng)的文件是否可讀
- public boolean canWrite():該抽象的 File 實(shí)例對應(yīng)的文件是否可寫
- public boolean exists():該抽象的 File 實(shí)例對應(yīng)的文件是否實(shí)際存在
- public boolean isDirectory():該抽象的 File 實(shí)例對應(yīng)的文件是否是一個(gè)目錄
- public boolean isFile():該抽象的 File 實(shí)例對應(yīng)的文件是否是一個(gè)純文件
- public boolean isHidden():該抽象的 File 實(shí)例對應(yīng)的文件是否是一個(gè)隱藏文件
- public long length():文件內(nèi)容所占的字節(jié)數(shù)
需要說明一點(diǎn)的是,length 方法對于純文件來說,可以正確返回該文件的字節(jié)總數(shù),但是對于一個(gè)目錄而言,返回值將會(huì)是一個(gè)「unspecified」的數(shù)值,既不是目錄下所有文件的總字節(jié)數(shù),也不是零,只是一個(gè)未被說明的數(shù)值,沒有意義。
文件的操作
文件的操作無外乎「增刪改查」,下面我們一起來看看。
- public boolean createNewFile():根據(jù)抽象的 File 對象創(chuàng)建一個(gè)實(shí)際存在的磁盤文件
- public boolean delete():刪除該 File 對象對應(yīng)的磁盤文件,刪除失敗會(huì)返回 false
當(dāng)然,處理上述兩個(gè)簡單的新建和刪除操作,F(xiàn)ile 類還提供了所謂「查詢」操作,這個(gè)我們要好好學(xué)習(xí)一下。例如:
public String[] list() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkRead(path);
}
if (isInvalid()) {
return null;
}
return fs.list(this);
}
這個(gè)方法會(huì)檢索出當(dāng)前實(shí)例所代表的目錄下所有的「純文件」和「目錄」簡單名稱集合。例如:
File file = new File("C:\\Users\\yanga\\Desktop");
String[] list = file.list();
for (String str : list){
System.out.println(str);
}
程序的輸出結(jié)果會(huì)打印我電腦桌面目錄下所有的文件的簡單名稱,就不給大家看了。
需要注意一點(diǎn),如果我們的 File 實(shí)例對應(yīng)的不是一個(gè)目錄,而是一個(gè)純文件,那么 list 將返回 null。
接著,我們再看一個(gè)檢索目錄文件的方法:
public String[] list(FilenameFilter filter) {
String names[] = list();
if ((names == null) || (filter == null)) {
return names;
}
List<String> v = new ArrayList<>();
for (int i = 0 ; i < names.length ; i++) {
if (filter.accept(this, names[i])) {
v.add(names[i]);
}
}
return v.toArray(new String[v.size()]);
}
這個(gè)方法其實(shí)是 list 的重載版本,它允許傳入一個(gè)過濾器用于檢索目錄時(shí)只篩選我們需要的文件及目錄。
而這個(gè) FilenameFilter 接口的定義卻是如此簡單:
public interface FilenameFilter {
boolean accept(File dir, String name);
}
只需要重寫這個(gè) accept 方法即可,list 的 for 循環(huán)每獲取一個(gè)文件或目錄就會(huì)嘗試著先調(diào)用這個(gè)過濾方法,如果通過篩選,才會(huì)將當(dāng)前文件的簡單名稱添加進(jìn)返回集合中。
所以這個(gè) accept 方法的重寫就決定著哪些文件能夠通過篩選,哪些則不能。我們看個(gè)例子:
我的桌面上 test 文件夾下文件情況如下:

File file = new File("C:\\Users\\yanga\\Desktop\\test");
String[] list = file.list(
new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
// dir 代表的當(dāng)前 File 對象
//name 是當(dāng)前遍歷的文件項(xiàng)的簡單名稱
if (!name.endsWith(".txt"))
return false;
else
return true;
}
}
);
for (String str : list){
System.out.println(str);
}
這里呢,我們使用匿名內(nèi)部類創(chuàng)建一個(gè) FilenameFilter 的子類實(shí)例,然后實(shí)現(xiàn)了它的 accept 方法,具體的實(shí)現(xiàn)很簡單,過濾掉所有的目錄并取出所有純文件的簡單名稱。
最后輸出結(jié)果如下:
3.txt
4.txt
當(dāng)然,F(xiàn)ile 類中還提供了兩個(gè)「變種」list 方法,例如:
- public File[] listFiles()
- public File[] listFiles(FilenameFilter filter)
它們不再返回目標(biāo)目錄下的「純文件」和「目錄」的簡單名稱,而返回它們所對應(yīng)的 File 對象,其實(shí)也沒什么,目標(biāo)目錄 + 簡單名稱 即可構(gòu)建出這些 File 實(shí)例了。
所以,本質(zhì)上說,list 方法并不會(huì)遍歷出目標(biāo)目錄下的所有文件,即目標(biāo)目錄的子目錄中的文件并不會(huì)被訪問遍歷。
所以你應(yīng)當(dāng)思考如何完成目標(biāo)目錄下所有文件的遍歷,包含一級子目錄下的深層次文件的遍歷。文末將給出答案。
接下來的兩個(gè)方法和文件夾的創(chuàng)建有關(guān):
- public boolean mkdir()
- public boolean mkdirs()
兩者都是依據(jù)的當(dāng)前 File 實(shí)例創(chuàng)建文件夾,關(guān)于它們的不同點(diǎn),我們先看一段代碼:
File file = new File("C:\\Users\\yanga\\Desktop\\test2");
System.out.println(file.mkdir());
File file2 = new File("C:\\Users\\yanga\\Desktop\\test3\\hello");
System.out.println(file2.mkdir());
其中,test2 和 test3 在程序執(zhí)行之前都不存在。
輸出結(jié)果如下:
true
false
為什么后者創(chuàng)建失敗了?
這源于 mkdir 方法一次只能創(chuàng)建一個(gè)文件夾,倘若給定的目錄的父級或更上層目錄存在未被創(chuàng)建的目錄,那么將導(dǎo)致創(chuàng)建失敗。
而 mkdirs 方法就是用于解決這種情境的,它會(huì)創(chuàng)建目標(biāo)路徑上所有未創(chuàng)建的目錄,看代碼:
File file3 = new File("C:\\Users\\yanga\\Desktop\\test3\\hello\\231");
System.out.println(file3.mkdirs());
即便我們 test3 文件夾就不存在,程序運(yùn)行之后,test3、hello、231 這三個(gè)文件夾都會(huì)被創(chuàng)建出來。
除此之外,F(xiàn)ile 還有一類創(chuàng)建臨時(shí)文件的方法,所謂臨時(shí)文件即:運(yùn)行期存在,虛擬機(jī)關(guān)閉時(shí)銷毀。大家可以自行研究,使用上還是比較簡單的,這里不再贅述了。
至此,有關(guān) File 這個(gè)文件類型,我們大致學(xué)習(xí)了一下,想必大家都會(huì)或多或少的感覺到將純文件和目錄使用同一個(gè)類型進(jìn)行表示的設(shè)計(jì)似乎有些混亂不合理。知道 jdk1.7 sun 推出了 Files 和 Path 分離了文件和目錄,我們后續(xù)文章會(huì)詳細(xì)學(xué)習(xí)一下。
文章中的所有代碼、圖片、文件都云存儲(chǔ)在我的 GitHub 上:
(https://github.com/SingleYam/overview_java)
大家也可以選擇通過本地下載。
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
相關(guān)文章
淺談mybatisPlus的Ipage分頁和map參數(shù)的問題
這篇文章主要介紹了mybatisPlus的Ipage分頁和map參數(shù)的問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12
Java使用正則表達(dá)式截取重復(fù)出現(xiàn)的XML字符串功能示例
這篇文章主要介紹了Java使用正則表達(dá)式截取重復(fù)出現(xiàn)的XML字符串功能,涉及java針對xml字符串及指定格式字符串的正則匹配相關(guān)操作技巧,需要的朋友可以參考下2017-08-08
深入了解Spring中最常用的11個(gè)擴(kuò)展點(diǎn)
我們一說到spring,可能第一個(gè)想到的是?IOC(控制反轉(zhuǎn))?和?AOP(面向切面編程)。除此之外,我們在使用spring的過程中,有沒有發(fā)現(xiàn)它的擴(kuò)展能力非常強(qiáng)。今天就來跟大家一起聊聊,在Spring中最常用的11個(gè)擴(kuò)展點(diǎn)2022-09-09
Java 實(shí)現(xiàn)加密數(shù)據(jù)庫連接的步驟
這篇文章主要介紹了Java 實(shí)現(xiàn)加密數(shù)據(jù)庫連接的步驟,幫助大家更好的理解和使用Java處理數(shù)據(jù)庫,感興趣的朋友可以了解下2020-11-11
Java中String類(字符串操作)的10個(gè)常見問題和解決方法
這篇文章主要介紹了Java中String類(字符串)操作的10個(gè)常見問題,需要的朋友可以參考下2014-04-04

