原創:小姐姐味道(微信公衆號ID:xjjdog),歡迎分享,轉載請保留出處。html
從classpath中讀取過文件的人,都知道須要寫一些讀取流的方法,非常繁瑣。最近使用IDEA在打出.
這個符號的時候,一行代碼讓人激動不已:居然提供直接讀出bytes字節的方法。java
byte[] bytes = Test
.class
.getResourceAsStream("/test.txt")
.readAllBytes();
複製代碼
這真是太讓人振奮了,不再用寫一些醜陋的,還容易忘記關閉流的代碼了。node
惋惜的是,代碼提示給了當頭一棒。這須要Java的9版本以上才能支持。這就像一個還有1年壽命的患者看到救命的藥,還須要兩年才能問世的感受,是同樣的。linux
廢話少說,咱們用不了這些函數,難道Java7+的也用不了麼(有些可憐的寶貝們還真用不了)?話說回來,JAVA7+對NIO進行了加強,主要在對文件操做部分作了大量的改進。它體如今,將File操做進行分離、封裝、改進,最終造成Path(Paths)、Files、FileSystem(FileSystems)三個主要類。程序員
咱們親切的叫它NIO2。web
其中,Paths、Files中提供了大量便捷的靜態操做方法;NIO2還提供了有關文件權限(屬性)操做、軟鏈接、文件查找等高級API,使得NIO2具備更全面的文件系統操做接口。正則表達式
新入行的小鮮肉可能一開始就接觸這個了,但對咱們一些老程序員來講,忽然看到這些東西,就像打開了一個新大陸。因此本文面向的是還不知道這些個操做的菜鳥,以及懶癌晚期的老Java程序員。redis
文件系統都是Tree或者層級結構來組織文件的,任何一個節點能夠是一個目錄或者一個文件,在NIO2中稱爲Path,這和原來的File有不少類似之處,只是Path具備更多的表述語義。shell
如下代碼展現了它的基本屬性。安全
Path path = Paths.get("/data/logs/web.log");
//屬性
//獲取路徑中的文件名或者最後一個節點元素
System.out.printf("FileName:%s%n", path.getFileName());
//路徑節點元素的格式
System.out.printf("NameCount:%s%n", path.getNameCount());
//遍歷路徑節點方法1
Iterator<Path> names = path.iterator();
int i = 0;
while (names.hasNext()) {
Path name = names.next();
System.out.printf("Name %s:%s%n",i,name.toString());
i++;
}
//方法2
for(int j = 0; j < path.getNameCount(); j++) {
System.out.printf("Name %s:%s%n",j,path.getName(j));
}
//父路徑
System.out.printf("Parent:%s%n",path.getParent());
//跟路徑,好比"/"、"C:";若是是相對路徑,則返回null。
System.out.printf("Root:%s%n",path.getRoot());
//子路徑,結果中不包含root,前開後閉
System.out.printf("Subpath[0,2]:%s%n",path.subpath(0,2));
複製代碼
結果:
FileName:web.log
NameCount:3
Name 0:data
Name 1:logs
Name 2:web.log
Name 0:data
Name 1:logs
Name 2:web.log
Parent:/data/logs
Root:/
Subpath[0,2]:data/logs
複製代碼
1)好比「/data/logs/./web.log」、「/data/logs/../db」這種包含「冗餘」路徑的,有時候咱們須要轉換爲正常路徑語句,則使用「Path.normalize()」方法,無比的清爽清潔。
Path path = Paths.get("/data/logs/../web.log");
//輸出結果:/data/web.log
System.out.printf("%s%n",path.normalize());
複製代碼
2)若是文件須要被外部資源訪問(resource),你能夠經過Path.toUri()來轉換,path對應的文件或者目錄能夠不存在,此方法不會check異常:
Path path = Paths.get("/data/logs/web.log");
//表示本地文件的uri,輸出結果:file:///data/logs/web.log
System.out.printf("%s%n",path.toUri());
複製代碼
3)toAbsolutePath():若是路徑爲相對路徑,則轉換爲絕對路徑,對於JAVA程序而言,起始路徑爲classpath。此方法不會檢測文件是否真的存在或者有權限。
4)其中toRealPath()是比較重要的方法,不過它會對文件是否存在、訪問權限進行檢測,須要捕獲異常。首先檢測文件是否存在、是否有權限;若是path爲相對路徑,則將會轉換爲絕對路徑,同「3)」;若是是「符號鏈接」文件(軟鏈接),則獲取其實際target路徑(除非指定了NO_FOLLOW_LINKS);若是路徑中包含「冗餘」,則移除,同1)。這個方法,一般用於對「用戶輸入的path」進行校驗和轉換,使用比較多。
5)resolve():路徑合併,當前path與參數進行路徑合併,append。
6)relativize():獲取相對路徑,「/data」與「/data/logs/p1」的相對路徑爲「logs/p1」,反之爲「../../」。
Files類中提供了大量靜態方法,用於實現文件(目錄)的建立、複製、遷移、刪除以及訪問文件數據等操做。
Files.exists(Path)和notExists(Path)兩個方法,這兩個方法都會實際檢測文件或者目錄是否存在、以及是否有訪問權限。注意:!exist() 與notExists()並不徹底相等,exist可能有三種狀態:若是不存在或者安全校驗不經過則返回false,若是返回true則表示文件確實存在且有權限。notExists()檢測相似,對於沒有經過安全校驗的也會返回false;當exists與notExists同時返回false時,說明文件不能夠驗證(即無權限),因此一般這兩個方法須要同時使用。
判斷文件(目錄)具備讀、寫、執行的權限,能夠經過以下方法:
Path path = Paths.get("data/logs/web.log");
boolean isRegularExecutableFile = Files.isRegularFile(path) &
Files.isReadable(path) & Files.isExecutable(path);
複製代碼
有時候,兩個不一樣的path,會指向同一個文件,好比當一個path是軟鏈接時,此時可使用Files.isSameFile(p1,p2)來檢測,固然你能夠經過Path + LinkOption相關組合獲取target實際path來比較。
delete和deleteIfExists兩個方法都可刪除文件,前者嘗試刪除的文件若是不存在則會拋出異常。若是文件是軟鏈接,則只刪除鏈接文件而不會刪除target文件,若是path爲目錄,則目錄須要爲空,不然刪除失敗(IOException)。在刪除操做以前,最後作一些常規的檢測,好比文件是否存在(有權限)、目錄是否爲空等。稍後咱們再介紹「遞歸刪除目錄樹和文件」。
copy(Path,Path,CopyOption...)方法能夠複製文件,不過,須要注意CopyOption的相關選項。
當copy一個軟鏈接文件時,默認將會複製target文件,若是隻想複製軟鏈接文件而不是target內容,能夠指定NOFOLLOW_LINKS選項。CopyOption的實現類爲StandardCopyOption,此外CopyOption也擴展了LinkOption,即包含NOFOLLOW_LINKS選項。以下爲CopyOption選項列表:
1)REPLACE_EXISTING:若是目標文件已經存在,則直接覆蓋;若是目標文件是個軟鏈接,則軟鏈接文件自己被覆蓋(而非鏈接文件的target文件);若是複製的是目錄,且目標目錄不爲空時,則會拋出異常(DirectoryNotEmptyException),稍後介紹「遞歸複製目錄樹和文件」。此參數一般必選。複製目錄時,目標目錄會自動建立,源目錄中若是有文件,則不會複製文件,只會建立空的目標目錄。source和target,要麼同時是目錄、要麼同時是文件。
2)COPY_ATTRIBUTES:複製文件時,也同時複製目標文件的屬性(metadata),對於文件屬性(FileAttribute)的支持依賴於文件系統(和平臺),不過lastModifiedTime一般會被複制。
3)NOFOLLOW_LINKS:繼承自LinkOption,表示若是文件是軟鏈接,則不followed,即只複製鏈接文件,不復制其target實際文件內容。
4)ATOMIC_MOVE:只支持move操做,copy不支持。
Path source = Paths.get("/data/logs/web.log");
Path target = Paths.get("/data/logs/web.log.copy");
Files.copy(source,target,REPLACE_EXISTING,COPY_ATTRIBUTES,NOFOLLOW_LINKS);
複製代碼
move(Path,Path,CopyIOption),基本原則同copy。須要注意,若是是目錄,目錄中包含文件時也能夠移動的(這可能依賴於平臺),子目錄也一塊兒移動,可是目標目錄必須爲空(DirectoryNotEmptyException)。基本語義同「mv -rf」,目標目錄不須要提早建立,move結束後,源目錄將不存在。支持兩種選項:
1)REPLACE_EXISTING:若是目標文件已存在,則覆蓋;若是目標文件是軟鏈接,則鏈接文件被覆蓋可是其指向不會受影響。
2)ATOMIC_MOVE:原子複製,須要平臺的文件系統支持(不支持則拋出異常),指定此參數時其餘選項將被忽略;若是文件不能被原子複製(或者替換),則會拋出AtomicMoveNotSupportedException。
Files類中提供了多個靜態的方法,用於直接讀寫文件。以下爲文件打開的幾個選項參(StandardOpenOptions):
1)WRITE: 打開文件用於write訪問。
2)APPEND:在文件尾部追加數據,伴隨用於WRITE或CREATE選項。
3)TRUNCATE_EXISTING:將文件truncate爲空,伴隨用於WRITE選項。好比,文件存在時,將文件數據清空並從新寫入。
4)CREATE_NEW:建立新文件,若是文件已存在則拋出異常。
5)CREATE:若是文件已存在則直接打開,不然建立文件。
6)DELETE_ON_CLOSE:當文件操做關閉時則刪除文件(close方法或者JVM關閉時),此選項適用於臨時文件(臨時文件不該該被其餘進程併發訪問)。
7)SPARSE:建立一個「稀疏」文件,伴隨使用CREATE_NEW,適用於某些特殊的文件系統好比NTFS,這些大文件容許出現「gaps」(空洞)在某些狀況下能夠提升性能且這些gaps不消耗磁盤空間。
8)SYNC:對文件內容(data)或者metadata的修改,都會同步到底層存儲。
9)DSYNC:對文件內容的修改,會同步到底層存儲。
對於一些小文件(M級別),一般咱們但願一次所有讀取全部內容,而再也不使用傳統的方式迭代讀取。
//所有讀取小文件中的數據
//Files.readAllBytes(Paths.get("/data/web.log"));
List<String> lines = Files.readAllLines(Paths.get("/data/web.log"),Charset.forName("utf-8"));
//將準備好的數據,直接所有寫入文件。(打開、寫入)
Files.write(Paths.get("/data/web-1.log"),lines,Charset.forName("utf-8"),
StandardOpenOption.APPEND,
StandardOpenOption.CREATE));
//傳統操做
try (BufferedReader reader = Files.newBufferedReader(Paths.get("/data/web.log"))) {
while (true) {
String line = reader.readLine();
if (line == null) {
break;
}
System.out.println(line);
}
} catch (IOException e) {
//
}
//傳統操做
try (BufferedWriter writer = Files.newBufferedWriter(Paths.get("/data/web.log"),Charset.forName("utf-8"),StandardOpenOption.APPEND,
StandardOpenOption.CREATE)) {
for(String line : lines) {
writer.write(line);
}
} catch (IOException e) {
//
}
複製代碼
此外Files中還提供了基於buffer的channel操做,返回類型爲SeekableByteChannel,這種操做一般適用於讀或者寫,以及數據能夠基於buffer進行拆封包,其餘特性相似「隨機訪問文件」。(Files.newByteChannel(),功能同File.open())
Path path = Paths.get("/data/web.log");
//建立一個普通文件,若是已存在則拋出異常,文件屬性爲默認。
Files.createFile(path);
//建立臨時文件,臨時文件的目錄和前綴能夠爲null,後綴若是爲null時默認使用".tmp";
Files.createTempFile(null,null,".tmp");
//建立一個軟鏈接文件
Files.createSymbolicLink(Paths.get("/data/link.log"),Paths.get("/data/web.log"));
複製代碼
BasicFileAttributes基本接口,提供了一些基本的文件metadata,好比lastAccessTime、lastModifiedTime等,它的實現類因平臺而已有:DosFileAttributes、PosixFileAttribute、UnixFileAttribute等;不一樣平臺所能支持的屬性有所不一樣。(在跨平臺場景下,你可能須要使用FileStore來判斷當前文件系統是否支持相應的FileAttributeView)
Path path = Paths.get("/data/logs/web.log");
BasicFileAttributes attributes = Files.readAttributes(path,BasicFileAttributes.class);
System.out.println("regular file:" + attributes.isRegularFile());
System.out.println("directory:" + attributes.isDirectory());
System.out.println("symbolic link:" + attributes.isSymbolicLink());
System.out.println("modified time:" + attributes.lastModifiedTime().toMillis());
//修改系統更新屬性
Files.setLastModifiedTime(path,FileTime.fromMillis(System.currentTimeMillis()));
//修改其餘屬性
Files.setAttribute(path,"dos:hidden",true);
複製代碼
屬性名格式爲「view-name:attribute-name」,好比「dos:hidden」;其中合法的view-name目前有「basic」、「posix」、「unix」、「owner」(全部者信息,權限),屬性的列表須要根據本身的平臺對應相應的Attributes類,不然會致使設置異常。
Path path = Paths.get("/data/logs/web.log");
PosixFileAttributes attributes = Files.readAttributes(path,PosixFileAttributes.class);
//用戶組和權限
UserPrincipal userPrincipal = attributes.owner();
System.out.println(userPrincipal.toString());
GroupPrincipal groupPrincipal = attributes.group();
System.out.println(groupPrincipal.toString());
Set<PosixFilePermission> permissions = attributes.permissions();
//將權限轉換爲文件屬性,用於建立新的文件,目前文件權限也是一種屬性
FileAttribute<Set<PosixFilePermission>> fileAttribute = PosixFilePermissions.asFileAttribute(permissions);
Files.createFile(Paths.get("/data/test.log"),fileAttribute);
//修改文件權限,能夠在permissions中增減權限列表,枚舉
Files.setPosixFilePermissions(path,permissions);
複製代碼
從權限字符串中,構建權限列表
Set<PosixFilePermission> permissions = PosixFilePermissions.fromString("-rw-r--r--");
Files.setPosixFilePermissions(path, permissions);
複製代碼
修改文件(目錄)的全部者或者所在組:
Path path = Paths.get("/data/logs/web.log");
//首先找到系統中的其餘用戶,根據用戶名
UserPrincipal userPrincipal = path.getFileSystem().getUserPrincipalLookupService().lookupPrincipalByName("userName");
Files.setOwner(path,userPrincipal);
//或者
Files.getFileAttributeView(path,FileOwnerAttributeView.class).setOwner(userPrincipal);
//修改group
GroupPrincipal groupPrincipal = path.getFileSystem().getUserPrincipalLookupService().lookupPrincipalByGroupName("groupName");
Files.getFileAttributeView(path,PosixFileAttributeView.class).setGroup(groupPrincipal);
複製代碼
用戶自定義文件屬性(即擴展文件屬性),以及屬性值的長度,這取決於底層文件系統或者平臺是否支持。目前,主流的平臺和文件系統都支持擴展文件屬性,好比FreeBSD(ZFS)、Linux(ext三、ext四、ZFS)、MacOS等。默認狀況下,此操做是開啓的,若是已關閉,能夠經過「sudo mount -o remount,user_xattr {你的文件系統掛載路徑}」,不然也會拋出UnsupportedOperationException。
Path path = Paths.get("/data/logs/web.log");
UserDefinedFileAttributeView view = Files.getFileAttributeView(path,UserDefinedFileAttributeView.class);
String name = "user.mimetype";
int size = view.size(name);//我我的認爲JDK應該直接支持獲取屬性值,而不是再周折一番
ByteBuffer buffer = ByteBuffer.allocate(size);
view.read(name,buffer);
buffer.flip();
String value = Charset.defaultCharset().decode(buffer).toString();
System.out.println(value);
//其餘操做,好比list獲取全部屬性的列表等。
//寫入或者跟新自定義屬性
view.write(name,Charset.defaultCharset().encode("text/html"));
複製代碼
FileStore是新增的API,用於描述底層存儲系統,一個平臺有多個FileStore,咱們能夠經過FileSystem獲取FileStore的列表,以及每一個store的存儲狀態、文件列表等。
Path path = Paths.get("/data/logs/web.log");
path.getFileSystem().getFileStores();//獲取文件所屬的文件系統的全部的存儲器。
//當前文件系統所能支持的FileAttributeView,此後能夠對文件使用相應的view獲取或者修改屬性
Set<String> viewNames = FileSystems.getDefault().supportedFileAttributeViews();
System.out.println(viewNames);//basic,unix,posix,owner,dos等
//或者,全局
//遍歷全部的磁盤存儲
Iterable<FileStore> fileStores = FileSystems.getDefault().getFileStores();//獲取默認文件系統的全部存儲器
for(FileStore store : fileStores) {
System.out.println("\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-");
System.out.println("className:" + store.getClass().getName());
System.out.println("name:" + store.name());//磁盤名稱
System.out.println("type:" + store.type());//類型
System.out.println("readOnly:" + store.isReadOnly());//是否爲只讀
System.out.println("usableSpace:" + store.getUsableSpace() + "/" + store.getTotalSpace());
boolean supported = store.supportsFileAttributeView(BasicFileAttributeView.class);
//或者
//boolean supported = store.supportsFileAttributeView("basic");
//fileStore的屬性,不一樣於FileAttributes,這些屬性應該與FileStore的各個實現類對應。
Long totalSpace = (Long)store.getAttribute("totalSpace");
System.out.println(totalSpace);
複製代碼
能夠獲取每一個FileStore支持的FileAttributeView,此後便可經過Files.getFileAttributeView()來獲取相應的視圖類。每一個view都有簡寫名稱,好比BasicFileAttributeView.name()返回其簡寫名稱爲「basic」。目前JDK支持不少FileAttributeView,好比BasicFileAttributeView、UnixFileAttributeView等等,以及容許自定義的UserDefinedFileAttributeView等。JDK將獲取文件屬性、修改文件屬性的操做所有基於對應的FileAttributeView類來實現。
PosixFileAttributeView view = Files.getFileAttributeView(path,PosixFileAttributeView.class);
PosixFileAttributes fileAttributes = view.readAttributes();
複製代碼
此外,根據FileStore的底層存儲不一樣,有多種實現,能夠參看FileStore的實現類,好比UnixFileStore、BsfFileStore(Mac)等,每一個FileStore所能支持的attribute也不太相同,須要根據其對應的實現類獲取,也可使用FileStore.getAttribute()來獲取,可是屬性名須要與類中支持的屬性名對應。
JAVA中目錄也用Path表示,其基本屬性與File同樣。
//遍歷文件系統的全部根目錄
Iterable<Path> roots = FileSystems.getDefault().getRootDirectories();
for(Path root : roots) {
System.out.print(root);
}
複製代碼
Path dir = Paths.get("/data/xyz");
Files.createDirectories(dir);
Files.createDirectory(dir);
複製代碼
其中createDirectory()方法是一個「嚴格校驗」的方法,若是父路徑不存在則會拋出異常,若是路徑已經存在或者同名文件存在則會拋出異常,簡單來講此方法只能建立最後一級目錄(且此前不存在)。對於createDirectories()方法,比較兼容,會逐層校驗並建立父路徑,若是不存在則建立。
建立目錄時,也能夠像文件那樣,指定文件屬性(包括權限)
Path dir = Paths.get("/data/xyz/12x/123x");
Set<PosixFilePermission> permissions = PosixFilePermissions.fromString("\-rw\-rw\-\-\-\-");
FileAttribute<Set<PosixFilePermission>> fileAttribute = PosixFilePermissions.asFileAttribute(permissions);
Files.createDirectories(dir,fileAttribute);
複製代碼
Path dir = Paths.get("/data");
DirectoryStream<Path> stream = Files.newDirectoryStream(dir);
for (Path path : stream) {
System.out.println(path);
}
stream.close();
複製代碼
stream方式會遍歷指定目錄下的全部文件,包括文件。目錄。鏈接、隱藏文件或者目錄等。
此外,JDK還支持基於filter模式,在遍歷時過濾「非必要」的文件或者目錄。
Path dir = Paths.get("/data");
DirectoryStream.Filter<Path> filter = new DirectoryStream.Filter<Path>() {
@Override
public boolean accept(Path entry) throws IOException {
return Files.isRegularFile(entry);
}
};
DirectoryStream<Path> stream = Files.newDirectoryStream(dir,filter);
for (Path path : stream) {
System.out.println(path);
}
stream.close();
複製代碼
NIO2中新增支持了基於Glob的文件過濾器,一種相似於正則表達式的匹配語法;glob是來自unlix(shell指令)用於文件匹配的表達式,不少主流語言和平臺(dos、window)都支持,不過不一樣語言對glob的支持程度不一樣(須要注意)。
一、*:匹配任意多個任意字符,包括空字符(none)。好比「/data/*.log」匹配「data」目錄下全部以「.log」結尾的文件。
二、**:相似於*,匹配任意多個字符,不過它能夠越過目錄分割符,此語法一般用於匹配全路徑。好比:「/data/**」
三、?:只匹配一個字符。
四、\:轉義符,用於轉義特殊字符,好比「\」、「-」、「{」、「}」、「[」等等。好比須要匹配「{」那麼其字面表達式爲「\{」,「\\」用於匹配單斜槓。
五、!:非,不包含,一般配合[]使用。(貌似不支持^)
六、{}:指定一組子規則,好比:
1){sum,moon,stars}:匹配「sun」或者「moon」或者「starts」(其一便可),子規則使用「,」分割。
2){temp*,tmp*}:匹配以temp或者tmp開頭的全部字符串。
七、[]:匹配一組字符串中的單個字符,若是字符串集中包含「-」則匹配區間中單個字符。好比[abc]匹配「a」或者「b」或者「c」,[a-z]匹配a到z小寫字符中的一個,[0-9]匹配0~9任意一個數字。能夠混合使用,好比[abce-g]匹配「a」、「b」、「c」、「e」、「f」、「g」,[!a-c]匹配除a、b、c以外的任意一個字符。複合子規則使用「,」分割,好比[a-z,0-9]匹配a~z或者0~9任意一個字符。
在[]中,「*」、「?」、「\」只匹配其本身(字面),若是「-」在[]內且是第一個字符或者在!以後,也匹配本身。
八、文件中的前導字符、「.」將做爲普通字符對待。好比「*」能夠匹配「.tmp」這個文件,FIles.isHidden()能夠用來檢測此文件爲隱藏文件。
九、其餘全部字符匹配其本身(取決於因平臺而已的實現方式,包括路徑分隔符等)。
示例:
一、*.html匹配任意已「.html」結尾的文件。
二、???:匹配任意三個字符(包括數字字符)
三、*[0-9]*:匹配包含一個數字的任意多個字符。
四、*.{htm,html},匹配任意以「html」或者「htm」結尾的字符串。
GLobbing表達式,一種比較便捷的過濾策略,對於一些簡單的操做(主要是隻根據文件或者路徑名特性匹配時),能夠不使用Filter的狀況下完成,固然glob的內部實現仍然是基於封裝的Filter來實現(PathMatcher);可是glob語法相對簡單,JDK NIO2有關文件過濾表達式,能夠同時支持glob和正則表達式。稍後介紹如何使用PathMatcher來遍歷文件或者目錄樹。
Path dir = Paths.get("/data");
//內部,默認會對glob表達式增長前綴,glob,爲了兼容PathMatcher
DirectoryStream<Path> stream = Files.newDirectoryStream(dir,"\*.txt");
for (Path path : stream) {
System.out.println(path);
}
stream.close();
複製代碼
硬鏈接(或者鏈接):
1)文件有相同的 inode 及 data block;
2)只能對已存在的文件進行建立;
3)不能交叉文件系統進行硬連接的建立;
4)不能對目錄進行建立,只可對文件建立;
5)刪除一個硬連接文件並不影響其餘有相同 inode 號的文件。
軟鏈接(符號鏈接):軟連接與硬連接不一樣,若文件用戶數據塊中存放的內容是另外一文件的路徑名的指向,則該文件就是軟鏈接。軟連接就是一個普通文件,只是數據塊內容有點特殊。軟連接有着本身的 inode 號以及用戶數據塊。所以軟連接的建立與使用沒有相似硬連接的諸多限制:
1)軟連接有本身的文件屬性及權限等;
2)可對不存在的文件或目錄建立軟連接;
3)軟連接可交叉文件系統;
4)軟連接可對文件或目錄建立;
5)建立軟連接時,連接計數 i_nlink 不會增長;
6)刪除軟連接並不影響被指向的文件,但若被指向的原文件被刪除,則相關軟鏈接被稱爲死連接(即 dangling link,若被指向路徑文件被從新建立,死連接可恢復爲正常的軟連接)。
##建立鏈接
ln 目標 鏈接名
## 建立軟鏈接
ln \-s 目標 鏈接名
##查看軟鏈接目標指向,對於硬鏈接是不顯示。
ls \-l 軟鏈接文件
##經過stat指令能夠查看軟硬鏈接的inode和block信息
##發現硬鏈接與目標文件的信息徹底一致。
##軟鏈接文件有單獨的inode和block。
複製代碼
建立鏈接
Path target = Paths.get("/data/test.log");
//target必須存在
Files.createLink(Paths.get("/data/hard\-link.log"),target);
Files.createSymbolicLink(Paths.get("/data/soft\-link.log"),target);
//檢測文件是否爲軟鏈接問題
boolean isSymbolicLink = Files.isSymbolicLink(Paths.get("/data/soft\-link.log"));
Path target2 = Files.readSymbolicLink(Paths.get("/data/soft\-link.log"));
//檢測是否爲同一個文件
System.out.println(Files.isSameFile(target,target2));//true
System.out.println(Files.isSameFile(target,Paths.get("/data/hard\-link.log")));//true
System.out.println(Files.isSameFile(target,Paths.get("/data/soft\-link.log")));//true
經過Files.isSameFile()比較,咱們會發現,不管是軟鏈接、硬連接,都與目標文件是同一個文件。
複製代碼
前文中介紹了有關PathMatcher,在JAVA NIO2中用於匹配文件的表達式,能夠支持glob和正則表達式(regex)兩種方式。其中glob的語法更接近linux shell,regex是更普遍、更豐富的一種方式。好比文件:/data/test.log
Path path = Paths.get("/data/test.log");
PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher("glob:\*\*.log");
System.out.println(pathMatcher.matches(path));
//基於正則
pathMatcher = FileSystems.getDefault().getPathMatcher("regex:.\*\\\\.log");
System.out.println(pathMatcher.matches(path));
複製代碼
表達式規則:syntax:pattern,其中合法的syntax爲「glob」和「regex」;須要注意這兩種表達式的區別。內部實現也比較簡單,對於glob字符串將會轉化爲正則表達式字符串,而後統一使用正則匹配。
曾經,使用JAVA遍歷文件數是一件比較繁瑣的事情,在NIO2中增長了原生提供了此操做。主要API爲FileVisitor,其簡單實現類爲SimpleFileVisitor:
一、preVisitDirectory:在瀏覽目錄以前。前置操做。好比遍歷並複製文件時,能夠在進入目錄以前,建立遷移的目標新目錄。
二、postVisitDirectory:在瀏覽目錄中全部文件以後(瀏覽其餘目錄以前)。後置操做。
三、visitFile:瀏覽文件,Path和BaseFileAttributes會傳遞入方法。
四、visitFileFailed:瀏覽文件失敗時調用,好比文件屬性沒法獲取、目錄沒法打開等異常時,調用此方法,同時傳入Path和Exception。
簡單的遍歷(查找、篩選匹配)
Path dir = Paths.get("/data/redis");
Stream<Path> stream = Files.walk(dir);
stream.forEach(path \-> {
System.out.println(path);
});
stream.close();
複製代碼
複雜遍歷(遍歷查找、文件遷移校驗)
public static void main(String\[\] args) throws Exception{
Path dir = Paths.get("/data/redis");
Files.walkFileTree(dir,new Finder());
}
public static class Finder implements FileVisitor<Path> {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
System.out.println("preVisitDirectory:" + dir);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
System.out.println("visitFile:" + file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
System.out.println("visitFileFailed:" + file + ",exception:" + (exc != null ? exc.getMessage() : "\-"));
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
System.out.println("postVisitDirectory:" + dir);
return FileVisitResult.CONTINUE;
}
}
複製代碼
FileVisitResult用於表示執行狀態:
1)CONTINUE:表示繼續執行(繼續下一步操做)
2)TERMINATE:終止遞歸遍歷,其餘的後續方法不會被執行,還沒有瀏覽的文件也將不會被訪問。
3)SKIP_SUBTREE:跳過子樹,即當前目錄以及其子目錄都將被跳過。適用在preVisitDirectory(),其餘方法返回此值則等效於CONTINUE。
4)SKIP_SIBLINGS:跳過此文件(或者目錄)的同級文件或者文件,適用在postVisitDirectory(),若是preVisitDirectory返回此值,則當前目錄也會跳過,且postVisitDirectory()不會被執行。
好了,關於NIO2新增的API,就已經介紹完畢了。
是否是鬆了口氣?
:)感謝牛草兒的投稿。
做者簡介:小姐姐味道 (xjjdog),一個不容許程序員走彎路的公衆號。聚焦基礎架構和Linux。十年架構,日百億流量,與你探討高併發世界,給你不同的味道。個人我的微信xjjdog0,歡迎添加好友,進一步交流。