此文是系列文章第十篇恭垦,前幾篇請(qǐng)點(diǎn)擊鏈接查看
程序猿的福音 - Apache Commons簡(jiǎn)介
程序員的福音 - Apache Commons Compress
程序員的福音 - Apache Commons Collections
程序員的福音 - Apache Commons HttpClient
Apache Commons VFS 為訪問各種不同的文件系統(tǒng)提供了一個(gè)統(tǒng)一API。支持本地磁盤、HTTP服務(wù)器、FTP服務(wù)器源内、HDFS文件系統(tǒng)油够、ZIP壓縮包等,支持自行擴(kuò)展存儲(chǔ)客戶端鱼响。
commons-vfs 目前最新版本是 2.9.0,最低要求 Java8 以上组底。
maven坐標(biāo)如下:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-vfs2</artifactId>
<version>2.9.0</version>
</dependency>
下面我將從整體結(jié)構(gòu)丈积,使用方式筐骇,自定義擴(kuò)展等方面簡(jiǎn)單介紹一下。
01. 簡(jiǎn)介
VFS 對(duì)所有文件名都被視為 URI桶癣,這樣就必須使用 %25 對(duì)“%”字符進(jìn)行編碼拥褂。
示例
file:///somedir/some%25file.txt
許多文件系統(tǒng)接受用戶ID和密碼作為 url 的一部分。為了安全性考慮牙寞,Commons VFS 提供了一種加密機(jī)制饺鹃。不過這并不是完全安全的,因?yàn)樵贑ommons VFS 使用密碼之前间雀,還是需要對(duì)密碼進(jìn)行解密的悔详。
創(chuàng)建加密密碼,需要執(zhí)行以下操作:
$ java-cp commons-vfs-2.0.jar org.apache.commons.vfs2.util.EncryptUtil encrypt mypassword
其中 mypassword 是要加密的密碼惹挟。結(jié)果是一行十六進(jìn)制字符串茄螃。例如
$ java-cp commons-vfs-2.0.jar org.apache.commons.vfs2.util.EncryptUtil encrypt WontUBee9
$ D7B82198B272F5C93790FEB38A73C7B8
然后將輸出粘貼到 URL 中,如下所示:
https://testUser:{D7B82198B272F5C93790FEB38A73C7B8}@test.com/test
VFS 將 {} 中包含的密碼視為已加密连锯,并將在使用密碼之前對(duì)其進(jìn)行解密归苍。
Commons VFS 目前支持以下幾種文件系統(tǒng)的訪問
1. 本地文件
提供對(duì)本地物理文件系統(tǒng)上的文件的訪問。
URI格式
[file://]absolute-path
其中絕對(duì)路徑是本地平臺(tái)的有效絕對(duì)文件名运怖。Windows 下支持 UNC 名稱拼弃。
示例
file:///home/someuser/somedir
file:///C:/Documents and Settings
file://///somehost/someshare/afile.txt
/home/someuser/somedir
c:\program files\some dir
c:/program files/some dir
2. Zip, Jar and Tar
提供對(duì)Zip、Jar和Tar文件內(nèi)容的只讀訪問摇展,需要單獨(dú)引入 commons-compress 包吻氧。
URI格式
zip://arch-file-uri[!absolute-path]
jar://arch-file-uri[!absolute-path]
tar://arch-file-uri[!absolute-path]
tgz://arch-file-uri[!absolute-path]
tbz2://arch-file-uri[!absolute-path]
注意:如果您想使用 ! 作為普通字符,必須使用 %21進(jìn)行轉(zhuǎn)義咏连。
tgz 和 tbz2 是 tar:gz 和 tar:bz2 簡(jiǎn)寫形式
示例
jar:../lib/classes.jar!/META-INF/manifest.mf
zip:http://somehost/downloads/somefile.zip
jar:zip:outer.zip!/nested.jar!/somedir
jar:zip:outer.zip!/nested.jar!/some%21dir
tar:gz:http://host/my.tar.gz!/my.tar!/path/in/R.txt
tgz:file://host/dir/my.tgz!/somepath/somefile
3. gzip and bzip2
提供對(duì)gzip和bzip2文件內(nèi)容的只讀訪問盯孙,需要單獨(dú)引入 commons-compress
URI格式
示例
gz:/my/gz/file.gz
4. HDFS
提供對(duì) apache hadoop 文件系統(tǒng)(HDFS)中文件的讀寫訪問。在 Windows 上祟滴,集成測(cè)試在默認(rèn)情況下是禁用的振惰,因?yàn)樗枰M(jìn)制文件。需要單獨(dú)引入 HDFS 相關(guān)依賴
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs-client</artifactId>
<version>3.3</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>3.3</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>3.3</version>
</dependency>
URI格式
hdfs://hostname[:port][absolute-path]
示例
hdfs://somehost:8080/downloads/some_dir
hdfs://somehost:8080/downloads/some_file.ext
5. HTTP and HTTPS
提供對(duì) HTTP 服務(wù)器上文件的訪問垄懂,需要依賴 HttpClient骑晶,同時(shí)支持HttpClient 3,HttpClient 4埠偿,HttpClient 5 版本,只需要引入其中一個(gè)版本的依賴即可榜晦。
URI格式
http://[username[:password]@]hostname[:port][absolute-path]
https://[username[:password]@]hostname[:port][absolute-path]
額外可選參數(shù)
proxyHost:要連接的代理主機(jī)冠蒋。
proxyPort:要使用的代理端口。
proxyScheme:要使用的代理方案(http/https)乾胶。
cookies:要添加到請(qǐng)求的 cookies 數(shù)組抖剿。
maxConnectionsPerHost:允許連接到特定主機(jī)和端口的最大連接數(shù)朽寞。默認(rèn)值為5。
maxTotalConnections:所有主機(jī)允許的最大連接數(shù)斩郎。默認(rèn)值為50脑融。
keystoreFile:SSL連接的密鑰庫(kù)文件。
keystorePass:密鑰庫(kù)密碼缩宜。
keystoreType:密鑰庫(kù)類型肘迎。
示例
http://somehost:8080/downloads/somefile.jar
http://myusername@somehost/index.html
6. WebDAV
通過 commons-vfs2-jackrabbit1 和 commons-vfs2-jackrabbit2 模塊提供對(duì) WebDAV 服務(wù)器上文件的訪問。
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-vfs2-jackrabbit1</artifactId>
<version>2.9.0</version>
</dependency>
URI格式
webdav://[username[:password]@]hostname[:port][absolute-path]
額外可選參數(shù)
versioning :如果應(yīng)啟用版本控制锻煌,則版本控制為 true
creatorName:要通過更改文件來標(biāo)識(shí)的用戶名妓布。如果未設(shè)置,將使用用于身份驗(yàn)證的用戶名宋梧。
示例
7. FTP and FTPS
提供對(duì)FTP服務(wù)器上文件的訪問匣沼。依賴 commons-net
URI格式
ftp://[user[:pass]@]host[:port][relative-path]
示例
ftp://user:pass@somehost/pub/somefile.tgz
默認(rèn)情況下,路徑相對(duì)于用戶的主目錄捂龄∈吞危可通過以下方式進(jìn)行更改:
FtpFileSystemConfigBuilder.getInstance().setUserDirIsRoot(options, false);
8. SFTP
提供對(duì) SFTP 服務(wù)器(即 SSH 或 SCP 服務(wù)器)上的文件的訪問。需要添加如下依賴
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.55</version>
</dependency>
URI格式
sftp://[user[:pass]@]hostname[:port][relative-path]
示例
sftp://user:pass@somehost/pub/file.tgz
默認(rèn)情況下倦沧,路徑相對(duì)于用戶的主目錄唇撬。可通過以下方式進(jìn)行更改:
FtpFileSystemConfigBuilder.getInstance().setUserDirIsRoot(options, false);
9. Temporary Files
提供對(duì)臨時(shí)文件系統(tǒng)的訪問刀脏,該文件系統(tǒng)在 Commons VFS 關(guān)閉時(shí)被刪除局荚。臨時(shí)文件系統(tǒng)由本地文件系統(tǒng)支持
URI格式
tmp://[absolute-path]
示例
10. RAM
在內(nèi)存中存儲(chǔ)所有數(shù)據(jù)的文件系統(tǒng)(每個(gè)文件內(nèi)容一個(gè)字節(jié)數(shù)組)
URI格式
ram://[path]
額外可選參數(shù)
maxsize:最大文件系統(tǒng)大小(所有文件內(nèi)容的總字節(jié)數(shù))
示例
ram:///any/path/to/file.txt
11. Resource
這實(shí)際上不是一個(gè)文件系統(tǒng)愈污,它使用 ClassLoader.getResource() 查找資源耀态,并創(chuàng)建一個(gè) VFS url 以供進(jìn)一步處理
URI格式
res://[path]
示例
將會(huì)轉(zhuǎn)換為 ->
jar:file://my/path/images.jar!/path/in/image.png
12. CIFS
提供對(duì) CIFS 服務(wù)器(如 Samba服務(wù) 或 Windows共享)的訪問。
官方還在開發(fā)中(截止本文發(fā)布時(shí)間2021-08-22)
URI格式
smb://[username[:password]@]hostname[:port][absolute-path]
示例
13. MIME
可以讀取郵件及其附件暂雹,比如歸檔文件首装。如果已解析郵件中的某個(gè)部分沒有名稱,則將生成一個(gè)偽名稱杭跪。虛擬名稱是:_body_part_X仙逻,其中X將被零件號(hào)替換。
官方還在開發(fā)中(截止本文發(fā)布時(shí)間2021-08-22)
URI格式
mime://mime-file-uri[!absolute-path]
示例
mime:file:///your/path/mail/anymail.mime!/
mime:file:///your/path/mail/anymail.mime!/filename.pdf
mime:file:///your/path/mail/anymail.mime!/_body_part_0
14. 自定義擴(kuò)展
Commons VFS 接口支持開閉原則涧尿,允許開發(fā)者在不改變?cè)蓄惖幕A(chǔ)上自行擴(kuò)展自己的實(shí)現(xiàn)系奉,如亞馬遜S3文件,阿里云文件等姑廉。只需在類路徑下創(chuàng)建"META-INF/vfs-providers.xml"文件缺亮,添加額外的配置并編寫類實(shí)現(xiàn) VFS 對(duì)應(yīng)的接口。具體擴(kuò)展方式將在下章節(jié)做介紹桥言。
02. 整體結(jié)構(gòu)
FileSystemManager:管理一組文件系統(tǒng)萌踱。此接口用于按名稱從這些文件系統(tǒng)之一中定位獲取 org.apache.commons.vfs2.FileObject 葵礼。
FileProvider:文件提供者。每個(gè)文件提供者負(fù)責(zé)處理特定 URI 的文件并鸵。
FileNameParser:提供將文件名解析為 org.apache.commons.vfs2.FileName 的方法鸳粉。
FileSystem:一個(gè)文件系統(tǒng),由文件的層次結(jié)構(gòu)組成园担。
FileObject:代表一個(gè)文件届谈,用于訪問文件的內(nèi)容和結(jié)構(gòu)。有兩種類型的文件:文件夾粉铐,包含其他文件疼约;普通文件,包含數(shù)據(jù)或內(nèi)容蝙泼。一個(gè)文件夾可能沒有任何內(nèi)容程剥,普通文件不能包含其他文件。
FilesCache:文件緩存接口汤踏。VFS 內(nèi)置一下幾種實(shí)現(xiàn)织鲸,SoftRefFilesCache(軟引用,默認(rèn)值)溪胶,WeakRefFilesCache(弱引用)搂擦,DefaultFilesCache(沒有過期和限制),LRUFilesCache(LRU實(shí)現(xiàn)哗脖,默認(rèn)容量100個(gè))瀑踢,NullFilesCache(空實(shí)現(xiàn),不做任何緩存)才避。
FileSelector:該接口用于查找子文件時(shí)定義選擇規(guī)則橱夭,使用方式 FileObject.findFiles(FileSelector) 。VFS 內(nèi)置了 7 種選擇器桑逝。
VFS 加載文件流程大致如下:
FileSystemManager 解析文件名棘劣,通過文件名中的協(xié)議(如ftp://中的ftp)獲取對(duì)應(yīng) FileProvider 對(duì)象,F(xiàn)ileProvider 通過 FileNameParser 對(duì)象解析文件名獲取對(duì)應(yīng)的 FileSystem 對(duì)象楞遏,通過 FileSystem 對(duì)象的 resolveFile 方法獲取文件(默認(rèn)先從緩存中查找茬暇,不存在再調(diào)用 createFile 方法創(chuàng)建 FileObject 對(duì)象,F(xiàn)ileObject 就是實(shí)體文件的抽象寡喝,提供讀取和修改等相關(guān)能力)
主要類圖結(jié)構(gòu)如下
03. 使用方式
1. 文件屬性
// 獲取默認(rèn)FileSystemManager
FileSystemManager fsMgr = VFS.getManager();
String path = "D:/test/abc";
// 獲取的是 LocalFileObject 實(shí)現(xiàn)
FileObject fo = fsMgr.resolveFile(path);
println(fo.getFileSystem()); // LocalFileSystem
if (!fo.exists()) {
println("fo not exists");
return;
}
println("parent:"+fo.getParent().toString());// "file:///D:/"
println("name:"+fo.getName());// "file:///D:/test"
println("path:"+fo.getPath());// "D:\test"
println("pubURI:"+fo.getPublicURIString());// "file:///D:/test"
println("URI:"+fo.getURI().toString());// "file:///D:/test"
println("URL:"+fo.getURL());// "file:///D:/test"
boolean isFile = fo.isFile();
boolean isFolder = fo.isFolder();
// 是否符號(hào)鏈接
boolean isSymbolic = fo.isSymbolicLink();
boolean executable = fo.isExecutable();
boolean isHidden = fo.isHidden();
println("type:"+fo.getType());
// 應(yīng)該放到finally塊中關(guān)閉糙俗,為了便于閱讀直接在此關(guān)閉了
// 會(huì)同時(shí)關(guān)閉FileContent并釋放FileObject
fo.close();
// 關(guān)閉文件系統(tǒng),釋放連接预鬓,清除緩存等
fsMgr.close();
2. 讀取文件內(nèi)容
// 讀取文件內(nèi)容
// 支持獲取字符串巧骚,流,字節(jié)數(shù)組等
FileSystemManager fsMgr = VFS.getManager();
String path = "E:\\yuanzhy\\yyhc.py";
FileObject fo = fsMgr.resolveFile(path);
if (fo.isFile()) {
FileContent fc = fo.getContent();
// fc.getInputStream();
// fc.getByteArray();
// 獲取內(nèi)容 - 字符串形式
String content = fc.getString("UTF-8");
println(content);
}
// 在finally或者try-resources中關(guān)閉資源
fo.close();
fsMgr.close();
3. 讀取文件屬性
讀取文件只讀的屬性信息
只有本地 Jar文件 和 HDFS文件 支持此功能,其他類型文件獲取的為空
jar 屬性就是 manifest 中的屬性
HDFS 支持的屬性都在 HdfsFileAttributes 枚舉中
訪問時(shí)間:HdfsFileAttributes.LAST_ACCESS_TIME
塊大型臁:HdfsFileAttributes.BLOCK_SIZE
用戶組:HdfsFileAttributes.GROUP
所有者:HdfsFileAttributes.OWNER
操作權(quán)限:HdfsFileAttributes.PERMISSIONS
文件大小:HdfsFileAttributes.LENGTH
修改時(shí)間:HdfsFileAttributes.MODIFICATION_TIME
FileSystemManager fsMgr = VFS.getManager();
String path = "D:/test/a.txt";
FileObject fo = fsMgr.resolveFile(path);
if (fo.isFile()) {
FileContent fc = fo.getContent();
// 獲取只讀的文件屬性
Map<String, Object> attrs = fc.getAttributes();
println(fc.getAttributes());
}
// 在finally或者try-resources中關(guān)閉資源
fo.close();
fsMgr.close();
4. 設(shè)置文件權(quán)限
設(shè)置文件屬性蟋定,如可讀可寫可執(zhí)行等粉臊,只有 本地文件 和 SFTP 上的文件支持此功能
FileSystemManager fsMgr = VFS.getManager();
String path = "D:/test/a.txt";
FileObject fo = fsMgr.resolveFile(path);
if (fo.isFile()) {
fo.setWritable(true, true);
fo.setExecutable(true, true);
}
// 在finally或者try-resources中關(guān)閉資源
fo.close();
fsMgr.close();
5. 讀取目錄
// 主要是獲取子文件
FileSystemManager fsMgr = VFS.getManager();
String path = "D:/test";
FileObject fo = fsMgr.resolveFile(path);
if (fo.isFolder()) {
// 獲取所有子文件
FileObject[] foArr = fo.getChildren();
// 獲取子文件(名稱為test)
FileObject test = fo.getChild("a.txt");
// 從所有后代中獲取類型是文件的文件
FileObject[] files = fo.findFiles(Selectors.SELECT_FILES);
}
// 在finally或者try-resources中關(guān)閉資源
fo.close();
fsMgr.close();
6. 刪除
目錄可通過參數(shù)刪除部分子文件。只有本地文件驶兜、內(nèi)存文件扼仲、FTP、SFTP抄淑、HDFS支持刪除
FileSystemManager fsMgr = VFS.getManager();
String path = "D:/test/a.txt";
FileObject fo = fsMgr.resolveFile(path);
if (fo.isFolder()) {
// 刪除此文件和所有子文件, 返回刪除的數(shù)量
fo.deleteAll(); // 同fo.delete(Selectors.SELECT_ALL);
// 只刪除所有子文件
fo.delete(Selectors.EXCLUDE_SELF);
// 只刪除直接子文件和空目錄
fo.delete(Selectors.SELECT_CHILDREN);
// 只刪除文件
fo.delete(Selectors.SELECT_FILES);
// 只刪除空的子目錄
fo.delete(Selectors.SELECT_FOLDERS);
// 刪除目錄本身(如果包含子文件則刪除失敗返回0)
fo.delete(Selectors.SELECT_SELF);
// 目錄不為空則刪除失敗返回false
boolean suc = fo.delete();
} else if (fo.isFile()) {
// 刪除文件本身
boolean suc = fo.delete();
}
// 在finally或者try-resources中關(guān)閉資源
fo.close();
fsMgr.close();
7. 拷貝文件
// 將文件內(nèi)容寫入其他地方
FileSystemManager fsMgr = VFS.getManager();
String path = "D:/test/a.txt";
FileObject fo = fsMgr.resolveFile(path);
if (fo.isFile()) {
FileContent fc = fo.getContent();
// 支持寫入輸出流屠凶,F(xiàn)ileContent和FileObject中
// fc.write(FileObject);
// fc.write(FileContent);
// 相當(dāng)于下載到 D:/test/a.txt
fc.write(new FileOutputStream("E:/test/a.txt"));
}
// 在finally或者try-resources中關(guān)閉資源
fo.close();
fsMgr.close();
8. 修改文件
新增or修改內(nèi)容。只有本地文件肆资、內(nèi)存文件矗愧、FTP、SFTP郑原、GZip唉韭、bz2、HDFS支持犯犁,其中只有本地文件属愤、內(nèi)存文件、FTP酸役、SFTP支持追加寫住诸,GZip、bz2涣澡、HDFS 只支持覆蓋寫贱呐。
FileSystemManager fsMgr = VFS.getManager();
String path = "D:/test/a.txt";
FileObject fo = fsMgr.resolveFile(path);
fo.createFile();
if (fo.isFile()) {
FileContent fc = fo.getContent();
OutputStream os = fc.getOutputStream();
if (fo.isWriteable()) {
// 覆蓋寫入
IOUtils.write("測(cè)試", os, "UTF-8");
os.close();
}
try {
// 追加寫
os = fc.getOutputStream(true);
IOUtils.write("追加數(shù)據(jù)", os, "UTF-8");
} catch (FileSystemException e) {
// 不支持追加寫入
System.err.println("不支持追加寫入");
}
}
// 在finally或者try-resources中關(guān)閉資源
fo.close();
fsMgr.close();
9. 隨機(jī)讀寫
隨機(jī)只讀:本地文件、內(nèi)存文件暑塑、FTP吼句、SFTP、HDFS事格、HTTP支持
隨機(jī)讀寫:本地文件惕艳、內(nèi)存文件支持
FileSystemManager fsMgr = VFS.getManager();
String path = "D:/test/a.txt";
FileObject fo = fsMgr.resolveFile(path);
if (fo.isFile()) {
FileContent fc = fo.getContent();
try {
RandomAccessContent rac = fc.getRandomAccessContent(RandomAccessMode.READ);
// ... ...
} catch (FileSystemException e) {
// 不支持RandomAccessMode.READ
}
}
// 在finally或者try-resources中關(guān)閉資源
fo.close();
fsMgr.close();
10. 更改緩存實(shí)現(xiàn)
StandardFileSystemManager fsMgr = new StandardFileSystemManager();
// 手動(dòng)處理緩存數(shù)據(jù)。調(diào)用 FileObject#refresh() 來刷新對(duì)象數(shù)據(jù)
fsMgr.setCacheStrategy(CacheStrategy.MANUAL);
// 每次從 FileSystemManager#resolveFile 請(qǐng)求文件時(shí)刷新數(shù)據(jù)驹愚。
// fsMgr.setCacheStrategy(CacheStrategy.ON_RESOLVE);
// 每次在 fileObject 上調(diào)用方法時(shí)刷新數(shù)據(jù)愚战。僅當(dāng)您確實(shí)需要最新信息時(shí)才使用此設(shè)置,因?yàn)榇嗽O(shè)置會(huì)造成重大性能損失把跨。
// fsMgr.setCacheStrategy(CacheStrategy.ON_CALL);
// 設(shè)置緩存實(shí)現(xiàn)為L(zhǎng)RU
fsMgr.setFilesCache(new LRUFilesCache());
fsMgr.init();
FileObject fo = fsMgr.resolveFile("D:/test/a.txt");
// ... ...
11. 文件監(jiān)聽
監(jiān)聽文件創(chuàng)建们拙,修改或刪除
public class ListenersDemo {
@Test
public void test() throws IOException {
// 監(jiān)聽文件創(chuàng)建,修改或刪除
FileSystemManager fsMgr = VFS.getManager();
String path = "D:/test/a.txt";
FileObject fo = fsMgr.toFileObject(new File(path));
// 添加監(jiān)聽器
fo.getFileSystem().addListener(fo, new MyListener());
if (!fo.exists()) {
fo.createFile();
}
fo.setWritable(false, false);
// fo.delete();
fo.close();
fsMgr.close();
}
private class MyListener implements FileListener {
@Override
public void fileCreated(FileChangeEvent event) throws Exception {
println("fileCreated:"+event.getFileObject().getName());
}
@Override
public void fileDeleted(FileChangeEvent event) throws Exception {
println("fileDeleted:"+event.getFileObject().getName());
}
@Override
public void fileChanged(FileChangeEvent event) throws Exception {
println("fileChanged:"+event.getFileObject().getName());
}
}
}
04. 總結(jié)
Commons VFS 擁有統(tǒng)一的文件系統(tǒng)訪問 API,使用一套代碼可以輕松的實(shí)現(xiàn)不同文件系統(tǒng)的讀寫操作倘潜,對(duì)于一些場(chǎng)景(比如同時(shí)支持多種存儲(chǔ)系統(tǒng)绷柒,或者存儲(chǔ)系統(tǒng)可以根據(jù)部署情況動(dòng)態(tài)配置)來說還是非常實(shí)用的,由于其接口的抽象性設(shè)計(jì)可以非常方便的擴(kuò)展以支持其他文件系統(tǒng)涮因,有對(duì)應(yīng)需求可以考慮使用废睦。
由于篇幅原因,關(guān)于自定義擴(kuò)展的方法我將在下一篇中再給大家做詳細(xì)講解养泡。
后續(xù)章節(jié)我將繼續(xù)給大家介紹commons中其他好用的工具類庫(kù)嗜湃,期待你的關(guān)注。