☰
仓颉标准库 std.fs 文件系统编程指南:路径、文件、目录与链接操作全解析
2026/10/15 16:09:27 网站建设 项目流程
  • 编程语言
  • 语言运行时
  • 标准库
  • 垃圾回收
  • 并发编程

【免费下载链接】cangjie_runtime

仓颉编程语言运行时与标准库。

项目地址:https://gitcode.com/Cangjie/cangjie_runtime
点击查看免费下载

std.fs 是仓颉标准库中负责文件系统操作的核心包,为开发者提供文件、文件夹、路径与文件元数据的完整操作能力。本文以仓库内 fs 包概述文档 为骨架,结合 stdlib/libs/std/fs 下的源码实现与各 API 参考文档,系统讲解 Path 路径处理、顶层文件系统函数、File 流式读写、Directory 目录遍历、硬链接/符号链接以及 FileInfo 元数据查询等内容。读完本文,你将能够熟练使用 std.fs 完成文件与目录的创建、拷贝、重命名、删除、读写和权限查询等日常开发任务。

std.fs 包概览与能力边界

fs(file system)包提供对文件、文件夹、路径、文件元数据信息的一些操作函数。整个包的 API 构成如下:

类别成员能力
顶层函数canonicalize / copy / exists / rename / remove / removeIfExists路径规范化、拷贝、存在性判断、重命名、删除
类Directory、File、HardLink、SymbolicLink目录操作、文件读写、硬链接与符号链接
枚举OpenMode文件打开模式
结构体FileDescriptor、FileInfo、Path文件句柄、元数据、路径
异常类FSException文件系统异常

权限与平台限制

注意:std.fs 仅能对有相应权限的文件、目录或其他相关信息做出操作。例如iOS由于沙盒机制的原因,系统仅允许对应用的 Documents、Library、tmp 等目录下的文件和文件夹进行操作。

在撰写业务代码时,应当始终考虑目标平台的权限模型:Linux/macOS 下权限判断基于 POSIX 文件权限位,而 Windows 下部分权限语义不同(详见下文 FileInfo 权限 API 的逐项说明)。此外,所有涉及路径的 API 都会对空路径或包含字符串结束符(\0)的路径抛出 IllegalArgumentException,具体实现在PathValidator.throwIfEmptyOrContainsNullByte(见 stdlib/libs/std/fs/utils.cj)。

Path:路径的表示、解析与规范化

Path 是 std.fs 中最基础的结构体,几乎所有其他 API 都接受Path或String两种形式的路径参数。从源码看,Path内部仅封装一个原始路径字符串_rawPath(见 stdlib/libs/std/fs/path.cj):

public struct Path <: Equatable<Path> & Hashable & ToString { public static const Separator: String = PATH_SEPARATOR public static const ListSeparator: String = PATH_LISTSEPARATOR public init(rawPath: String) }

Path构造时不检查路径字符串是否合法,支持绝对路径与相对路径。路径的字符串最大支持 4096 个字节(包括结束符\0),Windows 平台已支持 DOS 设备路径和 UNC 路径,长度限制跟随系统。

路径分隔符常量

常量含义Windows非 Windows
Path.Separator路径分隔符,用于分隔多级目录\/
Path.ListSeparator路径列表分隔符,用于分隔路径列表中的不同路径;:

源码中这两个常量由条件编译宏决定(见 stdlib/libs/std/fs/path.cj):

@When[os != "Windows"] const PATH_SEPARATOR = "/" @When[os == "Windows"] const PATH_SEPARATOR = "\\" @When[os != "Windows"] const PATH_LISTSEPARATOR = ":" @When[os == "Windows"] const PATH_LISTSEPARATOR = ";"

路径组成部分属性

Path提供以下只读属性,用于拆分路径:

属性说明典型示例
parent父路径。以最后一个有效文件分隔符(末尾分隔符会被忽略)为分界,parent 保留表示根目录的分隔符;无父目录时返回空Path/a/b/c→/a/b;/a→/;/→/
fileName文件名(含扩展名)。Windows 下不包括卷名部分./NewFile.txt→NewFile.txt;/a/b/→b
fileNameWithoutExtension文件名(不含扩展名),按最后一个.划分./a.b.c→a.b;./.gitignore→""
extensionName文件扩展名,无扩展名时返回空字符串./NewFile.txt→txt;./noextension→""

值得注意的边界行为(文档与示例 path_samples.md 均有演示):对于./和.,fileName返回.而extensionName为空;对于123.,fileNameWithoutExtension返回123。parent属性不会访问文件系统,也不会消除特殊名称,因此不会消除..等组件——例如/a/./../b的parent仍是/a/./..,若需要语义化的解析应结合normalize()或canonicalize()。

常用方法

  • isAbsolute()/isRelative():判断绝对/相对路径,Unix 中以/开头为绝对路径;二者结果互反。
  • isEmpty():判断是否为空路径。
  • join(path: Path | String):在当前路径后拼接另一路径。仅做字符串拼接,不处理..等路径遍历组件;参数为绝对路径时抛出 FSException。例如Path("/home/user").join("documents")得到/home/user/documents。
  • normalize():将路径字符串按规则规范化,仅做字符串解析,不做任何 IO 操作。规则包括:合并连续分隔符、删除末尾分隔符、删除.元素、删除..及其前面的非..元素、删除根路径开始的..、相对路径保留开头的../、空结果返回Path(".")。
  • hashCode()/toString():哈希值与路径字符串。
  • operator ==:判等时先对两个 Path 做规范化(规则同normalize()),规范化后字符串相等则视为同一路径。

canonicalize:获取真实绝对路径

与纯字符串处理的normalize()不同,顶层函数 canonicalize 会实际访问文件系统:所有的中间引用(.、..)和软链接都会被解析处理,返回绝对路径形式的规范化路径。例如路径/foo/test/../test/bar.txt会被规范化为/foo/test/bar.txt(UNC 路径下的软链接无法被规范化)。

从源码看(stdlib/libs/std/fs/path.cj),canonicalize先校验路径合法性,然后检查路径是否存在:

public func canonicalize(path: String): Path { PathValidator.throwIfEmptyOrContainsNullByte("path", path, true) if (!exists(path)) { throw FSException("Failed to canonicalize path '${path}': path does not exist or permission denied.") } return Path(processPathHeader(path, Path.canonicalizePath(path))) }

由此可归纳出canonicalize的两个关键异常场景:路径不存在或无法规范化时抛出FSException;路径为空或包含字符串结束符时抛出IllegalArgumentException。典型用法见 fs_package_funcs.md:

import std.fs.* main(): Unit { removeIfExists("./test/../test_canonicalize_path.txt", recursive: true) let testPath = Path("./test/../test_canonicalize_path.txt") Directory.create(testPath, recursive: true) let canonicalPath = canonicalize(testPath) println("Original path: ${testPath}") println("Canonical path: ${canonicalPath}") removeIfExists("./test/../test_canonicalize_path.txt", recursive: true) }

可能的运行结果(示例路径下):

Original path: ./test/../test_canonicalize_path.txt Canonical path: /home/user/test/test_canonicalize_path.txt

安全建议:由于join不做路径遍历防护,文档明确提示:如需安全验证,请先调用canonicalize并检查路径范围。这在处理用户输入路径时尤为重要。

顶层文件系统函数

std.fs 在包级别提供了 6 组文件系统函数,每组都同时提供Path与String两个重载,语义完全一致。它们定义在 stdlib/libs/std/fs/common.cj,其String重载最终都会转换为Path形式再委托处理。

exists:存在性判断

public func exists(path: Path | String): Bool

判断目标地址是否存在,底层调用原生接口CJ_FS_Exists(见 stdlib/libs/std/fs/common.cj)。路径为空或含空字符时抛出IllegalArgumentException。典型用法:

import std.fs.* main(): Unit { removeIfExists("./test_exists_path.txt", recursive: true) let notExist = exists(Path("./test_exists_path.txt")) println("File exists before creation: ${notExist}") var data: Array<Byte> = [69, 120, 105, 115, 116, 115] // "Exists" File.writeTo("./test_exists_path.txt", data) let exist = exists(Path("./test_exists_path.txt")) println("File exists after creation: ${exist}") removeIfExists("./test_exists_path.txt", recursive: true) }

运行结果:File exists before creation: false、File exists after creation: true。

copy:拷贝文件或目录

public func copy(sourcePath: Path | String, to!: Path | String, overwrite!: Bool = false): Unit

当前支持的拷贝类型包括:目录(Directory)、常规文件(Regular file)、符号链接(SymbolicLink)。overwrite默认为false;若目标位置已存在且未开启覆盖模式,抛出 FSException。

从源码实现看(stdlib/libs/std/fs/common.cj),copy的完整行为如下:

  • 常规文件:目标存在且overwrite = false时抛异常;overwrite = true时要求目标也必须是常规文件,否则抛异常,随后调用File.copy。
  • 目录:目标存在且overwrite = false时抛异常;覆盖模式下目标必须是目录,随后调用Directory.copy。目录拷贝采用队列驱动的广度优先遍历(copyDirectory/walkAndCopy,见 stdlib/libs/std/fs/directory.cj),逐个创建子目录并拷贝文件。
  • 符号链接:目标存在且overwrite = true时会先删除旧目标,再读取链接目标并重新创建链接。
  • 源与目标为同一路径时抛出FSException("refer to the same path")。
  • 其他文件类型(如设备文件)抛出 "unsupported file type" 异常。

说明:若目标位置已存在且开启覆盖模式,要求源路径与目标路径对应的类型保持一致(例如源路径为目录类型则目标路径也需为目录类型,不可为文件类型),类型不一致时将抛出 FSException 异常。

示例(见 fs_package_funcs.md):

import std.fs.* main(): Unit { removeIfExists("./test_copy_source.txt", recursive: true) removeIfExists("./test_copy_target.txt", recursive: true) var data: Array<Byte> = [67, 111, 112, 121] // "Copy" File.writeTo("./test_copy_source.txt", data) copy(Path("./test_copy_source.txt"), to: Path("./test_copy_target.txt"), overwrite: false) if (exists("./test_copy_target.txt")) { let content = File.readFrom("./test_copy_target.txt") println("File copied successfully, content: ${content}") } else { println("File copy failed") } removeIfExists("./test_copy_source.txt", recursive: true) removeIfExists("./test_copy_target.txt", recursive: true) }

运行结果:File copied successfully, content: [67, 111, 112, 121]。

rename:重命名文件或目录

public func rename(sourcePath: Path | String, to!: Path | String, overwrite!: Bool = false): Unit

将sourcePath指定的文件或目录重命名为to给定的名称。sourcePath必须是现有路径;如果to已存在,则行为由overwrite决定:为true时先删除现有文件或目录再执行重命名,为false时抛出异常。源码还做了同路径检查与存在性预检(stdlib/libs/std/fs/common.cj):

if (Path(sourcePath) == Path(to)) { throw FSException("Invalid argument: 'sourcePath' and 'to' refer to the same path ...") } if (exists(to) && !overwrite) { throw FSException("Cannot rename to '${to}': destination path already exists and overwrite is false.") }

注意:当overwrite为true时,rename的一个隐含行为是删除目标位置的原有文件或目录;如果目标位置是目录,将会递归删除目录内的所有内容,需要谨慎使用。

底层通过原生接口CJ_FS_Rename调用操作系统 rename,操作系统执行失败时抛出 FSException。

remove 与 removeIfExists:删除文件或目录

public func remove(path: Path | String, recursive!: Bool = false): Unit public func removeIfExists(path: Path | String, recursive!: Bool = false): Bool
  • remove:删除文件或目录。目标是文件夹时,可通过recursive选择是否递归删除(默认false)。如果指定目录不存在或删除失败,抛出 FSException。底层调用CJ_FS_Remove并透传递归标志(stdlib/libs/std/fs/common.cj)。
  • removeIfExists:先判断目标是否存在,存在则执行remove并返回true,不存在返回false。该函数是 std.fs 示例代码中最常用的"清理残留"工具,几乎所有示例都以removeIfExists(...)开头,避免上次运行残留导致创建失败。

两个函数都遵循:删除失败抛 FSException;路径为空或含空字符抛 IllegalArgumentException。

OpenMode:文件打开模式

OpenMode 是File构造时指定的读写权限枚举。源码中的注释直接对应 POSIX 打开标志(stdlib/libs/std/fs/file.cj):

枚举值对应系统打开标志行为
ReadO_RDONLY只读打开;文件不存在时抛出 FSException
WriteO_WRONLY \| O_CREAT \| O_TRUNC只写打开;文件存在则截断为零字节,不存在则创建
AppendO_WRONLY \| O_CREAT \| O_APPEND追加写入;文件不存在则创建
ReadWriteO_RDWR \| O_CREAT读写打开;文件不存在则创建

注意:ReadWrite 模式不会使文件被截断为零字节大小。

OpenMode实现了ToString与Equatable<OpenMode>,提供toString()、operator ==、operator !=,toString()返回枚举名(Read/Write/Append/ReadWrite)。

File:文件的打开、读写与截断

File 是文件操作的核心类,声明如下:

public class File <: Resource & IOStream & Seekable { public init(path: String, mode: OpenMode) public init(path: Path, mode: OpenMode) }

父类型为 Resource、IOStream 与 Seekable。从源码看(stdlib/libs/std/fs/file.cj),File对象会默认打开对应的文件,构造时检查父目录是否存在,并通过openFile()依据_openMode完成系统调用。

注意:创建的File对象会默认打开对应的文件,当使用结束后需要及时调用close()关闭文件,否则会造成资源泄露。File类还实现了~init()析构函数(见 stdlib/libs/std/fs/file.cj),会在对象销毁时自动关闭底层句柄,但这不能替代显式close()的最佳实践。

File 的静态便捷方法

静态方法签名行为
File.createcreate(path: Path/String): File创建指定路径文件并返回只写模式的 File 实例;上级目录不存在或文件已存在时抛 FSException
File.readFromreadFrom(path: Path/String): Array<Byte>一次性读取文件全部内容,以字节数组返回
File.writeTowriteTo(path: Path/String, buffer: Array<Byte>): Unit以覆盖方式写入:文件存在则先截断为零字节,不存在则创建
File.appendToappendTo(path: Path/String, buffer: Array<Byte>): Unit以追加方式写入,文件不存在则创建
File.createTempcreateTemp(directoryPath: Path/String): File在指定目录下创建临时文件,文件名为tmpFileXXXXXX形式;不使用的临时文件应手动删除

File 的属性与实例方法

属性:

  • fileDescriptor: FileDescriptor:获取文件描述符(文件句柄)信息。
  • info: FileInfo:获取文件元数据信息。
  • length: Int64:获取文件头至文件尾的数据字节数(通过getsize()实时获取)。

实例方法:

方法说明
canRead()/canWrite()判断当前 File 对象是否可读/可写,由创建时的 openMode 决定,文件关闭后返回 false
isClosed()/close()判断是否已关闭 / 关闭文件
read(buffer: Array<Byte>): Int64从文件读出数据到 buffer,返回读取字节数,文件读完返回 0;buffer 为空抛 IllegalArgumentException
write(buffer: Array<Byte>)将 buffer 数据写入文件;buffer 为空直接返回;写入失败、只写部分、文件已关闭或不可写时抛 FSException
seek(sp: SeekPosition): Int64将光标跳转到指定位置,返回文件头到新位置的偏移量(字节)。位置不能位于文件头之前;可超过文件末尾,但最大偏移量不能超过文件系统允许的最大值(一般约为最大文件大小减 4096 字节)
setLength(length: Int64)将文件截断为指定长度;length 大于当前长度时用 0 填充,不改变光标位置;负数抛 IllegalArgumentException
flush()将缓冲区数据写入流。由于File不存在缓冲区,该函数没有具体作用(源码中为空实现,见 stdlib/libs/std/fs/file.cj)

File 读写完整示例

综合示例见 fs_samples/file_samples.md,覆盖四种打开模式与 seek/setLength 组合:

import std.fs.* import std.io.* main() { let filePath: Path = Path("./tempFile.txt") removeIfExists(filePath) // 只写模式:写入三遍 "123456789\n" var file: File = File(filePath, Write) let bytes: Array<Byte> = "123456789\n".toArray() for (_ in 0..3) { file.write(bytes) } file.close() // 追加模式:写入 "abcdefghi\n" file = File(filePath, Append) file.write("abcdefghi\n".toArray()) file.close() // 只读模式:从偏移 10 处与文件末尾各读 10 字节 file = File(filePath, Read) let readBuffer: Array<Byte> = Array<Byte>(10, repeat: 0) file.seek(SeekPosition.Begin(10)) file.read(readBuffer) println("第 10 字节之后的 10 个字节: ${String.fromUtf8(readBuffer)}") file.seek(SeekPosition.End(-10)) file.read(readBuffer) println("文件末尾的 10 个字节: ${String.fromUtf8(readBuffer)}") file.close() // 读写模式:截断为空文件再写入新内容 file = File(filePath, ReadWrite) file.setLength(0) file.write("文件已被截断为空文件!".toArray()) file.seek(SeekPosition.Begin(0)) let allBytes: Array<Byte> = readToEnd(file) file.close() println("截断后新写入的内容: ${String.fromUtf8(allBytes)}") removeIfExists(filePath) return 0 }

运行结果:

文件创建成功: tempFile.txt 第 10 字节之后的 10 个字节: 123456789 文件末尾的 10 个字节: abcdefghi 截断后新写入的内容: 文件已被截断为空文件!

SeekPosition位于 std.io 包,支持Begin(offset)、Current(offset)、End(offset)三种定位方式(示例中SeekPosition.End(-10)表示从文件末尾向前 10 字节)。

Directory:目录的创建、遍历与拷贝

Directory 对应文件系统中的目录,提供创建、移动、复制、删除、查询属性以及遍历目录等能力。

说明:非法路径指以下情况之一:路径中包含非法字符(如空格、制表符、换行符等);路径中包含不合法的特殊字符、控制字符;路径中包含不存在的目录或文件;路径中包含无法访问的目录或文件(如权限不足或被锁定)。输入路径时应避免非法字符,确保路径合法性。

创建与临时目录

  • Directory.create(path: Path/String, recursive!: Bool = false):创建目录。recursive = true时逐级创建路径中不存在的中间目录;目录已存在、非递归创建时中间目录不存在、权限不足等都会抛 FSException。从源码看(stdlib/libs/std/fs/directory.cj),创建前会先去掉尾部分隔符并检查是否已存在。
  • Directory.createTemp(directoryPath: Path/String): Path:在指定目录下创建临时目录,返回其路径。底层会先对目录做canonicalize并调用原生CJ_FS_CreateTempDir。

判断与遍历

  • Directory.isEmpty(path): Bool:判断指定目录是否为空;路径不存在、路径不是目录或底层接口出错时抛 FSException。
  • Directory.readFrom(path): Array<FileInfo>:读取目录下所有子项目的信息,返回Array<FileInfo>。子项目在数组中的顺序取决于文件在系统中的排序。源码实现是内部调用walk收集所有条目(stdlib/libs/std/fs/directory.cj)。
  • Directory.walk(path, f: (FileInfo) -> Bool):遍历指定目录下的子项目(非递归,不包含子目录的子项目),对每个子项目执行回调函数。退出条件为遍历结束或回调函数返回false。遍历顺序取决于文件在系统中的排序。底层通过目录句柄逐项读取(CJ_FS_GetDirHandleAndFirstFile/CJ_FS_ReadDirHandle,见 stdlib/libs/std/fs/directory.cj),并在 finally 中关闭句柄。

walk回调式遍历的典型用法(按名称过滤并删除文件):

import std.fs.* main(): Unit { removeIfExists("./test_walk_directory", recursive: true) let path = Path("./test_walk_directory") Directory.create(path, recursive: true) File.create("./test_walk_directory/abc.txt") File.create("./test_walk_directory/bcd.txt") File.create("./test_walk_directory/cde.txt") File.create("./test_walk_directory/def.txt") // 把名字中带 b 的文件删除 Directory.walk(path) { fileinfo: FileInfo => if (fileinfo.name.contains("b")) { remove(fileinfo.path) } return true // 返回 false 会提前终止遍历 } println("查询是否存在 abc.txt 这个文件: ${exists("./test_walk_directory/abc.txt")}") removeIfExists("./test_walk_directory", recursive: true) }

运行结果:查询是否存在 abc.txt 这个文件: false。

Directory 组合操作示例

directory_samples.md 演示了目录创建、临时目录、重命名移动与拷贝的完整流程:

import std.fs.* main() { let testDirPath: Path = Path("./testDir") let subDirPath: Path = Path("./testDir/subDir") removeIfExists(testDirPath, recursive: true) // 递归创建目录(不存在的父目录会一并创建) Directory.create(subDirPath, recursive: true) println("递归创建目录成功: ${subDirPath}") // 创建临时目录 let tempDirPath: Path = Directory.createTemp(testDirPath) // 移动并重命名:subDir -> tempDir/subDir_new let newSubDirPath: Path = tempDirPath.join("subDir_new") rename(subDirPath, to: newSubDirPath) // 拷贝回原位置 copy(newSubDirPath, to: subDirPath, overwrite: false) removeIfExists(testDirPath, recursive: true) return 0 }

链接:HardLink 与 SymbolicLink

HardLink 硬链接

HardLink 提供硬链接相关接口,仅有一个静态方法:

public static func create(link: Path | String, to!: Path | String): Unit

创建一个新的硬链接到现有路径。如果新路径已存在,则不会覆盖(源码实现在 stdlib/libs/std/fs/hardlink.cj,底层调用CJ_FS_CreateHardLink)。参数路径为空或含空字符抛 IllegalArgumentException,创建失败抛 FSException。

SymbolicLink 符号链接

SymbolicLink 提供符号链接接口:

  • create(link, to!):创建符号链接到现有路径。
  • readFrom(path, recursive! = false): Path:获取符号链接的目标。recursive = true时跟踪指向最终目标的链接并返回目标的全路径;false时读取当前目标链接并返回。

说明:在 Windows 上,创建一个目标不存在的符号链接时,会创建一个文件符号链接;如果目标路径后来被创建为目录,则符号链接将不起作用。

符号链接的创建与读取示例(完整代码见 fs_package_classes.md):

import std.fs.* main(): Unit { removeIfExists("./test_symlink_source.txt", recursive: true) removeIfExists("./test_symlink_target.txt", recursive: true) var data: Array<Byte> = [83, 121, 109, 98, 111, 108, 105, 99, 76, 105, 110, 107] // "SymbolicLink" File.writeTo("./test_symlink_source.txt", data) SymbolicLink.create("./test_symlink_target.txt", to: "./test_symlink_source.txt") let targetPath = SymbolicLink.readFrom(Path("./test_symlink_target.txt")) println("Symbolic link target: ${targetPath}") removeIfExists("./test_symlink_source.txt", recursive: true) removeIfExists("./test_symlink_target.txt", recursive: true) }

运行结果:Symbolic link target: ./test_symlink_source.txt。

FileInfo:文件元数据与权限

FileInfo 对应文件系统中的文件元数据(文件名、大小、创建时间、修改时间、访问时间、权限、所有者等)。

说明:FileInfo的底层实现没有直接缓存文件属性,每次通过FileInfo的 API 都是现场获取最新属性。因此对于同一FileInfo实例,如果两次获取属性期间文件实体被其他用户或进程修改或替换,后一次获取的可能不是期望的属性。有特殊需求时应通过设置文件权限或加锁来保证。

FileInfo可通过FileInfo(path: Path | String)构造,或从File.info属性、Directory.readFrom/walk回调中获得。

属性

属性类型说明
pathPath当前文件路径
nameString文件名或目录名,等价于this.path.fileName
parentDirectoryOption<FileInfo>父级目录元数据,有父级返回Some(v),否则返回None
sizeInt64文件大小;对文件表示单个文件占用磁盘空间大小,对目录表示目录内所有文件占用磁盘空间大小
creationTime/lastAccessTime/lastModificationTimeDateTime创建时间 / 最后访问时间 / 最后修改时间

权限判断与设置

方法说明
canRead()判断当前用户是否有读取权限(文件为读,目录为浏览)。Windows 下固定返回 true,不生效
canWrite()判断是否有写入权限(文件为写,目录为删除/移动/创建内部文件)。Windows 下目录权限固定 true
canExecute()判断是否有执行权限(文件为执行,目录为进入)。Windows 下由文件扩展名决定,目录固定 true
isReadOnly()判断是否只读。Windows 下目录的删除修改权限固定返回 false
isDirectory()/isRegular()/isSymbolicLink()/isHidden()类型判断:目录 / 普通文件 / 软链接 / 隐藏文件
setReadable(readable)/setWritable(writable)/setExecutable(executable)设置文件所有者对应权限,返回操作是否成功(Bool)。Linux/macOS 下正常生效;Windows 下部分固定返回 false

说明:权限设置类方法在 Linux/macOS 下可正常使用,但如果调用期间FileInfo对应的文件实体被其他用户或进程修改,可能因竞争条件(Race Condition)导致修改不生效;以特权用户运行的进程可能绕过此权限设置。

FileInfo 相等性

operator func ==(other: FileInfo): Bool用于判断两个FileInfo是否对应同一文件(而非内容相同)。示例中两个写入相同数据的不同文件比较结果为false。

FileDescriptor 与 FSException

FileDescriptor 文件句柄

FileDescriptor 用于获取文件句柄信息,唯一的公开属性是fileHandle: IntNative。文件句柄是操作系统为跟踪文件分配的数据结构:在 Unix/Linux 中通常是非负整数(文件描述符编号),在 Windows 中通常是指向文件对象的指针。可从File.fileDescriptor获取(见 stdlib/libs/std/fs/file.cj,结构体内部以InvalidHandle初始化,文件未打开时为无效句柄)。

FSException 异常体系

FSException 是文件流异常类,继承自 IO 流异常类 IOException,提供两个构造函数:FSException()(无提示信息)与FSException(message: String)(带错误信息)。std.fs 中几乎所有文件系统操作失败都会抛出该异常,例如:路径不存在、权限不足、类型不一致、底层系统调用失败、文件已关闭或不可读写等。源码实现见 stdlib/libs/std/fs/fs_exception.cj。

实战组合:一个完整的文件管理任务

将前文能力组合起来,可以实现典型的"创建目录 → 写入文件 → 校验 → 清理"任务(参考 path_samples.md 中的"通过 Path 创建文件与目录"):

import std.fs.* main() { let currentDirPath: Path = Path("./") let dirPath: Path = currentDirPath.join("tempDir") let filePath: Path = dirPath.join("tempFile.txt") // 清理上次运行可能残留的目录 removeIfExists(dirPath, recursive: true) // 创建目录 tempDir Directory.create(dirPath) println("目录创建成功: ${dirPath}") // 在 tempDir 下创建文件 tempFile.txt File.create(filePath).close() println("文件创建成功: ${filePath}") // 清理本次运行创建的目录 removeIfExists(dirPath, recursive: true) return 0 }

运行结果:

目录创建成功: ./tempDir 文件创建成功: ./tempDir/tempFile.txt

结合canonicalize与Path判等的路径一致性校验模式(同一示例文档):

// 规范化处理冗余路径(多个 "."、".." 会被解析),结果与真实路径一致 let canonicalizedFilePath: Path = canonicalize(Path("././././a/./../a/b/../../a/b/c/.././../../a/b/c/d.cj")) if (canonicalizedFilePath == canonicalize(filePath)) { println("规范化路径与真实路径一致") }

源码结构速查

std.fs 包的实现位于 stdlib/libs/std/fs,可按需深入阅读:

  • common.cj:顶层函数copy/exists/rename/remove/removeIfExists的实现与参数校验。
  • path.cj:Path结构体、canonicalize、路径分隔符常量与规范化算法。
  • file.cj:File类、OpenMode枚举、FileDescriptor与读写/截断/定位实现。
  • directory.cj:Directory类的创建、遍历(walk/readFrom)与目录递归拷贝。
  • file_info.cj:FileInfo元数据与权限 API。
  • hardlink.cj、symboliclink.cj:硬链接与符号链接。
  • utils.cj:PathValidator等路径合法性校验工具。
  • native/file_system.h 与 native/file_system_unix.c、native/file_system_win.c:跨平台的原生系统调用封装(Unix 与 Windows 双实现)。

总结

std.fs 以Path为路径抽象核心,通过顶层函数与File、Directory、HardLink、SymbolicLink、FileInfo等类型提供了完整的文件系统编程能力:路径的拼接、规范化和真实化(join/normalize/canonicalize三者语义各不相同,需按需选用),文件四种打开模式下的流式读写与截断定位,目录的递归创建与回调式遍历,以及硬链接、符号链接与元数据权限管理。编写业务代码时务必牢记三点:所有文件操作仅对具备权限的路径生效(iOS 沙盒尤其受限);File对象使用后需及时close();rename(overwrite: true)与递归删除操作具有破坏性,应谨慎使用。

  • 编程语言
  • 语言运行时
  • 标准库
  • 垃圾回收
  • 并发编程

【免费下载链接】cangjie_runtime

仓颉编程语言运行时与标准库。

项目地址:https://gitcode.com/Cangjie/cangjie_runtime
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询