org.apache.hadoop.fs.FileSystem
抽象的 FileSystem
类是访问 Hadoop 文件系统的原始类;所有 Hadoop 支持的文件系统都有非抽象子类。
所有将 Path 传递到此接口的操作都必须支持相对路径。在这种情况下,它们必须相对于由 setWorkingDirectory()
定义的工作目录进行解析。
因此,对于所有客户端,我们还增加了状态组件 PWD 的概念:它表示客户端当前的工作目录。对该状态的更改不会反映在文件系统本身中:它们是客户端实例独有的。
实现说明:静态 FileSystem get(URI uri, Configuration conf)
方法可能返回文件系统客户端类的现有实例,该类也可能在其他线程中使用。Apache Hadoop 附带的 FileSystem
实现不会尝试同步访问工作目录字段。
有效 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 访问权限的文件系统和对象存储库始终必须向 isEncrypted()
谓词返回 true
。这可以通过在创建 FileStatus
实例时将 encrypted
标志设置为 true 来完成。
msync()
将客户端的元数据状态与文件系统的元数据服务的最新状态同步。
在高可用性文件系统中,备用服务可用作只读元数据副本。此调用对于保证从备用副本读取的一致性以及避免读取过时数据至关重要。
它目前仅针对 HDFS 实现,其他文件系统只会抛出 UnsupportedOperationException
。
此调用在内部记录调用时的元数据服务状态。这保证了从任何元数据副本进行后续读取的一致性。它确保客户端永远不会访问先于记录状态的元数据状态。
HDFS 通过调用活动名称节点并请求其最新的日志事务 ID 来在 HA 模式下支持 msync()
。有关更多详细信息,请参阅 HDFS 文档 从 HDFS 观察者名称节点进行一致读取
Path getHomeDirectory()
函数 getHomeDirectory
返回 FileSystem 和当前用户帐户的主目录。
对于某些 FileSystem,路径为 ["/", "users", System.getProperty("user-name")]
。
但是,对于 HDFS,用户名是从用于对客户端进行 HDFS 身份验证的凭据派生的。这可能与本地用户帐户名称不同。
FileSystem 负责确定调用者的实际主目录。
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)
仅当路径 path
满足过滤器的条件时才返回 true。
路径 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 调用均未定义返回值的任何顺序。需要对结果进行统一排序顺序的应用程序必须自行执行排序。
空返回:3.0.0 之前的本地文件系统在访问错误时返回空。这被认为是错误的。在访问错误时应返回 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]
实现 MAY 合并重复条目;和/或通过识别重复路径并仅列出条目一次来优化操作。
默认实现遍历列表;它不执行任何优化。
原子性和一致性约束与 listStatus(Path, PathFilter)
相同。
RemoteIterator<FileStatus> listStatusIterator(Path p)
返回一个迭代器,枚举路径下的 FileStatus
条目。这类似于 listStatus(Path)
,但不同之处在于返回一个迭代器,而不是返回一个完整列表。结果与 listStatus(Path)
完全相同,前提是没有其他调用者在列出期间更新目录。话虽如此,如果其他调用者在执行列出时添加/删除目录中的文件,则不保证原子性。不同的文件系统可能会提供更有效的实现,例如 S3A 以页面形式进行列出,并在处理一个页面时异步获取下一页。
请注意,由于初始列出是异步的,因此 bucket/path 存在异常可能会在下次调用中显示出来。
调用者应该优先使用 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
及其等效项在计算分区时会耗尽内存。
任何实际上不会将文件拆分为块的文件系统都应该返回一个数字,以便进行高效处理。文件系统可以使此功能可由用户配置(对象存储连接器通常会这样做)。
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("Cannot open filename " + 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
。默认选项集如下。
未移动 | 已移动 | |
---|---|---|
未更改 | EXACT | CONTENT |
已更改 | PATH | REFERENCE |
所有权、扩展属性和其他元数据的更改不需要与 PathHandle
匹配。实现可以通过自定义约束扩展 HandleOpt
参数集。
客户端指定 PathHandle
应使用 REFERENCE
跨重命名跟踪实体。在创建 PathHandle
时,除非无法解析引用意味着实体不再存在,否则实现必须抛出 UnsupportedOperationException
。
客户端指定 PathHandle
应仅在实体使用 PATH
保持不变时解析。在创建 PathHandle
时,除非它可以区分随后位于同一路径的相同实体,否则实现必须抛出 UnsupportedOperationException
。
result = PathHandle(p')
PathHandle
的引用是创建 FileStatus
实例时的命名空间,而不是创建 PathHandle
时的状态。实现可以拒绝创建或解析有效但服务成本高的 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
满足此契约所需的元数据可能会编码在 PathHandle
中。
result = FSDataInputStream(0, FS.Files'[p'])
返回的流受 open(Path)
返回的流的约束。在打开时检查的约束可能会保留在流中,但这并不能得到保证。
例如,使用 CONTENT
约束创建的 PathHandle
可能会返回一个流,该流在打开文件后忽略对文件的更新(如果在解析 open(PathHandle)
时文件未修改)。
实现可以在服务器或在将流返回给客户端之前检查不变量。例如,实现可以打开文件,然后使用 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;这避免了检查虚假返回值的代码过度反应。
对象存储:请参阅 对象存储:根目录删除。
recursive == False
删除非根的空目录将从 FS 中移除路径并返回 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()
的重复调用返回序列中的后续元素,直到返回整个序列。
RemoteIterator
在文件系统 API 中的主要用途是在(可能是远程的)文件系统上列出文件。这些文件系统总是并发访问的;文件系统的状态可能会在 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 | 可同步 | 刷新客户端用户缓冲区中的数据。此调用返回后,新读者将看到数据。 |
hsync | HSYNC | 可同步 | 刷新客户端用户缓冲区中的数据,一直到磁盘设备(但磁盘可能将其保留在缓存中)。类似于 POSIX fsync。 |
in:readahead | READAHEAD | CanSetReadahead | 设置输入流上的预读。 |
dropbehind | DROPBEHIND | CanSetDropBehind | 丢弃缓存。 |
in:unbuffer | UNBUFFER | CanUnbuffer | 减少输入流上的缓冲。 |
EtagSource
探测 Etag文件系统实现可能支持从 FileStatus
条目查询 HTTP etag。如果是这样,则要求如下
getFileStatus()
调用中。也就是说:添加 etag 支持时,所有返回 FileStatus
或 ListLocatedStatus
条目的操作都必须返回 EtagSource
实例的子类。
FileStatus
实例必须具有 etag。为了支持 etag,必须在 getFileStatus()
和 list 调用中提供它们。
实现者注意:必须重写以实现此功能的核心 API 如下
FileStatus getFileStatus(Path) FileStatus[] listStatus(Path) RemoteIterator<FileStatus> listStatusIterator(Path) RemoteIterator<LocatedFileStatus> listFiles([Path, boolean)
对于返回特定对象的 getFileStatus()
调用的 etag 的 list* 查询,EtagSource.getEtag()
的值必须相同。
((EtagSource)getFileStatus(path)).getEtag() == ((EtagSource)listStatus(path)[0]).getEtag()
同样,对于路径的 listFiles()
、listStatusIncremental()
以及列出父路径时,列表中所有文件的 EtagSource.getEtag()
的值必须相同。
写入同一路径的两个不同的数据数组在探测时必须具有不同的 etag 值。这是 HTTP 规范的要求。
重命名文件后,((EtagSource)getFileStatus(dest)).getEtag()
的值应与重命名之前 ((EtagSource)getFileStatus(source)).getEtag()
的值相同。
这是存储的实现细节;它不适用于 AWS S3。
当且仅当存储始终满足此要求时,文件系统应在 hasPathCapability()
中声明它支持 fs.capability.etags.preserved.in.rename
目录条目可能会在列出/探测操作中返回 etag;这些条目可能会在重命名中保留。
同样,目录条目可能不会提供此类条目,可能不会在重命名中保留它们,并且可能无法保证随着时间的推移而保持一致性。
注意:特别提到了根路径“/”。由于这不是真正的“目录”,因此任何人都不会期望它具有 etag。
FileStatus
子类必须是 Serializable
;可能是 Writable
基本 FileStatus
类实现了 Serializable
和 Writable
,并适当地编组其字段。
子类必须支持 Java 序列化(一些 Apache Spark 应用程序使用它),保留 etag。这是一个使 etag 字段非静态并添加 serialVersionUID
的问题。
Writable
支持用于通过 Hadoop IPC 调用编组状态数据;在 Hadoop 3 中,这是通过 org/apache/hadoop/fs/protocolPB/PBHelper.java
实现的,并且方法已弃用。子类可以覆盖已弃用的方法以添加 etag 编组。但是,对此没有任何期望,并且此类编组不太可能发生。
hasPathCapability(path, "fs.capability.etags.available")
必须返回 true,当且仅当文件系统在文件状态/列出操作中返回有效(非空 etag)时。hasPathCapability(path, "fs.capability.etags.consistent.across.rename")
必须返回 true,当且仅当 etag 在重命名中保留时。FileSystem.getFileChecksum(Path)
返回与对象的 etag 相关的校验和值(如果返回任何值)。