org.apache.hadoop.fs.FileSystem
抽象的 FileSystem
類別是存取 Hadoop 檔案系統的原始類別;所有 Hadoop 支援的檔案系統都有非抽象的子類別。
所有對此介面採取路徑的作業都必須支援相對路徑。在這種情況下,它們必須相對於 setWorkingDirectory()
定義的工作目錄解析。
因此,對於所有客戶端,我們也加入一個狀態組件 PWD 的概念:這表示客戶端的目前工作目錄。此狀態的變更不會反映在檔案系統本身:它們是客戶端實例獨有的。
實作備註:靜態 FileSystem get(URI uri, Configuration conf)
方法可能傳回檔案系統客戶端類別的預先存在實例,而此類別也可能在其他執行緒中使用。Apache Hadoop 附帶的 FileSystem
實作不會嘗試同步存取工作目錄欄位。
有效 FileSystem 的所有需求都被視為隱含的前置條件和後置條件:對有效 FileSystem 的所有操作都必須導致新的 FileSystem 也有效。
HDFS 有 受保護目錄 的概念,這些目錄宣告在選項 fs.protected.directories
中。任何嘗試刪除或重新命名此類目錄或其父目錄的動作都會引發 AccessControlException
。因此,任何嘗試刪除根目錄的動作,如果存在受保護目錄,將導致引發此類例外狀況。
boolean exists(Path p)
def exists(FS, p) = p in paths(FS)
boolean isDirectory(Path p)
def isDirectory(FS, p)= p in directories(FS)
boolean isFile(Path p)
def isFile(FS, p) = p in files(FS)
FileStatus getFileStatus(Path p)
取得路徑狀態
if not exists(FS, p) : raise FileNotFoundException
result = stat: FileStatus where: if isFile(FS, p) : stat.length = len(FS.Files[p]) stat.isdir = False stat.blockSize > 0 elif isDir(FS, p) : stat.length = 0 stat.isdir = True elif isSymlink(FS, p) : stat.length = 0 stat.isdir = False stat.symlink = FS.Symlinks[p] stat.hasAcl = hasACL(FS, p) stat.isEncrypted = inEncryptionZone(FS, p) stat.isErasureCoded = isErasureCoded(FS, p)
傳回的路徑 FileStatus
狀態另外載有 ACL、加密和刪除編碼資訊的詳細資料。可以查詢 getFileStatus(Path p).hasAcl()
以找出路徑是否有 ACL。可以查詢 getFileStatus(Path p).isEncrypted()
以找出路徑是否已加密。getFileStatus(Path p).isErasureCoded()
會告訴路徑是否已刪除編碼。
YARN 的分散式快取讓應用程式透過 Job.addCacheFile()
和 Job.addCacheArchive()
將路徑新增到容器和應用程式中快取。快取會將新增為應用程式間可共用的世界可讀取資源路徑,並以不同的方式下載它們,除非它們宣告為已加密。
為了避免容器啟動期間的失敗,特別是在使用委派權杖時,未實作檔案和目錄的 POSIX 存取權限的檔案系統和物件儲存庫,必須始終傳回 true
給 isEncrypted()
謂詞。這可以在建立 FileStatus
執行個體時,將 encrypted
旗標設定為 true 來完成。
msync()
將用戶端的金鑰資料狀態與 FileSystem 金鑰資料服務的最新狀態同步。
在高可用性 FileSystem 中,備用服務可用作唯讀金鑰資料複本。此呼叫對於保證從備用複本讀取的一致性,並避免過時的讀取至關重要。
目前僅實作於 HDFS,其他部分只會擲回 UnsupportedOperationException
。
此呼叫會在呼叫時內部記錄元資料服務的狀態。這可確保從任何元資料複本進行後續讀取的一致性。它確保用戶端絕不會存取記錄狀態之前的元資料狀態。
HDFS 在 HA 模式下透過呼叫 Active NameNode 並要求其最新的日誌交易 ID 來支援 msync()
。更多詳細資料請參閱 HDFS 文件 從 HDFS Observer NameNode 進行一致讀取
Path getHomeDirectory()
函式 getHomeDirectory
會傳回檔案系統和目前使用者帳戶的家目錄。
對於某些檔案系統,路徑為 ["/", "users", System.getProperty("user-name")]
。
然而,對於 HDFS,使用者名稱會從用於對用戶端進行 HDFS 驗證的憑證中衍生。這可能與本機使用者帳戶名稱不同。
由檔案系統負責確定呼叫者的實際家目錄。
result = p where valid-path(FS, p)
在呼叫方法時,不需要路徑存在,或者如果存在,則不需要指向目錄。然而,程式碼傾向於假設 not isFile(FS, getHomeDirectory())
成立,以致於後續程式碼可能會失敗。
FTPFileSystem
會從遠端檔案系統查詢此值,如果發生連線問題,可能會失敗並傳回 RuntimeException
或其子類別。執行作業的時間沒有限制。FileStatus[] listStatus(Path path, PathFilter filter)
列出路徑 path
下的項目。
如果 path
參照檔案且篩選器接受它,則該檔案的 FileStatus
項目會在單一元素陣列中傳回。
如果路徑參照目錄,呼叫會傳回其所有立即子路徑的清單,這些子路徑會被篩選器接受,且不包含目錄本身。
PathFilter
filter
是其 accept(path)
會傳回 true 的類別,表示路徑 path
符合篩選器的條件。
路徑 path
必須存在
if not exists(FS, path) : raise FileNotFoundException
if isFile(FS, path) and filter.accept(path) : result = [ getFileStatus(path) ] elif isFile(FS, path) and not filter.accept(P) : result = [] elif isDir(FS, path): result = [ getFileStatus(c) for c in children(FS, path) if filter.accepts(c) ]
隱含不變式:透過 listStatus()
擷取的子項目的 FileStatus
內容等於對相同路徑呼叫 getFileStatus()
的內容
forall fs in listStatus(path) : fs == getFileStatus(fs.path)
結果排序:沒有保證列出項目會排序。雖然 HDFS 目前會傳回字母數字排序的清單,但 Posix readdir()
和 Java 的 File.listFiles()
API 呼叫都沒有定義傳回值的任何排序。需要對結果進行統一排序順序的應用程式必須自行執行排序。
Null 傳回:3.0.0 之前的本機檔案系統會在存取錯誤時傳回 null。這被視為錯誤。預期在存取錯誤時會傳回 IOException。
當 listStatus()
作業傳回呼叫者時,並不能保證回應中所包含的資訊為最新。詳細資訊可能會過時,包括任何目錄的內容、任何檔案的屬性,以及所提供路徑的存在。
目錄的狀態可能會在評估過程中變更。
在路徑 P
中建立一個條目後,且在對檔案系統進行任何其他變更之前,listStatus(P)
必須找到檔案並傳回其狀態。
在路徑 P
中刪除一個條目後,且在對檔案系統進行任何其他變更之前,listStatus(P)
必須引發 FileNotFoundException
。
在路徑 P
中建立一個條目後,且在對檔案系統進行任何其他變更之前,listStatus(parent(P))
的結果應該包含 getFileStatus(P)
的值。
在路徑 P
中建立一個條目後,且在對檔案系統進行任何其他變更之前,listStatus(parent(P))
的結果不應該包含 getFileStatus(P)
的值。
這並非理論上的可能性,當目錄包含數千個檔案時,在 HDFS 中可以觀察到這一點。
考慮一個目錄 "/d"
,其內容為
a part-0000001 part-0000002 ... part-9999999
如果檔案數量多到 HDFS 在每個回應中傳回部分清單,則如果清單 listStatus("/d")
與作業 rename("/d/a","/d/z"))
同時執行,結果可能是
[a, part-0000001, ... , part-9999999] [part-0000001, ... , part-9999999, z] [a, part-0000001, ... , part-9999999, z] [part-0000001, ... , part-9999999]
雖然這種情況不太可能發生,但可能會發生。在 HDFS 中,只有在列出具有許多子目錄的目錄時,才可能會出現這些不一致的檢視。
其他檔案系統可能具有更強的一致性保證,或更輕易地傳回不一致的資料。
FileStatus[] listStatus(Path path)
這完全等同於 listStatus(Path, DEFAULT_FILTER)
,其中 DEFAULT_FILTER.accept(path) = True
適用於所有路徑。
原子性和一致性約束與 listStatus(Path, DEFAULT_FILTER)
相同。
FileStatus[] listStatus(Path[] paths, PathFilter filter)
列舉傳入目錄清單中找到的所有檔案,對每個檔案呼叫 listStatus(path, filter)
。
與 listStatus(path, filter)
一樣,結果可能不一致。也就是說:檔案系統的狀態在作業期間發生變更。
無法保證路徑會以特定順序列出,只保證它們必須全部列出,且在列出時存在。
所有路徑都必須存在。無需唯一性。
forall p in paths : exists(fs, p) else raise FileNotFoundException
結果是一個陣列,其條目包含路徑清單中找到的每個狀態元素,且沒有其他元素。
result = [listStatus(p, filter) for p in paths]
實作可能會合併重複的條目;和/或透過辨識重複的路徑並只列出條目一次來最佳化作業。
預設實作會遍歷清單;它不會執行任何最佳化。
原子性和一致性約束與 listStatus(Path, PathFilter)
相同。
RemoteIterator<FileStatus> listStatusIterator(Path p)
傳回一個迭代器,列舉路徑下的 FileStatus
項目。這類似於 listStatus(Path)
,但不是傳回整個清單,而是傳回一個迭代器。結果與 listStatus(Path)
完全相同,前提是沒有其他呼叫者在列示期間更新目錄。話雖如此,如果其他呼叫者在執行列示時新增/刪除目錄內的檔案,這並不能保證原子性。不同的檔案系統可能會提供更有效率的實作,例如 S3A 會分頁列示,並在處理一個頁面的同時非同步擷取下一個頁面。
請注意,由於現在的初始列示是非同步的,因此 bucket/路徑存在例外狀況可能會在稍後的 next() 呼叫中顯示。
呼叫者應優先使用 listStatusIterator,而不是 listStatus,因為它是遞增的。
FileStatus[] listStatus(Path[] paths)
列舉在傳入的目錄清單中找到的所有檔案,對每個檔案呼叫 listStatus(path, DEFAULT_FILTER)
,其中 DEFAULT_FILTER
接受所有路徑名稱。
RemoteIterator[LocatedFileStatus] listLocatedStatus(Path path, PathFilter filter)
傳回一個迭代器,列舉路徑下的 LocatedFileStatus
項目。這類似於 listStatus(Path)
,但傳回值是 FileStatus
子類別 LocatedFileStatus
的實例,而不是傳回整個清單,而是傳回一個迭代器。
這實際上是一個 protected
方法,直接由 listLocatedStatus(Path path)
呼叫。對它的呼叫可能會透過分層檔案系統委派,例如 FilterFileSystem
,因此其實作必須被視為強制性的,即使 listLocatedStatus(Path path)
已以不同的方式實作。有開放的 JIRA 提出將此方法設為公開;它可能會在未來發生。
迭代器不需要提供路徑子項目的相容性檢視。預設實作會使用 listStatus(Path)
來列示其子項目,其一致性約束已記載在文件中。其他實作可能會更動態地執行列舉。例如擷取子項目的一個視窗子集,因此避免建立大型資料結構和傳輸大型訊息。在這種情況下,檔案系統的變更更有可能變得明顯。
呼叫者必須假設,如果在呼叫傳回與反覆運算完全執行之間,檔案系統發生變更,反覆運算操作可能會失敗。
路徑 path
必須存在
exists(FS, path) : raise FileNotFoundException
操作會產生一組結果,resultset
,等於 listStatus(path, filter)
的結果
if isFile(FS, path) and filter.accept(path) : resultset = [ getLocatedFileStatus(FS, path) ] elif isFile(FS, path) and not filter.accept(path) : resultset = [] elif isDir(FS, path) : resultset = [ getLocatedFileStatus(FS, c) for c in children(FS, path) where filter.accept(c) ]
操作 getLocatedFileStatus(FS, path: Path): LocatedFileStatus
定義為 LocatedFileStatus
執行個體 ls
的產生器,其中
fileStatus = getFileStatus(FS, path) bl = getFileBlockLocations(FS, path, 0, fileStatus.len) locatedFileStatus = new LocatedFileStatus(fileStatus, bl)
反覆運算器中傳回 resultset
元素的順序未定義。
原子性和一致性約束與 listStatus(Path, PathFilter)
相同。
RemoteIterator[LocatedFileStatus] listLocatedStatus(Path path)
等同於 listLocatedStatus(path, DEFAULT_FILTER)
,其中 DEFAULT_FILTER
接受所有路徑名稱。
RemoteIterator[LocatedFileStatus] listFiles(Path path, boolean recursive)
在目錄中/目錄下建立反覆運算器,並可能遞迴至子目錄。
此操作的目標是讓檔案系統能更有效率地處理大型遞迴目錄掃描,方法是減少在單一 RPC 呼叫中必須收集的資料量。
exists(FS, path) else raise FileNotFoundException
結果是反覆運算器,其從 iterator.next()
呼叫順序產生的輸出可以定義為集合 iteratorset
if not recursive: iteratorset == listStatus(path) else: iteratorset = [ getLocatedFileStatus(FS, d) for d in descendants(FS, path) ]
函數 getLocatedFileStatus(FS, d)
定義在 listLocatedStatus(Path, PathFilter)
中。
原子性和一致性約束與 listStatus(Path, PathFilter)
相同。
ContentSummary getContentSummary(Path path)
提供路徑,傳回其內容摘要。
getContentSummary()
首先檢查提供的路徑是否為檔案,如果是,則傳回目錄計數為 0,檔案計數為 1。
exists(FS, path) else raise FileNotFoundException
傳回 ContentSummary
物件,其中包含提供路徑的目錄計數和檔案計數等資訊。
原子性和一致性約束與 listStatus(Path, PathFilter)
相同。
BlockLocation[] getFileBlockLocations(FileStatus f, int s, int l)
if s < 0 or l < 0 : raise {HadoopIllegalArgumentException, InvalidArgumentException}
HadoopIllegalArgumentException
;這會延伸 IllegalArgumentException
。如果檔案系統具有位置感知,它必須傳回區塊位置清單,其中可以找到範圍 [s:s+l]
中的資料。
if f == null : result = null elif f.getLen() <= s: result = [] else result = [ locations(FS, b) for b in blocks(FS, p, s, s+l)]
其中
def locations(FS, b) = a list of all locations of a block in the filesystem def blocks(FS, p, s, s + l) = a list of the blocks containing data(FS, path)[s:s+l]
請注意,由於 length(FS, f)
定義為 0
(如果 isDir(FS, f)
),因此在目錄上執行 getFileBlockLocations()
的結果為 []
如果檔案系統沒有位置感知,它應該傳回
[ BlockLocation(["localhost:9866"] , ["localhost"], ["/default/localhost"] 0, f.getLen()) ] ;
*Hadoop 1.0.3 中的錯誤表示必須提供與叢集拓撲相同元素數量的拓撲路徑,因此檔案系統應該傳回 "/default/localhost"
路徑。雖然這不再是問題,但通常會保留此慣例。
BlockLocation[] getFileBlockLocations(Path P, int S, int L)
if p == null : raise NullPointerException if not exists(FS, p) : raise FileNotFoundException
result = getFileBlockLocations(getFileStatus(FS, P), S, L)
long getDefaultBlockSize()
取得檔案系統的「預設」區塊大小。這通常用於分割計算,以最佳方式將工作分配給一組工作程序。
result = integer > 0
雖然此結果沒有定義的最小值,因為它用於在提交工作期間分割工作,但過小的區塊大小會導致分割不佳的工作負載,甚至會導致 JobSubmissionClient
和等效的記憶體用盡,因為它會計算分割區。
任何實際上不會將檔案分割成區塊的 FileSystem 都應傳回一個數字,以進行有效率的處理。FileSystem 可以讓使用者進行設定(物件儲存器連接器通常會這樣做)。
long getDefaultBlockSize(Path p)
取得路徑的「預設」區塊大小,也就是將物件寫入檔案系統中的路徑時要使用的區塊大小。
result = integer >= 0
此操作的結果通常與 getDefaultBlockSize()
相同,不會檢查給定路徑是否存在。
支援掛載點的檔案系統可能對不同的路徑有不同的預設值,在這種情況下,應傳回目標路徑的特定預設值。
如果路徑不存在,這不是錯誤:必須傳回檔案系統該部分的預設/建議值。
long getBlockSize(Path p)
此方法與查詢 getFileStatus(p)
中傳回的 FileStatus
結構的區塊大小完全相同。它已過時,目的是鼓勵使用者呼叫一次 getFileStatus(p)
,然後使用結果來檢查檔案的多個屬性(例如長度、類型、區塊大小)。如果查詢多個屬性,這可能會成為顯著的效能最佳化,並減少檔案系統的負載。
if not exists(FS, p) : raise FileNotFoundException
if len(FS, P) > 0: getFileStatus(P).getBlockSize() > 0 result == getFileStatus(P).getBlockSize()
getFileStatus(P).getBlockSize()
的值相同。boolean mkdirs(Path p, FsPermission permission)
建立目錄及其所有父目錄。
路徑必須是目錄或不存在
if exists(FS, p) and not isDir(FS, p) : raise [ParentNotDirectoryException, FileAlreadyExistsException, IOException]
沒有祖先可以是檔案
forall d = ancestors(FS, p) : if exists(FS, d) and not isDir(FS, d) : raise [ParentNotDirectoryException, FileAlreadyExistsException, IOException]
FS' where FS'.Directories' = FS.Directories + [p] + ancestors(FS, p) result = True
檔案系統的目錄、檔案和符號連結的條件排他性需求必須成立。
探測路徑和目錄建立的存在和類型必須是原子性的。包含 `mkdirs(parent(F))` 的複合操作可以是原子性的。
回傳值總是為 true,即使沒有建立新的目錄(這在 HDFS 中有定義)。
FSDataOutputStream create(Path, ...)
FSDataOutputStream create(Path p, FsPermission permission, boolean overwrite, int bufferSize, short replication, long blockSize, Progressable progress) throws IOException;
對於不覆寫建立,檔案不能存在
if not overwrite and isFile(FS, p) : raise FileAlreadyExistsException
寫入或覆寫目錄必須失敗。
if isDir(FS, p) : raise {FileAlreadyExistsException, FileNotFoundException, IOException}
沒有祖先可以是檔案
forall d = ancestors(FS, p) : if exists(FS, d) and not isDir(FS, d) : raise [ParentNotDirectoryException, FileAlreadyExistsException, IOException]
檔案系統可能會因為其他原因拒絕要求,例如檔案系統為唯讀(HDFS)、區塊大小低於允許的最小值(HDFS)、複製計數超出範圍(HDFS)、超過名稱空間或檔案系統的配額、保留名稱等。所有拒絕都應該為 `IOException` 或其子類別,並且可以是 `RuntimeException` 或其子類別。例如,HDFS 可能會引發 `InvalidPathException`。
FS' where : FS'.Files'[p] == [] ancestors(p) is-subset-of FS'.Directories' result = FSDataOutputStream
在指定的路徑末端必須存在一個零位元組檔案,所有人都看得見。
更新的(有效)檔案系統必須包含路徑的所有父目錄,由 `mkdirs(parent(p))` 建立。
結果是 `FSDataOutputStream`,它可能透過其操作產生新的檔案系統狀態,其中包含 `FS.Files[p]` 的更新值
已回傳串流的行為在 輸出 中說明。
有些實作會將建立拆分為檢查檔案是否存在和實際建立。這表示操作不是原子性的:如果檔案在兩個測試之間由另一個用戶端建立,則使用 `overwrite==true` 建立檔案的用戶端可能會失敗。
S3A 和其他潛在的物件儲存體連接器目前不會變更 `FS` 狀態,直到輸出串流 `close()` 操作完成。這是物件儲存體和檔案系統行為之間的重大差異,因為它允許 >1 個用戶端使用 `overwrite=false` 建立檔案,並可能混淆檔案/目錄邏輯。特別是,在使用物件儲存體時,使用 `create()` 取得檔案的獨佔鎖定(沒有錯誤建立檔案的人員會被視為鎖定的持有者)可能不是安全的演算法。
物件儲存體可能會在建立檔案時建立一個空檔案作為標記。但是,具有 `overwrite=true` 語意的物件儲存體可能無法原子性地實作這一點,因此無法使用 `overwrite=false` 建立檔案作為程序之間的隱式排除機制。
嘗試在目錄上建立檔案時,本機檔案系統會引發 `FileNotFoundException`,因此它會列為此前提條件失敗時可能會引發的例外狀況。
未涵蓋:符號連結。符號連結的已解析路徑會用作 `create()` 操作的最後路徑引數
FSDataOutputStreamBuilder createFile(Path p)
建立一個 FSDataOutputStreamBuilder
來指定建立檔案的參數。
已回傳串流的行為在 輸出 中說明。
createFile(p)
只會傳回一個 FSDataOutputStreamBuilder
,而且不會立即對檔案系統進行變更。當在 FSDataOutputStreamBuilder
上呼叫 build()
時,會驗證建立器的參數,並在底層檔案系統上呼叫 create(Path p)
。build()
具有與 create(Path p)
相同的先決條件和後置條件。
create(Path p)
類似,檔案預設會被覆寫,除非指定 builder.overwrite(false)
。create(Path p)
不同,預設不會建立遺失的父目錄,除非指定 builder.recursive()
。FSDataOutputStream append(Path p, int bufferSize, Progressable progress)
沒有相容呼叫的實作應擲回 UnsupportedOperationException
。
if not exists(FS, p) : raise FileNotFoundException if not isFile(FS, p) : raise [FileAlreadyExistsException, FileNotFoundException, IOException]
FS' = FS result = FSDataOutputStream
傳回值:FSDataOutputStream
,它可以透過將資料附加到現有清單來更新項目 FS.Files[p]
。
已回傳串流的行為在 輸出 中說明。
FSDataOutputStreamBuilder appendFile(Path p)
建立一個 FSDataOutputStreamBuilder
來指定附加到現有檔案的參數。
已回傳串流的行為在 輸出 中說明。
appendFile(p)
只會傳回一個 FSDataOutputStreamBuilder
,而且不會立即對檔案系統進行變更。當在 FSDataOutputStreamBuilder
上呼叫 build()
時,會驗證建立器的參數,並在底層檔案系統上呼叫 append()
。build()
具有與 append()
相同的先決條件和後置條件。
FSDataInputStream open(Path f, int bufferSize)
沒有相容呼叫的實作應擲回 UnsupportedOperationException
。
if not isFile(FS, p)) : raise [FileNotFoundException, IOException]
這是一個重要的先決條件。某些檔案系統的實作(例如物件儲存)可以透過延後其 HTTP GET 作業,直到傳回的 FSDataInputStream
上的第一次 read()
,來縮短一輪往返時間。然而,許多客戶端程式碼確實依賴於在 open()
作業時執行存在的檢查。實作必須在建立時檢查檔案是否存在。這並不表示檔案及其資料在後續的 read()
或任何後繼作業時仍然存在。
result = FSDataInputStream(0, FS.Files[p])
結果提供對 FS.Files[p]
定義的位元組陣列的存取;無論該存取是在呼叫 open()
作業時對內容的存取,或是在 FS 的後續狀態中是否以及如何擷取該資料的變更,這都是實作細節。
結果必須對操作的本地和遠端呼叫者相同。
嘗試穿越符號連結時,HDFS 可能會拋出 UnresolvedPathException
如果路徑存在於元資料中,但找不到任何其區塊的拷貝,HDFS 會拋出 IOException("無法開啟檔案名稱 " + src)
;-FileNotFoundException
看起來會更準確且有用。
FSDataInputStreamBuilder openFile(Path path)
請參閱 openFile()。
FSDataInputStreamBuilder openFile(PathHandle)
請參閱 openFile()。
PathHandle getPathHandle(FileStatus stat, HandleOpt... options)
沒有相容呼叫的實作必須拋出 UnsupportedOperationException
let stat = getFileStatus(Path p) let FS' where: (FS.Directories', FS.Files', FS.Symlinks') p' in paths(FS') where: exists(FS, stat.path) implies exists(FS', p')
在解析時,FileStatus
實例的參照與 getPathHandle(FileStatus)
的結果相同。PathHandle
可用於後續操作,以確保呼叫之間的不變性。
options
參數指定後續呼叫(例如 open(PathHandle)
)是否會在參照資料或位置變更時成功。預設情況下,任何修改都會導致錯誤。呼叫者可以指定放寬,即使參照存在於不同的路徑和/或其資料已變更,也能讓操作成功。
如果實作無法支援呼叫者指定的語意,則必須拋出 UnsupportedOperationException
。預設選項集如下。
未移動 | 已移動 | |
---|---|---|
未變更 | 精確 | 內容 |
已變更 | 路徑 | 參考 |
不需變更所有權、延伸屬性和其他元資料以符合 PathHandle
。實作可以自訂限制條件來延伸 HandleOpt
參數集。
客戶端指定 PathHandle
應使用 REFERENCE
追蹤重新命名後的實體。除非無法解析參考表示實體已不存在,否則實作必須在建立 PathHandle
時拋出 UnsupportedOperationException
。
客戶端指定 PathHandle
應僅在實體未變更時解析,並使用 PATH
。除非實作可以區分稍後位於相同路徑的相同實體,否則必須在建立 PathHandle
時拋出 UnsupportedOperationException
。
result = PathHandle(p')
PathHandle
的參照對象是在建立 FileStatus
執行個體時的名稱空間,而非建立 PathHandle
時的狀態。實作 MAY 拒絕建立或解析有效但服務成本昂貴的 PathHandle
執行個體。
實作透過複製物件進行重新命名的物件儲存體,除非已解析物件的譜系,否則不可宣稱支援 CONTENT
和 REFERENCE
。
必須可以序列化 PathHandle
執行個體,並在一個或多個處理序、另一部機器上重新建立執行個體,且在未來的任意時間點上重新建立,而不會變更其語意。如果實作無法再保證其不變性,則必須拒絕解析執行個體。
HDFS 不支援 PathHandle
對目錄或符號連結的參照。對 CONTENT
和 REFERENCE
的支援會透過 INode 查詢檔案。INode 在 NameNode 中並非唯一,因此聯合叢集應在 PathHandle
中包含足夠的元資料,以偵測來自其他名稱空間的參照。
FSDataInputStream open(PathHandle handle, int bufferSize)
沒有相容呼叫的實作必須擲回 UnsupportedOperationException
let fd = getPathHandle(FileStatus stat) if stat.isdir : raise IOException let FS' where: (FS.Directories', FS.Files', FS.Symlinks') p' in FS.Files' where: FS.Files'[p'] = fd if not exists(FS', p') : raise InvalidPathHandleException
實作必須依照 getPathHandle(FileStatus)
在建立時指定的約束,解析 PathHandle
的參照對象。
FileSystem
滿足此合約所需的元資料 MAY 編碼在 PathHandle
中。
result = FSDataInputStream(0, FS.Files'[p'])
傳回的串流會受到 open(Path)
傳回串流的約束限制。在開啟時檢查的約束 MAY 會對串流有效,但並非保證如此。
例如,使用 CONTENT
約束建立的 PathHandle
MAY 傳回一個在開啟後會忽略檔案更新的串流,如果在解析 open(PathHandle)
時檔案未修改。
實作 MAY 在伺服器或在將串流傳回給用戶端之前檢查不變性。例如,實作可能會開啟檔案,然後使用 getFileStatus(Path)
驗證 PathHandle
中的不變性,以實作 CONTENT
。這可能會產生假陽性,且需要額外的 RPC 流量。
boolean delete(Path p, boolean recursive)
刪除路徑,不論是檔案、符號連結或目錄。recursive
旗標指出是否應執行遞迴刪除,如果未設定,則無法刪除非空目錄。
除了根目錄的特殊情況外,如果此 API 呼叫已成功完成,則路徑末端沒有任何內容。也就是說:結果是預期的。傳回旗標僅告訴呼叫者檔案系統狀態是否已變更。
注意:此方法的許多用途都會檢查傳回值是否為 false,如果是則會引發例外。例如
if (!fs.delete(path, true)) throw new IOException("Could not delete " + path);
不需要這個模式。程式碼應僅呼叫 delete(path, recursive)
,並假設目標不再存在,但根目錄例外,根目錄將永遠存在(請參閱下方根目錄的特殊涵蓋範圍)。
具有子目錄且 recursive == False
的目錄無法刪除
if isDir(FS, p) and not recursive and (children(FS, p) != {}) : raise IOException
(HDFS 在這裡引發 PathIsNotEmptyDirectoryException
。)
如果檔案不存在,檔案系統狀態不會改變
if not exists(FS, p): FS' = FS result = False
結果應為 False
,表示沒有刪除任何檔案。
移除指向檔案的路徑,傳回值:True
if isFile(FS, p) : FS' = (FS.Directories, FS.Files - [p], FS.Symlinks) result = True
recursive == False
刪除空的根目錄不會改變檔案系統狀態,且可能會傳回 true 或 false。
if isRoot(p) and children(FS, p) == {} : FS ' = FS result = (undetermined)
嘗試刪除根目錄沒有相符的傳回碼。
實作應傳回 true;這可避免檢查 false 傳回值的程式碼反應過度。
物件儲存體:請參閱物件儲存體:根目錄刪除。
recursive == False
刪除非根目錄的空目錄會從檔案系統中移除路徑,並傳回 true。
if isDir(FS, p) and not isRoot(p) and children(FS, p) == {} : FS' = (FS.Directories - [p], FS.Files, FS.Symlinks) result = True
刪除具有子目錄且 recursive==True
的根目錄路徑通常會有三種結果
POSIX 模型假設如果使用者具有刪除所有內容的正確權限,他們可以自由執行此操作(導致檔案系統為空)。
if isDir(FS, p) and isRoot(p) and recursive : FS' = ({["/"]}, {}, {}, {}) result = True
HDFS 永不允許刪除檔案系統的根目錄;如果需要空的檔案系統,必須將檔案系統離線並重新格式化。
if isDir(FS, p) and isRoot(p) and recursive : FS' = FS result = False
物件儲存體:請參閱物件儲存體:根目錄刪除。
本規範不建議任何特定動作。不過,請注意,POSIX 模型假設有一個權限模型,使得一般使用者沒有權限刪除該根目錄;這是一個只有系統管理員才能執行的動作。
任何與缺乏此類安全模型的遠端檔案系統互動的檔案系統用戶端,可能會拒絕對 delete("/", true)
的呼叫,因為這使得資料遺失變得太容易。
一些基於物件儲存體的檔案系統實作在刪除根目錄時總是傳回 false,使儲存體的狀態保持不變。
if isRoot(p) : FS ' = FS result = False
這與遞迴旗標狀態或目錄狀態無關。
這是一種簡化,可避免不可避免的非原子掃描和刪除儲存體內容。它也避免了有關操作是否實際刪除該特定儲存體/容器本身以及儲存體較簡單權限模型的不利後果的任何混淆。
刪除具有子項目的非根路徑時,recursive==true
會移除路徑和所有子項
if isDir(FS, p) and not isRoot(p) and recursive : FS' where: not isDir(FS', p) and forall d in descendants(FS, p): not isDir(FS', d) not isFile(FS', d) not isSymlink(FS', d) result = True
刪除檔案必須是原子動作。
刪除空目錄必須是原子動作。
目錄樹的遞迴刪除必須是原子動作。
delete()
實作為遞迴列示和逐項刪除操作。這可能會破壞用戶端應用程式對 O(1) 原子目錄刪除的預期,讓儲存體無法作為 HDFS 的替代方案。boolean rename(Path src, Path d)
根據其規格,rename()
是檔案系統中最複雜的操作之一。
根據其實作,它在何時傳回 false 與引發例外之間最為含糊不清。
重新命名包括計算目標路徑。如果目標存在且為目錄,則重新命名的最終目標會變成目標 + 來源路徑的檔名。
let dest = if (isDir(FS, d) and d != src) : d + [filename(src)] else : d
在計算出最終 dest
路徑後,必須對目標路徑執行所有檢查。
來源 src
必須存在
exists(FS, src) else raise FileNotFoundException
dest
不能是 src
的子項
if isDescendant(FS, src, dest) : raise IOException
這隱含涵蓋了 isRoot(FS, src)
的特殊情況。
dest
必須是根,或具有存在的父項
isRoot(FS, dest) or exists(FS, parent(dest)) else raise IOException
目標的父路徑不能是檔案
if isFile(FS, parent(dest)) : raise IOException
這隱含涵蓋了父項的所有祖先。
目標路徑的結尾處不能有現有檔案
if isFile(FS, dest) : raise FileAlreadyExistsException, IOException
將目錄重新命名為自身為無操作;未指定傳回值。
在 POSIX 中,結果為 False
;在 HDFS 中,結果為 True
。
if isDir(FS, src) and src == dest : FS' = FS result = (undefined)
將檔案重新命名為自身為無操作;結果為 True
。
if isFile(FS, src) and src == dest : FS' = FS result = True
將檔案重新命名,其中目的地為目錄,會將檔案移至目的地目錄的子目錄,並保留來源路徑的檔案名稱元素。
if isFile(FS, src) and src != dest: FS' where: not exists(FS', src) and exists(FS', dest) and data(FS', dest) == data (FS, source) result = True
如果 src
是目錄,則其所有子目錄都將存在於 dest
下,而路徑 src
及其後代將不再存在。dest
下的路徑名稱將與 src
下的路徑名稱相符,內容亦然
if isDir(FS, src) and isDir(FS, dest) and src != dest : FS' where: not exists(FS', src) and dest in FS'.Directories and forall c in descendants(FS, src) : not exists(FS', c)) and forall c in descendants(FS, src) where isDir(FS, c): isDir(FS', dest + childElements(src, c) and forall c in descendants(FS, src) where not isDir(FS, c): data(FS', dest + childElements(s, c)) == data(FS, c) result = True
not exists(FS, parent(dest))
在此沒有任何一致的行為。
HDFS
結果不會變更檔案系統狀態,傳回值為 false。
FS' = FS; result = False
本機檔案系統
結果如同一般重新命名,具有額外的(隱含)功能,即目的地的父目錄也存在。
exists(FS', parent(dest))
S3A 檔案系統
結果如同一般重新命名,具有額外的(隱含)功能,即目的地的父目錄存在:exists(FS', parent(dest))
如果 parent(dest)
是檔案,則會檢查並拒絕,但不會檢查任何其他祖先。
其他檔案系統
其他檔案系統會嚴格拒絕操作,並引發 FileNotFoundException
rename()
的核心操作(將檔案系統中的某個項目移至另一個項目)必須是原子的。有些應用程式依賴此功能來協調對資料的存取。
有些檔案系統實作會在重新命名之前和之後檢查目的地檔案系統。其中一個範例是 ChecksumFileSystem
,它提供對本機資料的檢查和存取。整個順序可能不是原子的。
開啟用於讀取、寫入或追加的檔案
rename()
對開啟檔案的行為未指定:是否允許,稍後嘗試從開啟串流讀取或寫入會發生什麼事
重新命名目錄到自身
重新命名目錄到自身的傳回碼未指定。
目的地存在且為檔案
重新命名檔案到現有檔案上指定為失敗,並引發例外狀況。
本機檔案系統:重新命名會成功;目的地檔案會被來源檔案取代。
HDFS:重新命名會失敗,不會引發例外狀況。方法呼叫只會傳回 false。
來源檔案遺失
如果來源檔案 src
不存在,應引發 FileNotFoundException
。
HDFS 會失敗,但不會引發例外狀況;rename()
只會傳回 false。
FS' = FS result = false
HDFS 在此的行為不應被視為可複製的功能。FileContext
明確變更行為以引發例外狀況,而將此動作改造到 DFSFileSystem
實作仍是持續辯論的事項。
void concat(Path p, Path sources[])
將多個區塊合併在一起以建立單一檔案。這是一個鮮少使用的作業,目前僅由 HDFS 實作。
沒有相容呼叫的實作應擲回 UnsupportedOperationException
。
if not exists(FS, p) : raise FileNotFoundException if sources==[] : raise IllegalArgumentException
所有來源都必須在同一個目錄中
for s in sources: if parent(S) != parent(p) raise IllegalArgumentException
所有區塊大小都必須與目標相符
for s in sources: getBlockSize(FS, S) == getBlockSize(FS, p)
沒有重複路徑
not (exists p1, p2 in (sources + [p]) where p1 == p2)
HDFS:除了最後一個檔案之外的所有來源檔案都必須是完整區塊
for s in (sources[0:length(sources)-1] + [p]): (length(FS, s) mod getBlockSize(FS, p)) == 0
FS' where: (data(FS', T) = data(FS, T) + data(FS, sources[0]) + ... + data(FS, srcs[length(srcs)-1])) and for s in srcs: not exists(FS', S)
HDFS 的限制可能是透過變更 inode 參考來實作 concat
的實作細節,將它們串聯在一起。由於 Hadoop 核心程式碼庫中沒有其他檔案系統實作這個方法,因此無法區分實作細節和規格。
boolean truncate(Path p, long newLength)
將檔案 p
縮減至指定的 newLength
。
沒有相容呼叫的實作應擲回 UnsupportedOperationException
。
if not exists(FS, p) : raise FileNotFoundException if isDir(FS, p) : raise [FileNotFoundException, IOException] if newLength < 0 || newLength > len(FS.Files[p]) : raise HadoopIllegalArgumentException
HDFS:來源檔案必須關閉。無法對開啟用於寫入或附加的檔案執行縮減。
FS' where: len(FS.Files[p]) = newLength
傳回:如果縮減已完成且檔案可以立即開啟用於附加,則傳回 true
,否則傳回 false
。
HDFS:HDFS 傳回 false
以表示已啟動調整最後一個區塊長度的背景程序,且客戶端應等到它完成才能繼續進行進一步的檔案更新。
如果在執行 truncate() 時開啟輸入串流,則與正在縮減的檔案部分相關的讀取作業結果未定義。
boolean copyFromLocalFile(boolean delSrc, boolean overwrite, Path src, Path dst)
src
處的來源檔案或目錄位於本機磁碟中,且會複製到目的地 dst
的檔案系統中。如果必須在移動後刪除來源,則必須將 delSrc
旗標設定為 TRUE。如果目的地已存在,且必須覆寫目的地內容,則必須將 overwrite
旗標設定為 TRUE。
來源和目的地必須不同
if src = dest : raise FileExistsException
目的地和來源不能彼此為後代
if isDescendant(src, dest) or isDescendant(dest, src) : raise IOException
來源檔案或目錄必須存在於本機
if not exists(LocalFS, src) : raise FileNotFoundException
目錄無法複製到檔案中,不論覆寫旗標設定為何
if isDir(LocalFS, src) and isFile(FS, dst) : raise PathExistsException
對於所有情況,除了上述前提條件會引發例外情況的情況之外,如果目的地存在,則必須將覆寫旗標設定為 TRUE,作業才能成功。這也會覆寫目的地中的所有檔案/目錄
if exists(FS, dst) and not overwrite : raise PathExistsException
假設來源 base
上有一個基本路徑和一個子路徑 child
,其中 base
位於 ancestors(child) + child
中
def final_name(base, child, dest): is base = child: return dest else: return dest + childElements(base, child)
isFile(LocalFS, src)
對於檔案,目的地中的資料會變成來源中的資料。所有祖先都是目錄。
if isFile(LocalFS, src) and (not exists(FS, dest) or (exists(FS, dest) and overwrite)): FS' = FS where: FS'.Files[dest] = LocalFS.Files[src] FS'.Directories = FS.Directories + ancestors(FS, dest) LocalFS' = LocalFS where not delSrc or (delSrc = true and delete(LocalFS, src, false)) else if isFile(LocalFS, src) and isDir(FS, dest): FS' = FS where: let d = final_name(src, dest) FS'.Files[d] = LocalFS.Files[src] LocalFS' = LocalFS where: not delSrc or (delSrc = true and delete(LocalFS, src, false))
對於本機 LocalFS
和遠端 FS
,沒有預期檔案變更會是原子性的。
isDir(LocalFS, src)
if isDir(LocalFS, src) and (isFile(FS, dest) or isFile(FS, dest + childElements(src))): raise FileAlreadyExistsException else if isDir(LocalFS, src): if exists(FS, dest): dest' = dest + childElements(src) if exists(FS, dest') and not overwrite: raise PathExistsException else: dest' = dest FS' = FS where: forall c in descendants(LocalFS, src): not exists(FS', final_name(c)) or overwrite and forall c in descendants(LocalFS, src) where isDir(LocalFS, c): FS'.Directories = FS'.Directories + (dest' + childElements(src, c)) and forall c in descendants(LocalFS, src) where isFile(LocalFS, c): FS'.Files[final_name(c, dest')] = LocalFS.Files[c] LocalFS' = LocalFS where not delSrc or (delSrc = true and delete(LocalFS, src, true))
沒有預期操作隔離/原子性。這表示在操作執行期間,檔案會在來源或目的地中變更。在複製後,不保證檔案或目錄的最終狀態,只會盡力而為。例如:複製目錄時,可以將一個檔案從來源移到目的地,但沒有任何機制可以阻止複製操作仍在進行時,目的地中的新檔案被更新。
預設的 HDFS 實作是遞迴尋找在 src
中找到的每個檔案和資料夾,然後將它們依序複製到最終目的地(相對於 dst
)。
基於物件儲存的檔案系統應該注意上述實作產生的限制,並可以利用並行上傳和重新排序複製到儲存空間中的檔案,以最大化傳輸量。
RemoteIterator
RemoteIterator
介面用作 java.util.Iterator
的遠端存取等效項,讓呼叫者可以遞迴瀏覽有限序列的遠端資料元素。
核心差異為
Iterator
的選用 void remove()
方法不受支援。IOException
例外。public interface RemoteIterator<E> { boolean hasNext() throws IOException; E next() throws IOException; }
介面的基本觀點是,如果 hasNext()
為 true,則表示 next()
會成功傳回清單中的下一個項目
while hasNext(): next()
同樣地,成功呼叫 next()
表示如果在呼叫 next()
之前呼叫 hasNext()
,則會為 true。
boolean elementAvailable = hasNext(); try { next(); assert elementAvailable; } catch (NoSuchElementException e) { assert !elementAvailable }
next()
算子必須遞迴瀏覽可用結果清單,即使沒有呼叫 hasNext()
。
也就是說,可以透過只在引發 NoSuchElementException
例外時才會終止的迴圈來列舉結果。
try { while (true) { process(iterator.next()); } } catch (NoSuchElementException ignored) { // the end of the list has been reached }
遞迴的輸出等同於迴圈
while (iterator.hasNext()) { process(iterator.next()); }
由於在 JVM 中引發例外是一個昂貴的操作,因此 while(hasNext())
迴圈選項更有效率。(另請參閱 並行性和遠端反覆運算器,以了解此主題的討論)。
介面的實作人員必須支援這兩種形式的迭代;測試的作者應驗證這兩種迭代機制是否運作。
迭代必須傳回有限的順序;這兩種形式的迴圈最終都必須終止。Hadoop 程式碼庫中介面所有的實作都符合此需求;所有使用者都假設它成立。
boolean hasNext()
如果且僅如果後續單一呼叫 next()
會傳回元素,而不是引發例外,則傳回 true。
result = True ==> next() will succeed. result = False ==> next() will raise an exception
多次呼叫 hasNext()
,而沒有任何介入的 next()
呼叫,必須傳回相同的值。
boolean has1 = iterator.hasNext(); boolean has2 = iterator.hasNext(); assert has1 == has2;
E next()
傳回迭代中的下一個元素。
hasNext() else raise java.util.NoSuchElementException
result = the next element in the iteration
重複呼叫 next()
會傳回順序中的後續元素,直到整個順序都傳回為止。
在檔案系統 API 中使用 RemoteIterator
的主要用途是在(可能是遠端的)檔案系統上列出檔案。這些檔案系統總是同時存取;檔案系統的狀態可能會在 hasNext()
探查和呼叫 next()
之間變更。
在透過 RemoteIterator
進行迭代期間,如果遠端檔案系統上的目錄已刪除,則 hasNext()
或 next()
呼叫可能會引發 FileNotFoundException
。
因此,透過 RemoteIterator
的穩健迭代會在過程中擷取並捨棄引發的 NoSuchElementException
例外,這可透過上述的 while(true)
迭代範例,或透過具有外部 try/catch
子句的 hasNext()/next()
順序來完成,以在失敗期間擷取 NoSuchElementException
及可能引發的其他例外(例如 FileNotFoundException
)
try { while (iterator.hasNext()) { process(iterator.next()); } } catch (NoSuchElementException ignored) { // the end of the list has been reached }
值得注意的是,Hadoop 程式碼庫中沒有這樣做。這並不表示不建議使用穩健迴圈,而是表示在實作這些迴圈時並未考量並行性問題。
StreamCapabilities
StreamCapabilities
提供一種以程式方式查詢 OutputStream
、InputStream
或其他 FileSystem 類別支援的功能的方法。
public interface StreamCapabilities { boolean hasCapability(String capability); }
boolean hasCapability(capability)
如果 OutputStream
、InputStream
或其他 FileSystem 類別具有所需的效能,則傳回 true。
呼叫者可以使用字串值查詢串流的效能。以下是可能的字串值表格
字串 | 常數 | 實作 | 說明 |
---|---|---|---|
hflush | HFLUSH | Syncable | 清除用戶端使用者緩衝區中的資料。呼叫此函式後,新的讀取器將會看到資料。 |
hsync | HSYNC | Syncable | Flush 出用戶端使用者緩衝區中的資料至磁碟裝置(但磁碟可能將其保留在快取中)。類似於 POSIX fsync。 |
in:readahead | READAHEAD | CanSetReadahead | 設定輸入串流的預讀取。 |
dropbehind | DROPBEHIND | CanSetDropBehind | 捨棄快取。 |
in:unbuffer | UNBUFFER | CanUnbuffer | 減少輸入串流的緩衝。 |
EtagSource
偵測FileSystem 實作可能會支援從 FileStatus
項目查詢 HTTP etag。如果是這樣,需求如下
getFileStatus()
呼叫中。也就是說:當新增 etag 支援時,所有傳回 FileStatus
或 ListLocatedStatus
項目的作業都必須傳回 EtagSource
執行個體的子類別。
FileStatus
執行個體就必須具備 etag。若要支援 etag,必須在 getFileStatus()
和 list 呼叫中提供 etag。
實作人員請注意:必須覆寫的核心 API 如下
FileStatus getFileStatus(Path) FileStatus[] listStatus(Path) RemoteIterator<FileStatus> listStatusIterator(Path) RemoteIterator<LocatedFileStatus> listFiles([Path, boolean)
EtagSource.getEtag()
的值對於傳回特定物件的 getFileStatus()
呼叫的 etag 的 list* 查詢必須相同。
((EtagSource)getFileStatus(path)).getEtag() == ((EtagSource)listStatus(path)[0]).getEtag()
同樣地,對於路徑的 listFiles()
、listStatusIncremental()
以及列出父路徑時,清單中所有檔案都必須傳回相同的值。
寫入相同路徑的兩個不同資料陣列在偵測時必須具有不同的 etag 值。這是 HTTP 規格的要求。
重新命名檔案後,((EtagSource)getFileStatus(dest)).getEtag()
的值應與重新命名之前 ((EtagSource)getFileStatus(source)).getEtag()
的值相同。
這是儲存的實作細節;AWS S3 不適用。
如果且僅當儲存庫持續符合此需求時,檔案系統應在 hasPathCapability()
中宣告它支援 fs.capability.etags.preserved.in.rename
目錄項目 MAY 在列示/探測操作中傳回 etag;這些項目 MAY 在重新命名中保留。
同樣地,目錄項目 MAY 不提供此類項目,MAY 不在重新命名中保留它們,且 MAY 不保證隨著時間的一致性。
注意:特別提到根路徑「/」。由於這不是真正的「目錄」,因此不應期望它有 etag。
FileStatus
子類別 MUST 為 Serializable
;MAY 為 Writable
基礎 FileStatus
類別實作 Serializable
和 Writable
,並適當地封送其欄位。
子類別 MUST 支援 Java 序列化(某些 Apache Spark 應用程式使用它),保留 etag。這表示讓 etag 欄位非靜態並新增 serialVersionUID
。
Writable
支援用於封送 Hadoop IPC 呼叫的狀態資料;在 Hadoop 3 中,這是透過 org/apache/hadoop/fs/protocolPB/PBHelper.java
實作,且方法已棄用。子類別 MAY 覆寫已棄用的方法以新增 etag 封送。但是,並沒有預期這會發生,而且此類封送不太可能發生。
hasPathCapability(path, "fs.capability.etags.available")
僅在檔案系統在檔案狀態/列示操作中傳回有效(非空 etag)時,MUST 傳回 true。hasPathCapability(path, "fs.capability.etags.consistent.across.rename")
僅在 etag 在重新命名中保留時,MUST 傳回 true。FileSystem.getFileChecksum(Path)
傳回與物件 etag 相關的檢查總和值。