第 09 章:文件 I/O
第 09 章:文件 I/O
9.1 文件读取
读取文本文件
// 读取整个文件为字符串
const text = await Deno.readTextFile("./data.txt");
console.log(text);
// 同步读取
const textSync = Deno.readTextFileSync("./data.txt");
console.log(textSync);
⚠️ 权限要求:
--allow-read
读取二进制文件
// 读取为 Uint8Array
const bytes = await Deno.readFile("./image.png");
console.log("文件大小:", bytes.length, "字节");
// 读取并处理图片(简单示例)
const header = bytes.slice(0, 8);
console.log("文件头:", Array.from(header).map(b => b.toString(16)));
使用 Deno.open 读取
// 打开文件获取文件句柄
const file = await Deno.open("./data.txt", { read: true });
// 读取全部内容
const content = await new Response(file.readable).text();
console.log(content);
file.close();
逐行读取
// 方法 1:使用 TextLineStream
import { TextLineStream } from "jsr:@std/streams/text-line-stream";
const file = await Deno.open("./large-file.txt");
const lines = file.readable
.pipeThrough(new TextDecoderStream())
.pipeThrough(new TextLineStream());
for await (const line of lines) {
console.log("行:", line);
}
file.close();
// 方法 2:手动解析
const text2 = await Deno.readTextFile("./data.txt");
const lines2 = text2.split("\n");
for (const line of lines2) {
console.log(line.trim());
}
分块读取大文件
const file = await Deno.open("./large-file.bin", { read: true });
const reader = file.readable.getReader();
let totalBytes = 0;
while (true) {
const { done, value } = await reader.read();
if (done) break;
totalBytes += value.length;
// 处理每个 chunk
console.log(`已读取 ${totalBytes} 字节`);
}
file.close();
console.log(`总共读取 ${totalBytes} 字节`);
9.2 文件写入
写入文本文件
// 写入字符串
await Deno.writeTextFile("./output.txt", "Hello, Deno!\n");
// 追加写入
await Deno.writeTextFile("./log.txt", `[${new Date().toISOString()}] 新日志\n`, {
append: true,
});
⚠️ 权限要求:
--allow-write
写入二进制数据
// 写入二进制数据
const data = new Uint8Array([72, 101, 108, 108, 111]); // "Hello"
await Deno.writeFile("./binary.dat", data);
// 写入并创建目录
await Deno.mkdir("./output", { recursive: true });
await Deno.writeTextFile("./output/result.json", JSON.stringify({ success: true }));
使用 Deno.open 写入
// 打开文件用于写入
const file = await Deno.open("./output.txt", {
write: true,
create: true, // 文件不存在则创建
truncate: true, // 清空已有内容
});
// 使用 Writer 接口
const encoder = new TextEncoder();
await file.write(encoder.encode("第一行\n"));
await file.write(encoder.encode("第二行\n"));
file.close();
使用 WritableStream
const file = await Deno.open("./stream-output.txt", {
write: true,
create: true,
});
const writer = file.writable.getWriter();
const encoder = new TextEncoder();
await writer.write(encoder.encode("流式写入第一行\n"));
await writer.write(encoder.encode("流式写入第二行\n"));
await writer.close();
复制文件(使用流)
// 高效复制文件
async function copyFile(src: string, dest: string) {
const srcFile = await Deno.open(src, { read: true });
const destFile = await Deno.open(dest, { write: true, create: true });
await srcFile.readable.pipeTo(destFile.writable);
// pipeTo 自动关闭两端
}
await copyFile("./source.mp4", "./dest.mp4");
9.3 文件信息与属性
// 获取文件信息(stat)
const info = await Deno.stat("./data.txt");
console.log("是文件:", info.isFile);
console.log("是目录:", info.isDirectory);
console.log("是符号链接:", info.isSymlink);
console.log("文件大小:", info.size, "字节");
console.log("修改时间:", info.mtime);
console.log("创建时间:", info.birthtime);
console.log("访问时间:", info.atime);
// 获取文件大小(简写)
const size = (await Deno.stat("./data.txt")).size;
// 检查文件是否存在
async function fileExists(path: string): Promise<boolean> {
try {
await Deno.stat(path);
return true;
} catch (error) {
if (error instanceof Deno.errors.NotFound) {
return false;
}
throw error;
}
}
console.log(await fileExists("./data.txt")); // true 或 false
文件权限
// 获取文件权限
const info = await Deno.stat("./data.txt");
console.log("文件模式:", info.mode?.toString(8));
// 修改文件权限(Unix)
await Deno.chmod("./script.sh", 0o755);
// 修改文件所有者(Unix)
await Deno.chown("./data.txt", 1000, 1000); // uid, gid
9.4 目录操作
创建目录
// 创建单层目录
await Deno.mkdir("./new-dir");
// 递归创建多层目录
await Deno.mkdir("./parent/child/grandchild", { recursive: true });
读取目录
// 读取目录内容
const entries = [];
for await (const entry of Deno.readDir("./src")) {
entries.push({
name: entry.name,
isFile: entry.isFile,
isDirectory: entry.isDirectory,
isSymlink: entry.isSymlink,
});
}
console.log(entries);
删除目录
// 删除空目录
await Deno.remove("./empty-dir");
// 递归删除目录及内容
await Deno.remove("./dir-to-delete", { recursive: true });
重命名/移动
// 重命名文件
await Deno.rename("./old-name.txt", "./new-name.txt");
// 移动文件
await Deno.rename("./file.txt", "./archive/file.txt");
删除文件
// 删除文件
await Deno.remove("./file.txt");
// 删除前检查
try {
await Deno.remove("./file.txt");
console.log("删除成功");
} catch (error) {
if (error instanceof Deno.errors.NotFound) {
console.log("文件不存在");
} else {
throw error;
}
}
9.5 文件监听(Watch)
使用 Deno.watchFs
// 监听文件变化
const watcher = Deno.watchFs("./src");
for await (const event of watcher) {
console.log("事件类型:", event.kind); // "create" | "modify" | "remove" | "access" | "any" | "other"
console.log("影响文件:", event.paths);
}
// 注意:这是一个无限循环,需要 Ctrl+C 停止
监听特定文件类型
const watcher = Deno.watchFs("./src", { recursive: true });
for await (const event of watcher) {
// 只关注 .ts 文件的变化
const tsFiles = event.paths.filter(p => p.endsWith(".ts"));
if (tsFiles.length > 0) {
console.log(`[TS] ${event.kind}:`, tsFiles);
}
}
带防抖的文件监听
import { debounce } from "jsr:@std/async";
const watcher = Deno.watchFs("./src", { recursive: true });
const handleChanges = debounce((paths: string[]) => {
console.log("文件变化,重新构建...", paths);
// 执行构建逻辑
}, 300);
for await (const event of watcher) {
if (["create", "modify", "remove"].includes(event.kind)) {
handleChanges(event.paths);
}
}
9.6 临时文件
使用 Deno.makeTempFile
// 创建临时文件
const tempFile = await Deno.makeTempFile({ suffix: ".json" });
console.log("临时文件:", tempFile); // /tmp/tmpXXXXXX.json
// 写入数据
await Deno.writeTextFile(tempFile, JSON.stringify({ temp: true }));
// 使用完毕后删除
await Deno.remove(tempFile);
创建临时目录
const tempDir = await Deno.makeTempDir({ prefix: "myapp_" });
console.log("临时目录:", tempDir); // /tmp/myapp_XXXXXX
// 在临时目录中创建文件
await Deno.writeTextFile(`${tempDir}/data.json`, "{}");
// 用完删除
await Deno.remove(tempDir, { recursive: true });
使用 try-finally 确保清理
async function withTempFile<T>(
fn: (path: string) => Promise<T>,
options?: Deno.MakeTempOptions
): Promise<T> {
const tempFile = await Deno.makeTempFile(options);
try {
return await fn(tempFile);
} finally {
await Deno.remove(tempFile);
}
}
// 使用
const result = await withTempFile(async (path) => {
await Deno.writeTextFile(path, "临时数据");
return await Deno.readTextFile(path);
});
console.log(result); // "临时数据"
9.7 符号链接
// 创建符号链接
await Deno.symlink("./original.txt", "./link.txt");
// 读取符号链接目标
const target = await Deno.readLink("./link.txt");
console.log("链接目标:", target); // ./original.txt
// 获取真实路径
const realPath = await Deno.realPath("./link.txt");
console.log("真实路径:", realPath); // /absolute/path/to/original.txt
9.8 综合实战:日志文件管理器
import { ensureDir } from "jsr:@std/fs";
import { join } from "jsr:@std/path";
import { debounce } from "jsr:@std/async";
class LogManager {
#logDir: string;
#currentFile?: Deno.FsFile;
constructor(logDir: string = "./logs") {
this.#logDir = logDir;
}
async init() {
await ensureDir(this.#logDir);
const filename = `app-${new Date().toISOString().split("T")[0]}.log`;
const filepath = join(this.#logDir, filename);
this.#currentFile = await Deno.open(filepath, { write: true, create: true, append: true });
}
async log(level: string, message: string) {
const timestamp = new Date().toISOString();
const line = `[${timestamp}] [${level.toUpperCase()}] ${message}\n`;
const encoder = new TextEncoder();
await this.#currentFile!.write(encoder.encode(line));
}
async info(message: string) { await this.log("info", message); }
async warn(message: string) { await this.log("warn", message); }
async error(message: string) { await this.log("error", message); }
close() {
this.#currentFile?.close();
}
// 清理旧日志
async cleanOldLogs(days: number = 30) {
const cutoff = Date.now() - days * 24 * 60 * 60 * 1000;
for await (const entry of Deno.readDir(this.#logDir)) {
if (entry.isFile && entry.name.endsWith(".log")) {
const info = await Deno.stat(join(this.#logDir, entry.name));
if (info.mtime && info.mtime.getTime() < cutoff) {
await Deno.remove(join(this.#logDir, entry.name));
console.log(`清理旧日志:${entry.name}`);
}
}
}
}
}
// 使用示例
const logger = new LogManager("./app-logs");
await logger.init();
await logger.info("应用启动");
await logger.warn("内存使用率偏高");
await logger.error("数据库连接失败");
logger.close();
9.9 本章小结
| 操作 | API | 权限 |
|---|---|---|
| 读取文本 | Deno.readTextFile() | --allow-read |
| 读取二进制 | Deno.readFile() | --allow-read |
| 写入文本 | Deno.writeTextFile() | --allow-write |
| 写入二进制 | Deno.writeFile() | --allow-write |
| 文件信息 | Deno.stat() | --allow-read |
| 创建目录 | Deno.mkdir() | --allow-write |
| 读取目录 | Deno.readDir() | --allow-read |
| 删除 | Deno.remove() | --allow-write |
| 重命名 | Deno.rename() | --allow-read + --allow-write |
| 监听 | Deno.watchFs() | --allow-read |
| 临时文件 | Deno.makeTempFile() | --allow-read + --allow-write |
📖 扩展阅读
下一章:第 10 章:HTTP 服务器 → 使用 Deno 构建 Web 服务器。