面向 Java 工程师的 Node.js 核心模块详解
Node.js 提供了一组内置核心模块,无需安装即可使用。这些模块类似 Java 的标准库(java.io、java.nio、java.lang 等),但 API 风格截然不同——以异步和回调为主。
导入核心模块时推荐使用 node: 前缀:
// 推荐写法:加 node: 前缀,明确表示这是内置模块
import { readFile } from 'node:fs/promises';
import path from 'node:path';
import { exec } from 'node:child_process';
// 也能工作,但不推荐(容易与 npm 包混淆)
import { readFile } from 'fs/promises';核心模块与 Java 标准库对照:
| Node.js 模块 | Java 等价物 | 用途 |
|---|---|---|
fs/promises |
java.nio.file.Files |
文件读写 |
path |
java.nio.file.Path / Paths |
路径处理 |
child_process |
ProcessBuilder / Runtime.exec() |
子进程 |
process |
System / ProcessHandle |
进程信息 |
os |
System.getProperty("os.*") |
操作系统信息 |
http |
HttpServer (JDK 18+) |
HTTP 服务 |
crypto |
java.security.* |
加密/哈希 |
stream |
java.io.InputStream/OutputStream |
流处理 |
fs 模块是 Node.js 中最常用的核心模块之一,提供文件系统操作。推荐使用 fs/promises 版本,它返回 Promise,可配合 async/await 使用。
| 操作 | Java NIO (java.nio.file) |
Node.js (fs/promises) |
|---|---|---|
| 读文件 | Files.readString(path) |
readFile(path, 'utf-8') |
| 写文件 | Files.writeString(path, content) |
writeFile(path, content) |
| 列目录 | Files.list(dir) |
readdir(dir) |
| 创建目录 | Files.createDirectories(dir) |
mkdir(dir, { recursive: true }) |
| 文件信息 | Files.getAttribute(path, attr) |
stat(path) |
| 删除文件 | Files.delete(path) |
unlink(path) |
| 判断存在 | Files.exists(path) |
access(path) (try/catch) |
import { readFile } from 'node:fs/promises';
// 读取文本文件(指定编码返回 string)
async function readTextFile(filePath: string): Promise<string> {
const content = await readFile(filePath, 'utf-8');
return content;
}
// 读取 JSON 文件
async function readJsonFile<T>(filePath: string): Promise<T> {
const content = await readFile(filePath, 'utf-8');
return JSON.parse(content) as T;
}
// 读取二进制文件(不指定编码返回 Buffer)
async function readBinaryFile(filePath: string): Promise<Buffer> {
const buffer = await readFile(filePath);
console.log(`文件大小: ${buffer.length} 字节`);
return buffer;
}
// 使用示例
const text = await readTextFile('./config.txt');
console.log(text);
interface PackageJson {
name: string;
version: string;
dependencies?: Record<string, string>;
}
const pkg = await readJsonFile<PackageJson>('./package.json');
console.log(`项目名: ${pkg.name}, 版本: ${pkg.version}`);Java 对比:
// Java NIO 读文件
String content = Files.readString(Path.of("config.txt"));
// Java 读 JSON(需要 Jackson/Gson)
ObjectMapper mapper = new ObjectMapper();
PackageJson pkg = mapper.readValue(new File("package.json"), PackageJson.class);import { writeFile, appendFile } from 'node:fs/promises';
// 写入文本文件(覆盖写入)
async function writeTextFile(filePath: string, content: string): Promise<void> {
await writeFile(filePath, content, 'utf-8');
console.log(`已写入: ${filePath}`);
}
// 写入 JSON 文件(格式化输出)
async function writeJsonFile(filePath: string, data: unknown): Promise<void> {
const json = JSON.stringify(data, null, 2);
await writeFile(filePath, json, 'utf-8');
}
// 追加写入
async function appendToFile(filePath: string, line: string): Promise<void> {
await appendFile(filePath, line + '\n', 'utf-8');
}
// 使用示例
await writeTextFile('./output.txt', 'Hello, Node.js!');
await writeJsonFile('./config.json', {
name: 'my-app',
port: 3000,
debug: true,
});
// 追加日志
await appendToFile('./app.log', `[${new Date().toISOString()}] 服务启动`);
await appendToFile('./app.log', `[${new Date().toISOString()}] 收到请求`);Java 对比:
// Java NIO 写文件
Files.writeString(Path.of("output.txt"), "Hello, Java!");
// Java 追加写入
Files.writeString(Path.of("app.log"), "日志行\n",
StandardOpenOption.CREATE, StandardOpenOption.APPEND);import { readdir, mkdir, stat, rm } from 'node:fs/promises';
import path from 'node:path';
// 列出目录内容
async function listDirectory(dirPath: string): Promise<void> {
// 基础用法:返回文件名数组
const names = await readdir(dirPath);
console.log('文件列表:', names);
// 输出: ['file1.txt', 'file2.js', 'subdir']
// 带文件类型信息
const entries = await readdir(dirPath, { withFileTypes: true });
for (const entry of entries) {
const type = entry.isDirectory() ? '目录' : '文件';
console.log(` ${type}: ${entry.name}`);
}
}
// 递归列出所有文件
async function listAllFiles(dirPath: string): Promise<string[]> {
const files: string[] = [];
const entries = await readdir(dirPath, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dirPath, entry.name);
if (entry.isDirectory()) {
// 递归进入子目录
const subFiles = await listAllFiles(fullPath);
files.push(...subFiles);
} else {
files.push(fullPath);
}
}
return files;
}
// 递归创建目录(类似 Java 的 Files.createDirectories)
async function ensureDir(dirPath: string): Promise<void> {
await mkdir(dirPath, { recursive: true });
// recursive: true 等价于 Java 的 Files.createDirectories()
// 如果目录已存在也不会报错
}
// 获取文件信息
async function getFileInfo(filePath: string): Promise<void> {
const stats = await stat(filePath);
console.log(`文件: ${filePath}`);
console.log(` 大小: ${stats.size} 字节`);
console.log(` 是否目录: ${stats.isDirectory()}`);
console.log(` 是否文件: ${stats.isFile()}`);
console.log(` 创建时间: ${stats.birthtime.toISOString()}`);
console.log(` 修改时间: ${stats.mtime.toISOString()}`);
}
// 删除目录(递归)
async function removeDir(dirPath: string): Promise<void> {
await rm(dirPath, { recursive: true, force: true });
// force: true 表示即使不存在也不报错
}
// 判断文件是否存在
async function fileExists(filePath: string): Promise<boolean> {
try {
await stat(filePath);
return true;
} catch {
return false;
}
}
// 使用示例
await ensureDir('./output/reports/2024');
await listDirectory('./src');
const allFiles = await listAllFiles('./src');
console.log('所有文件:', allFiles);
await getFileInfo('./package.json');
console.log('文件存在:', await fileExists('./package.json'));Java 对比:
// Java 列目录
try (Stream<Path> stream = Files.list(Path.of("src"))) {
stream.forEach(p -> System.out.println(p.getFileName()));
}
// Java 递归列所有文件
try (Stream<Path> stream = Files.walk(Path.of("src"))) {
stream.filter(Files::isRegularFile)
.forEach(System.out::println);
}
// Java 创建目录
Files.createDirectories(Path.of("output/reports/2024"));
// Java 获取文件信息
BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
System.out.println("大小: " + attrs.size());
System.out.println("修改时间: " + attrs.lastModifiedTime());对于大文件,一次性读入内存不现实,需要使用流(Stream)。这与 Java 的 InputStream / OutputStream 概念相同。
import { createReadStream, createWriteStream } from 'node:fs';
import { createInterface } from 'node:readline';
import { pipeline } from 'node:stream/promises';
import { createGzip, createGunzip } from 'node:zlib';
// 逐行读取大文件(类似 Java 的 BufferedReader.readLine())
async function readLargeFileByLine(filePath: string): Promise<void> {
const fileStream = createReadStream(filePath, 'utf-8');
const rl = createInterface({
input: fileStream,
crlfDelay: Infinity, // 处理 Windows 换行符
});
let lineNumber = 0;
for await (const line of rl) {
lineNumber++;
// 处理每一行
if (line.includes('ERROR')) {
console.log(`第 ${lineNumber} 行: ${line}`);
}
}
console.log(`总行数: ${lineNumber}`);
}
// 流式复制文件(适合大文件)
async function copyFile(src: string, dest: string): Promise<void> {
const readStream = createReadStream(src);
const writeStream = createWriteStream(dest);
await pipeline(readStream, writeStream);
console.log(`已复制: ${src} -> ${dest}`);
}
// 流式压缩文件(gzip)
async function compressFile(src: string, dest: string): Promise<void> {
const readStream = createReadStream(src);
const gzip = createGzip();
const writeStream = createWriteStream(dest);
await pipeline(readStream, gzip, writeStream);
console.log(`已压缩: ${src} -> ${dest}`);
}
// 流式解压文件
async function decompressFile(src: string, dest: string): Promise<void> {
const readStream = createReadStream(src);
const gunzip = createGunzip();
const writeStream = createWriteStream(dest);
await pipeline(readStream, gunzip, writeStream);
console.log(`已解压: ${src} -> ${dest}`);
}
// 使用示例
await readLargeFileByLine('./server.log');
await copyFile('./data.csv', './data-backup.csv');
await compressFile('./data.csv', './data.csv.gz');Java 对比:
// Java 逐行读取(BufferedReader)
try (BufferedReader reader = Files.newBufferedReader(Path.of("server.log"))) {
String line;
int lineNumber = 0;
while ((line = reader.readLine()) != null) {
lineNumber++;
if (line.contains("ERROR")) {
System.out.printf("第 %d 行: %s%n", lineNumber, line);
}
}
}
// Java 流式复制
try (InputStream in = Files.newInputStream(src);
OutputStream out = Files.newOutputStream(dest)) {
in.transferTo(out);
}
// Java gzip 压缩
try (InputStream in = Files.newInputStream(src);
GZIPOutputStream gzOut = new GZIPOutputStream(Files.newOutputStream(dest))) {
in.transferTo(gzOut);
}path 模块提供跨平台的文件路径操作,类似 Java 的 java.nio.file.Path 和 Paths。
| 操作 | Java | Node.js |
|---|---|---|
| 拼接路径 | path1.resolve(path2) |
path.join(a, b) |
| 绝对路径 | path.toAbsolutePath() |
path.resolve(p) |
| 父目录 | path.getParent() |
path.dirname(p) |
| 文件名 | path.getFileName() |
path.basename(p) |
| 扩展名 | 手动处理 | path.extname(p) |
| 分隔符 | File.separator |
path.sep |
import path from 'node:path';
// ========== path.join — 拼接路径 ==========
// 智能拼接,自动处理分隔符
const filePath = path.join('/home', 'user', 'documents', 'file.txt');
console.log(filePath);
// Linux/macOS: /home/user/documents/file.txt
// Windows: \home\user\documents\file.txt
// 自动处理多余的分隔符和 ..
const cleaned = path.join('/home/user/', '../admin/', './config.json');
console.log(cleaned); // /home/admin/config.json
// ========== path.resolve — 解析为绝对路径 ==========
// 从右到左处理,遇到绝对路径就停止
const abs1 = path.resolve('src', 'index.ts');
console.log(abs1); // /当前工作目录/src/index.ts
const abs2 = path.resolve('/home/user', 'src', 'index.ts');
console.log(abs2); // /home/user/src/index.ts
// ========== path.dirname — 获取父目录 ==========
const dir = path.dirname('/home/user/project/src/index.ts');
console.log(dir); // /home/user/project/src
// ========== path.basename — 获取文件名 ==========
const filename = path.basename('/home/user/project/src/index.ts');
console.log(filename); // index.ts
// 去掉扩展名
const nameOnly = path.basename('/home/user/project/src/index.ts', '.ts');
console.log(nameOnly); // index
// ========== path.extname — 获取扩展名 ==========
console.log(path.extname('index.ts')); // .ts
console.log(path.extname('README.md')); // .md
console.log(path.extname('Makefile')); // '' (空字符串)
console.log(path.extname('.gitignore')); // '' (空字符串)
console.log(path.extname('archive.tar.gz')); // .gz
// ========== path.parse — 完整解析路径 ==========
const parsed = path.parse('/home/user/project/src/index.ts');
console.log(parsed);
// {
// root: '/',
// dir: '/home/user/project/src',
// base: 'index.ts',
// ext: '.ts',
// name: 'index'
// }
// ========== path.format — 从对象构造路径 ==========
const formatted = path.format({
dir: '/home/user/project/src',
name: 'index',
ext: '.ts',
});
console.log(formatted); // /home/user/project/src/index.ts
// ========== path.relative — 计算相对路径 ==========
const rel = path.relative('/home/user/project', '/home/user/project/src/utils/file.ts');
console.log(rel); // src/utils/file.ts
const rel2 = path.relative('/home/user/project/src', '/home/user/project/dist');
console.log(rel2); // ../dist
// ========== path.isAbsolute — 是否为绝对路径 ==========
console.log(path.isAbsolute('/home/user')); // true
console.log(path.isAbsolute('./src')); // false
console.log(path.isAbsolute('src/index.ts')); // falseimport path from 'node:path';
import { fileURLToPath } from 'node:url';
// ========== 获取当前文件目录(ESM 中没有 __dirname) ==========
// CommonJS 中有 __dirname 和 __filename,ESM 中需要手动计算
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
console.log('当前文件:', __filename);
console.log('当前目录:', __dirname);
// 基于当前文件定位其他文件
const configPath = path.join(__dirname, '..', 'config', 'default.json');
const templatesDir = path.resolve(__dirname, '../templates');
// ========== 路径分隔符 ==========
console.log(path.sep); // '/' (macOS/Linux) 或 '\\' (Windows)
console.log(path.delimiter); // ':' (macOS/Linux) 或 ';' (Windows)
// ========== 规范化路径 ==========
const messy = '/home/user//project/./src/../src/index.ts';
const clean = path.normalize(messy);
console.log(clean); // /home/user/project/src/index.tsJava 对比:
// Java 获取当前文件目录
Path currentDir = Path.of("").toAbsolutePath();
// Java 路径拼接
Path configPath = currentDir.resolve("config").resolve("default.json");
// Java 相对路径
Path rel = Path.of("/home/user/project").relativize(Path.of("/home/user/project/src"));
// 结果: src
// Java 规范化
Path normalized = Path.of("/home/user//project/./src/../src/index.ts").normalize();child_process 模块用于创建子进程、执行外部命令。类似 Java 的 ProcessBuilder 和 Runtime.exec()。
| 操作 | Java | Node.js |
|---|---|---|
| 执行命令(等待完成) | ProcessBuilder.start() + waitFor() |
execSync() / execFile() |
| 执行命令(异步) | ProcessBuilder.start() |
exec() / spawn() |
| 获取输出 | process.getInputStream() |
回调 / Promise |
| 设置工作目录 | pb.directory(dir) |
{ cwd: dir } |
| 设置环境变量 | pb.environment().put(k, v) |
{ env: { ...process.env, k: v } } |
exec 在 shell 中执行命令,缓冲整个输出后返回。适合输出较少的短命令。
import { exec } from 'node:child_process';
import { promisify } from 'node:util';
// 将 exec 转为 Promise 版本
const execAsync = promisify(exec);
// 基本用法:执行命令并获取输出
async function runCommand(command: string): Promise<string> {
const { stdout, stderr } = await execAsync(command);
if (stderr) {
console.warn('标准错误:', stderr);
}
return stdout.trim();
}
// 使用示例
const nodeVersion = await runCommand('node --version');
console.log('Node 版本:', nodeVersion); // v22.x.x
const files = await runCommand('ls -la');
console.log('文件列表:\n', files);
// 获取 Git 信息
const gitBranch = await runCommand('git branch --show-current');
console.log('当前分支:', gitBranch);
const gitLog = await runCommand('git log --oneline -5');
console.log('最近提交:\n', gitLog);
// 指定工作目录和环境变量
const { stdout } = await execAsync('ls', {
cwd: '/tmp', // 工作目录
env: { ...process.env, NODE_ENV: 'production' }, // 环境变量
timeout: 5000, // 超时时间(毫秒)
maxBuffer: 1024 * 1024, // 最大输出缓冲(默认 1MB)
});import { execSync } from 'node:child_process';
// 同步执行命令(阻塞当前线程)
// 适合在脚本或 CLI 工具中使用
function runSync(command: string): string {
try {
const output = execSync(command, {
encoding: 'utf-8', // 返回字符串而非 Buffer
timeout: 10000, // 10 秒超时
});
return output.trim();
} catch (error) {
// 命令执行失败时 execSync 会抛出异常
const err = error as { stderr?: string; status?: number };
console.error(`命令失败 (退出码 ${err.status}):`, err.stderr);
throw error;
}
}
// 使用示例
const whoami = runSync('whoami');
console.log('当前用户:', whoami);
const diskUsage = runSync('df -h .');
console.log('磁盘使用:\n', diskUsage);spawn 不缓冲输出,而是通过流(Stream)实时传递。适合长时间运行或输出量大的命令。
import { spawn } from 'node:child_process';
// 基本用法:流式获取输出
function spawnCommand(command: string, args: string[]): Promise<string> {
return new Promise((resolve, reject) => {
const child = spawn(command, args);
let stdout = '';
let stderr = '';
// 实时接收标准输出
child.stdout.on('data', (data: Buffer) => {
stdout += data.toString();
// 可以在这里实时处理输出
});
// 实时接收标准错误
child.stderr.on('data', (data: Buffer) => {
stderr += data.toString();
});
// 进程结束
child.on('close', (code) => {
if (code === 0) {
resolve(stdout.trim());
} else {
reject(new Error(`进程退出码 ${code}: ${stderr}`));
}
});
// 进程启动失败
child.on('error', (err) => {
reject(new Error(`启动失败: ${err.message}`));
});
});
}
// 使用示例:实时输出
function runWithLiveOutput(command: string, args: string[]): Promise<number> {
return new Promise((resolve, reject) => {
const child = spawn(command, args, {
stdio: 'inherit', // 直接继承父进程的 stdin/stdout/stderr
// 命令输出会直接打印到终端
});
child.on('close', (code) => resolve(code ?? 1));
child.on('error', reject);
});
}
// 使用示例
const result = await spawnCommand('find', ['.', '-name', '*.ts', '-maxdepth', '3']);
console.log('找到的 TS 文件:\n', result);
// 实时输出(例如运行测试或构建)
const exitCode = await runWithLiveOutput('npm', ['test']);
console.log('测试退出码:', exitCode);Java 对比:
// Java ProcessBuilder
ProcessBuilder pb = new ProcessBuilder("git", "log", "--oneline", "-5");
pb.directory(new File("."));
pb.redirectErrorStream(true);
Process process = pb.start();
String output = new String(process.getInputStream().readAllBytes());
int exitCode = process.waitFor();
System.out.println("输出: " + output);
System.out.println("退出码: " + exitCode);import { exec } from 'node:child_process';
import { promisify } from 'node:util';
const execAsync = promisify(exec);
// 执行结果接口
interface CommandResult {
stdout: string;
stderr: string;
exitCode: number;
}
// 执行选项接口
interface CommandOptions {
cwd?: string;
timeout?: number;
env?: Record<string, string>;
}
// 通用命令执行函数
async function executeCommand(
command: string,
options: CommandOptions = {}
): Promise<CommandResult> {
try {
const { stdout, stderr } = await execAsync(command, {
cwd: options.cwd,
timeout: options.timeout ?? 30000,
env: options.env ? { ...process.env, ...options.env } : undefined,
maxBuffer: 10 * 1024 * 1024, // 10MB
});
return { stdout: stdout.trim(), stderr: stderr.trim(), exitCode: 0 };
} catch (error) {
const err = error as { stdout?: string; stderr?: string; code?: number };
return {
stdout: (err.stdout ?? '').trim(),
stderr: (err.stderr ?? '').trim(),
exitCode: err.code ?? 1,
};
}
}
// 使用示例
const gitResult = await executeCommand('git status --porcelain');
if (gitResult.exitCode === 0) {
const changedFiles = gitResult.stdout.split('\n').filter(Boolean);
console.log(`有 ${changedFiles.length} 个文件变更`);
} else {
console.error('不在 Git 仓库中');
}
// 在指定目录执行
const npmResult = await executeCommand('npm ls --depth=0', {
cwd: '/path/to/project',
timeout: 10000,
});
console.log('已安装的依赖:\n', npmResult.stdout);process 是一个全局对象,无需导入,提供当前 Node.js 进程的信息和控制。类似 Java 的 System 类。
| 操作 | Java | Node.js |
|---|---|---|
| 环境变量 | System.getenv("KEY") |
process.env.KEY |
| 命令行参数 | args (main 参数) |
process.argv |
| 当前目录 | System.getProperty("user.dir") |
process.cwd() |
| 退出程序 | System.exit(code) |
process.exit(code) |
| 标准输出 | System.out.println() |
process.stdout.write() |
| 标准输入 | System.in |
process.stdin |
// 读取环境变量
const nodeEnv = process.env.NODE_ENV ?? 'development';
const port = parseInt(process.env.PORT ?? '3000', 10);
const dbUrl = process.env.DATABASE_URL;
console.log(`环境: ${nodeEnv}`);
console.log(`端口: ${port}`);
if (!dbUrl) {
console.error('错误: 未设置 DATABASE_URL 环境变量');
process.exit(1);
}
// 类型安全的环境变量读取
interface EnvConfig {
NODE_ENV: 'development' | 'production' | 'test';
PORT: number;
DATABASE_URL: string;
LOG_LEVEL: 'debug' | 'info' | 'warn' | 'error';
}
function getEnvConfig(): EnvConfig {
const required = ['DATABASE_URL'];
for (const key of required) {
if (!process.env[key]) {
throw new Error(`缺少必需的环境变量: ${key}`);
}
}
return {
NODE_ENV: (process.env.NODE_ENV as EnvConfig['NODE_ENV']) ?? 'development',
PORT: parseInt(process.env.PORT ?? '3000', 10),
DATABASE_URL: process.env.DATABASE_URL!,
LOG_LEVEL: (process.env.LOG_LEVEL as EnvConfig['LOG_LEVEL']) ?? 'info',
};
}
// 列出所有环境变量
for (const [key, value] of Object.entries(process.env)) {
if (key.startsWith('NODE_')) {
console.log(`${key} = ${value}`);
}
}Java 对比:
// Java 读取环境变量
String nodeEnv = System.getenv().getOrDefault("NODE_ENV", "development");
int port = Integer.parseInt(System.getenv().getOrDefault("PORT", "3000"));
String dbUrl = System.getenv("DATABASE_URL");
if (dbUrl == null) {
System.err.println("错误: 未设置 DATABASE_URL");
System.exit(1);
}// process.argv 是一个字符串数组
// argv[0] = node 可执行文件路径
// argv[1] = 脚本文件路径
// argv[2...] = 用户传入的参数
// 运行: node script.ts --name=hello --verbose -p 3000
console.log(process.argv);
// [
// '/usr/local/bin/node',
// '/path/to/script.ts',
// '--name=hello',
// '--verbose',
// '-p',
// '3000'
// ]
// 获取用户参数(跳过前两个)
const userArgs = process.argv.slice(2);
console.log('用户参数:', userArgs);
// ['--name=hello', '--verbose', '-p', '3000']
// 简单的参数解析
function parseArgs(args: string[]): Record<string, string | boolean> {
const result: Record<string, string | boolean> = {};
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (arg.startsWith('--')) {
// --key=value 或 --flag
const [key, value] = arg.slice(2).split('=');
result[key] = value ?? true;
} else if (arg.startsWith('-')) {
// -k value
const key = arg.slice(1);
const nextArg = args[i + 1];
if (nextArg && !nextArg.startsWith('-')) {
result[key] = nextArg;
i++; // 跳过下一个参数
} else {
result[key] = true;
}
}
}
return result;
}
const parsed = parseArgs(userArgs);
console.log(parsed);
// { name: 'hello', verbose: true, p: '3000' }Java 对比:
// Java main 方法的 args
public static void main(String[] args) {
// args 直接是用户参数,不包含 java/类名
// 运行: java App --name=hello --verbose
// args = ["--name=hello", "--verbose"]
}// 当前工作目录
const currentDir = process.cwd();
console.log('工作目录:', currentDir);
// 等价于 Java 的 System.getProperty("user.dir")
// 改变工作目录(很少用)
// process.chdir('/tmp');
// 进程 ID
console.log('PID:', process.pid);
// Node.js 版本
console.log('Node 版本:', process.version);
// 平台信息
console.log('平台:', process.platform); // 'darwin', 'linux', 'win32'
console.log('架构:', process.arch); // 'x64', 'arm64'
// 内存使用
const mem = process.memoryUsage();
console.log('内存使用:');
console.log(` 堆已用: ${Math.round(mem.heapUsed / 1024 / 1024)} MB`);
console.log(` 堆总量: ${Math.round(mem.heapTotal / 1024 / 1024)} MB`);
console.log(` RSS: ${Math.round(mem.rss / 1024 / 1024)} MB`);
// 正常退出
process.exit(0);
// 异常退出
process.exit(1);
// 监听退出事件(做清理工作)
process.on('exit', (code) => {
console.log(`进程即将退出,退出码: ${code}`);
// 注意:此处只能执行同步操作
});
// 监听未捕获的异常
process.on('uncaughtException', (err) => {
console.error('未捕获的异常:', err);
process.exit(1);
});
// 监听未处理的 Promise 拒绝
process.on('unhandledRejection', (reason) => {
console.error('未处理的 Promise 拒绝:', reason);
process.exit(1);
});
// 监听中断信号(Ctrl+C)
process.on('SIGINT', () => {
console.log('\n收到 SIGINT,正在清理...');
// 执行清理工作
process.exit(0);
});结合以上所有核心模块,实现一个实用的文件统计工具:
import { readdir, stat, readFile } from 'node:fs/promises';
import path from 'node:path';
// 文件统计结果
interface FileStats {
extension: string;
count: number;
totalSize: number;
totalLines: number;
}
// 扫描目录并统计文件信息
async function scanDirectory(
dirPath: string,
excludeDirs: string[] = ['node_modules', '.git', 'dist']
): Promise<Map<string, FileStats>> {
const stats = new Map<string, FileStats>();
async function walk(currentPath: string): Promise<void> {
const entries = await readdir(currentPath, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(currentPath, entry.name);
if (entry.isDirectory()) {
// 跳过排除的目录
if (excludeDirs.includes(entry.name)) continue;
await walk(fullPath);
} else if (entry.isFile()) {
const ext = path.extname(entry.name) || '(无扩展名)';
const fileStat = await stat(fullPath);
// 尝试统计行数(只对文本文件)
let lines = 0;
try {
const content = await readFile(fullPath, 'utf-8');
lines = content.split('\n').length;
} catch {
// 二进制文件读取可能失败,忽略
}
const existing = stats.get(ext) ?? {
extension: ext,
count: 0,
totalSize: 0,
totalLines: 0,
};
existing.count++;
existing.totalSize += fileStat.size;
existing.totalLines += lines;
stats.set(ext, existing);
}
}
}
await walk(dirPath);
return stats;
}
// 格式化输出
function formatReport(stats: Map<string, FileStats>): string {
const sorted = [...stats.values()].sort((a, b) => b.count - a.count);
const lines: string[] = [];
lines.push('='.repeat(60));
lines.push('文件统计报告');
lines.push('='.repeat(60));
lines.push(
'扩展名'.padEnd(12) +
'文件数'.padStart(8) +
'总行数'.padStart(10) +
'总大小'.padStart(12)
);
lines.push('-'.repeat(60));
let totalFiles = 0;
let totalLines = 0;
let totalSize = 0;
for (const s of sorted) {
totalFiles += s.count;
totalLines += s.totalLines;
totalSize += s.totalSize;
const sizeStr = formatSize(s.totalSize);
lines.push(
s.extension.padEnd(12) +
String(s.count).padStart(8) +
String(s.totalLines).padStart(10) +
sizeStr.padStart(12)
);
}
lines.push('-'.repeat(60));
lines.push(
'合计'.padEnd(12) +
String(totalFiles).padStart(8) +
String(totalLines).padStart(10) +
formatSize(totalSize).padStart(12)
);
lines.push('='.repeat(60));
return lines.join('\n');
}
// 格式化文件大小
function formatSize(bytes: number): string {
if (bytes < 1024) return `${bytes} B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
}
// 主函数
async function main(): Promise<void> {
const targetDir = process.argv[2] ?? process.cwd();
console.log(`扫描目录: ${path.resolve(targetDir)}\n`);
const stats = await scanDirectory(targetDir);
const report = formatReport(stats);
console.log(report);
}
main().catch(console.error);运行效果:
扫描目录: /home/user/my-project
============================================================
文件统计报告
============================================================
扩展名 文件数 总行数 总大小
------------------------------------------------------------
.ts 24 1856 45.2 KB
.json 5 120 3.8 KB
.md 3 240 8.1 KB
.css 2 85 2.1 KB
------------------------------------------------------------
合计 34 2301 59.2 KB
============================================================
| 概念 | Java | Node.js |
|---|---|---|
| 读文件 | Files.readString() |
readFile() |
| 写文件 | Files.writeString() |
writeFile() |
| 列目录 | Files.list() / Files.walk() |
readdir() |
| 流式读取 | BufferedReader |
createReadStream + readline |
| 路径拼接 | Path.resolve() |
path.join() / path.resolve() |
| 获取文件名 | path.getFileName() |
path.basename() |
| 执行命令 | ProcessBuilder |
exec() / spawn() |
| 环境变量 | System.getenv() |
process.env |
| 命令行参数 | main(String[] args) |
process.argv.slice(2) |
| 退出进程 | System.exit() |
process.exit() |
- 编写一个脚本,递归扫描指定目录,找出所有超过 100KB 的文件并列出
- 编写一个脚本,调用
git log获取最近 10 次提交的提交者和提交信息 - 编写一个简单的文件复制工具,从命令行参数读取源文件和目标文件路径
- 使用流式读取,逐行处理一个大文件,统计每个单词出现的次数