java7 uri,细数Java8中那些让人纵享丝滑的文件操作
在丑陋的 Java I/O 編程方式誕生多年以后,Java終于簡(jiǎn)化了文件讀寫的基本操作。
打開(kāi)并讀取文件對(duì)于大多數(shù)編程語(yǔ)言來(lái)是非常常用的,由于 I/O 糟糕的設(shè)計(jì)以至于很少有人能夠在不依賴其他參考代碼的情況下完成打開(kāi)文件的操作。
在 Java7 中對(duì)此引入了巨大的改進(jìn)。這些新元素被放在java.nio.file包下面,過(guò)去人們通常把nio中的n理解為new即新的io,現(xiàn)在更應(yīng)該當(dāng)成是non-blocking非阻塞io(io就是input/output輸入/輸出)。java.nio.file庫(kù)終于將 Java 文件操作帶到與其他編程語(yǔ)言相同的水平。最重要的是 Java8 新增的 streams 與文件結(jié)合使得文件操作編程變得更加優(yōu)雅。
看一下文件操作的兩個(gè)基本組件:
文件或者目錄的路徑;
文件本身。
文件和目錄路徑
一個(gè)Path對(duì)象表示一個(gè)文件或者目錄的路徑,是一個(gè)跨操作系統(tǒng)(OS)和文件系統(tǒng)的抽象,目的是在構(gòu)造路徑時(shí)不必關(guān)注底層操作系統(tǒng),代碼可以在不進(jìn)行修改的情況下運(yùn)行在不同的操作系統(tǒng)上。java.nio.file.Paths類包含一個(gè)重載方法static get(),該方法接受一系列String字符串或一個(gè)統(tǒng)一資源標(biāo)識(shí)符(URI)作為參數(shù),并且進(jìn)行轉(zhuǎn)換返回一個(gè)Path對(duì)象。
當(dāng)toString()方法生成完整形式的路徑,getFileName()方法總是返回當(dāng)前文件名。
通過(guò)使用Files工具類,可以測(cè)試一個(gè)文件是否存在,測(cè)試是否是一個(gè)”普通”文件還是一個(gè)目錄等等。”Nofile.txt”這個(gè)示例展示我們描述的文件可能并不在指定的位置;這樣可以允許你創(chuàng)建一個(gè)新的路徑。”PathInfo.java”存在于當(dāng)前目錄中,最初它只是沒(méi)有路徑的文件名,但它仍然被檢測(cè)為”存在”。一旦我們將其轉(zhuǎn)換為絕對(duì)路徑,我們將會(huì)得到一個(gè)從”C:”盤(因?yàn)槲覀兪窃赪indows機(jī)器下進(jìn)行測(cè)試)開(kāi)始的完整路徑,現(xiàn)在它也擁有一個(gè)父路徑。
“真實(shí)”路徑的定義在文檔中有點(diǎn)模糊,因?yàn)樗Q于具體的文件系統(tǒng)。例如,如果文件名不區(qū)分大小寫,即使路徑由于大小寫的緣故而不是完全相同,也可能得到肯定的匹配結(jié)果。在這樣的平臺(tái)上,toRealPath()將返回實(shí)際情況下的Path,并且還會(huì)刪除任何冗余元素。
這里你會(huì)看到URI看起來(lái)只能用于描述文件,實(shí)際上URI可以用于描述更多的東西;通過(guò) 維基百科 可以了解更多細(xì)節(jié)。現(xiàn)在我們成功地將URI轉(zhuǎn)為一個(gè)Path對(duì)象。
Path中看到一些有點(diǎn)欺騙的東西,這就是調(diào)用toFile()方法會(huì)生成一個(gè)File對(duì)象。聽(tīng)起來(lái)似乎可以得到一個(gè)類似文件的東西(畢竟被稱為File),但是這個(gè)方法的存在僅僅是為了向后兼容。雖然看上去應(yīng)該被稱為”路徑”,實(shí)際上卻應(yīng)該表示目錄或者文件本身。這是個(gè)非常草率并且令人困惑的命名,但是由于java.nio.file的存在我們可以安全地忽略它的存在。
選取路徑部分片段
Path對(duì)象可以非常容易地生成路徑的某一部分:
可以通過(guò)getName()來(lái)索引Path的各個(gè)部分,直到達(dá)到上限getNameCount()。Path也實(shí)現(xiàn)了Iterable接口,因此我們也可以通過(guò)增強(qiáng)的 for-each 進(jìn)行遍歷。請(qǐng)注意,即使路徑以.java結(jié)尾,使用endsWith()方法也會(huì)返回false。這是因?yàn)槭褂胑ndsWith()比較的是整個(gè)路徑部分,而不會(huì)包含文件路徑的后綴。通過(guò)使用startsWith()和endsWith()也可以完成路徑的遍歷。但是我們可以看到,遍歷Path對(duì)象并不包含根路徑,只有使用startsWith()檢測(cè)根路徑時(shí)才會(huì)返回true。
路徑分析
Files工具類包含一系列完整的方法用于獲得Path相關(guān)的信息。
在調(diào)用最后一個(gè)測(cè)試方法getPosixFilePermissions()之前我們需要確認(rèn)一下當(dāng)前文件系統(tǒng)是否支持Posix接口,否則會(huì)拋出運(yùn)行時(shí)異常。
Paths的增減修改
我們必須能通過(guò)對(duì)Path對(duì)象增加或者刪除一部分來(lái)構(gòu)造一個(gè)新的Path對(duì)象。我們使用relativize()移除Path的根路徑,使用resolve()添加Path的尾路徑(不一定是“可發(fā)現(xiàn)”的名稱)。
對(duì)于下面代碼中的示例,我使用relativize()方法從所有的輸出中移除根路徑,部分原因是為了示范,部分原因是為了簡(jiǎn)化輸出結(jié)果,這說(shuō)明你可以使用該方法將絕對(duì)路徑轉(zhuǎn)為相對(duì)路徑。
這個(gè)版本的代碼中包含id,以便于跟蹤輸出結(jié)果:
目錄
Files工具類包含大部分我們需要的目錄操作和文件操作方法。出于某種原因,它們沒(méi)有包含刪除目錄樹(shù)相關(guān)的方法
刪除目錄樹(shù)的方法實(shí)現(xiàn)依賴于Files.walkFileTree(),”walking” 目錄樹(shù)意味著遍歷每個(gè)子目錄和文件。Visitor 設(shè)計(jì)模式提供了一種標(biāo)準(zhǔn)機(jī)制來(lái)訪問(wèn)集合中的每個(gè)對(duì)象,然后你需要提供在每個(gè)對(duì)象上執(zhí)行的操作。
此操作的定義取決于實(shí)現(xiàn)的FileVisitor的四個(gè)抽象方法,包括:
preVisitDirectory()
在訪問(wèn)目錄中條目之前在目錄上運(yùn)行。
visitFile():調(diào)用目錄中的文件
visitFileFailed()
調(diào)用無(wú)法被訪問(wèn)的文件。如果該文件的屬性不能被讀取,該文件是無(wú)法打開(kāi)一個(gè)目錄,以及其他原因,該方法被調(diào)用。
postVisitDirectory()
在訪問(wèn)目錄中條目之后在目錄上運(yùn)行,包括所有的子目錄。
為了簡(jiǎn)化,java.nio.file.SimpleFileVisitor提供了所有方法的默認(rèn)實(shí)現(xiàn)
在自己的匿名內(nèi)部類中,只需要重寫非標(biāo)準(zhǔn)行為的方法:visitFile()和postVisitDirectory()實(shí)現(xiàn)刪除文件和刪除目錄。兩者都應(yīng)該返回標(biāo)志位決定是否繼續(xù)訪問(wèn)
作為探索目錄操作的一部分,現(xiàn)在我們可以有條件地刪除已存在的目錄。在以下例子中,makeVariant()接受基本目錄測(cè)試,并通過(guò)旋轉(zhuǎn)部件列表生成不同的子目錄路徑。這些旋轉(zhuǎn)與路徑分隔符sep使用String.join()貼在一起,然后返回一個(gè)Path對(duì)象。
如果你對(duì)于已經(jīng)存在的目錄調(diào)用createDirectory()將會(huì)拋出異常。createFile()使用參數(shù)Path創(chuàng)建一個(gè)空文件;resolve()將文件名添加到test Path的末尾。
我們嘗試使用createDirectory()來(lái)創(chuàng)建多級(jí)路徑,但是這樣會(huì)拋出異常,因?yàn)檫@個(gè)方法只能創(chuàng)建單級(jí)路徑。我已經(jīng)將populateTestDir()作為一個(gè)單獨(dú)的方法,因?yàn)樗鼘⒃诤竺娴睦又斜恢赜谩?duì)于每一個(gè)變量variant,我們都能使用createDirectories()創(chuàng)建完整的目錄路徑,然后使用此文件的副本以不同的目標(biāo)名稱填充該終端目錄。然后我們使用createTempFile()生成一個(gè)臨時(shí)文件。
在調(diào)用populateTestDir()之后,我們?cè)趖est目錄下面下面創(chuàng)建一個(gè)臨時(shí)目錄。請(qǐng)注意,createTempDirectory()只有名稱的前綴選項(xiàng)。與createTempFile()不同,我們?cè)俅问褂盟鼘⑴R時(shí)文件放入新的臨時(shí)目錄中。你可以從輸出中看到,如果未指定后綴,它將默認(rèn)使用”.tmp”作為后綴。
為了展示結(jié)果,我們首次使用看起來(lái)很有希望的newDirectoryStream(),但事實(shí)證明這個(gè)方法只是返回test目錄內(nèi)容的 Stream 流,并沒(méi)有更多的內(nèi)容。要獲取目錄樹(shù)的全部?jī)?nèi)容的流,請(qǐng)使用Files.walk()。
文件系統(tǒng)
為了完整起見(jiàn),我們需要一種方法查找文件系統(tǒng)相關(guān)的其他信息。在這里,我們使用靜態(tài)的FileSystems工具類獲取”默認(rèn)”的文件系統(tǒng),但也可以在Path對(duì)象上調(diào)用getFileSystem()以獲取創(chuàng)建該P(yáng)ath的文件系統(tǒng)。
可以獲得給定 URI 的文件系統(tǒng),還可以構(gòu)建新的文件系統(tǒng)(對(duì)于支持它的操作系統(tǒng))。
路徑監(jiān)聽(tīng)
通過(guò)WatchService可以設(shè)置一個(gè)進(jìn)程對(duì)目錄中的更改做出響應(yīng)。
一旦我們從FileSystem中得到了WatchService對(duì)象,我們將其注冊(cè)到test路徑以及我們感興趣的項(xiàng)目的變量參數(shù)列表中,可以選擇
ENTRY_CREATE
ENTRY_DELETE
ENTRY_MODIFY(其中創(chuàng)建和刪除不屬于修改)。
接下來(lái)對(duì)watcher.take()的調(diào)用會(huì)在發(fā)生某些事情之前停止所有操作,所以我們希望deltxtfiles()能夠并行運(yùn)行以便生成我們感興趣的事件。為了實(shí)現(xiàn)這個(gè)目的,通過(guò)調(diào)用Executors.newSingleThreadScheduledExecutor()產(chǎn)生一個(gè)ScheduledExecutorService對(duì)象,然后調(diào)用schedule()方法傳遞所需函數(shù)的方法引用,并且設(shè)置在運(yùn)行之前應(yīng)該等待的時(shí)間。
此時(shí),watcher.take()將等待并阻塞在這里。當(dāng)目標(biāo)事件發(fā)生時(shí),會(huì)返回一個(gè)包含WatchEvent的Watchkey對(duì)象。
如果說(shuō)”監(jiān)視這個(gè)目錄”,自然會(huì)包含整個(gè)目錄和下面子目錄,但實(shí)際上的:只會(huì)監(jiān)視給定的目錄,而不是下面的所有內(nèi)容。如果需要監(jiān)視整個(gè)樹(shù)目錄,必須在整個(gè)樹(shù)的每個(gè)子目錄上放置一個(gè)Watchservice。
文件查找
粗糙的方法,在 path 上調(diào)用 toString(),然后使用 string 操作查看結(jié)果。
java.nio.file 有更好的解決方案:通過(guò)在 FileSystem 對(duì)象上調(diào)用 getPathMatcher() 獲得一個(gè) PathMatcher,然后傳入感興趣的模式。
模式
glob
glob 比較簡(jiǎn)單,實(shí)際上功能非常強(qiáng)大,因此可以使用 glob 解決許多問(wèn)題。
在 matcher 中,glob 表達(dá)式開(kāi)頭的 **/ 表示“當(dāng)前目錄及所有子目錄”,這在當(dāng)你不僅僅要匹配當(dāng)前目錄下特定結(jié)尾的 Path 時(shí)非常有用。
單 * 表示“任何東西”,然后是一個(gè)點(diǎn),然后大括號(hào)表示一系列的可能性—-我們正在尋找以 .tmp 或 .txt 結(jié)尾的東西
regex
如果問(wèn)題更復(fù)雜,可以使用 regex
文件讀寫
如果一個(gè)文件很“小”,也就是說(shuō)“它運(yùn)行得足夠快且占用內(nèi)存小”,那么 java.nio.file.Files 類中的實(shí)用程序?qū)椭爿p松讀寫文本和二進(jìn)制文件。
Files.readAllLines() 一次讀取整個(gè)文件(因此,“小”文件很有必要),產(chǎn)生一個(gè)List。
只需將 Path 傳遞給 readAllLines()
readAllLines() 有一個(gè)重載版本,包含一個(gè) Charset 參數(shù)來(lái)存儲(chǔ)文件的 Unicode 編碼
Files.write() 被重載以寫入 byte 數(shù)組或任何 Iterable 對(duì)象(它也有 Charset 選項(xiàng)):
如果文件大小有問(wèn)題怎么辦? 比如說(shuō):
文件太大,如果你一次性讀完整個(gè)文件,你可能會(huì)耗盡內(nèi)存。
您只需要在文件的中途工作以獲得所需的結(jié)果,因此讀取整個(gè)文件會(huì)浪費(fèi)時(shí)間。
Files.lines() 方便地將文件轉(zhuǎn)換為行的 Stream:
流式處理,跳過(guò) 13 行,然后選擇下一行并將其打印出來(lái)。
Files.lines() 對(duì)于把文件處理行的傳入流時(shí)非常有用,但是如果你想在 Stream 中讀取,處理或?qū)懭朐趺崔k?這就需要稍微復(fù)雜的代碼:
因?yàn)槲覀冊(cè)谕粋€(gè)塊中執(zhí)行所有操作,所以這兩個(gè)文件都可以在相同的 try-with-resources 語(yǔ)句中打開(kāi)。
PrintWriter 是一個(gè)舊式的 java.io 類,允許你“打印”到一個(gè)文件,所以它是這個(gè)應(yīng)用的理想選擇
總結(jié)
雖然本章對(duì)文件和目錄操作做了相當(dāng)全面的介紹,但是仍然有沒(méi)被介紹的類庫(kù)中的功能——一定要研究 java.nio.file 的 Javadocs,尤其是 java.nio.file.Files 這個(gè)類。
Java 7 和 8 對(duì)于處理文件和目錄的類庫(kù)做了大量改進(jìn)。如果您剛剛開(kāi)始使用 Java,那么您很幸運(yùn)。在過(guò)去,它令人非常不愉快,Java 設(shè)計(jì)者以前對(duì)于文件操作不夠重視才沒(méi)做簡(jiǎn)化。對(duì)于初學(xué)者來(lái)說(shuō)這是一件很棒的事,對(duì)于教學(xué)者來(lái)說(shuō)也一樣。我不明白為什么花了這么長(zhǎng)時(shí)間來(lái)解決這個(gè)明顯的問(wèn)題,但不管怎么說(shuō)它被解決了,我很高興。使用文件現(xiàn)在很簡(jiǎn)單,甚至很有趣,這是你以前永遠(yuǎn)想不到的。
總結(jié)
以上是生活随笔為你收集整理的java7 uri,细数Java8中那些让人纵享丝滑的文件操作的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: java 比较源文件_Beyond Co
- 下一篇: java的字节码无法显示_【java】查