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