ASP.NET Core迁移Spring对照系列:文件系统操作篇

版本基线

类别 版本
Java 17 (LTS)
C# 12 (.NET 8)

1、Java 文件系统操作对照

1.1 类型对比

维度 Java C#
文件路径 java.nio.file.Path System.IO.Path(静态类)
文件操作 java.nio.file.Files System.IO.File(静态类)
目录操作 java.nio.file.DirectoryStream System.IO.Directory(静态类)
文件监听 WatchService FileSystemWatcher
路径拼接 Paths.get("a", "b", "c.txt") Path.Combine("a", "b", "c.txt")
文件存在 Files.exists(path) File.Exists(path)
创建目录 Files.createDirectories(path) Directory.CreateDirectory(path)
复制文件 Files.copy(src, dst, StandardCopyOption.REPLACE_EXISTING) File.Copy(src, dst, overwrite: true)(.NET Core 3.0+ / .NET 5+)
移动文件 Files.move(src, dst, StandardCopyOption.REPLACE_EXISTING) File.Move(src, dst, overwrite: true)(.NET Core 3.0+ / .NET 5+)
删除文件 Files.delete(path) File.Delete(path)
读取字节 Files.readAllBytes(path) File.ReadAllBytes(path)
写入字节 Files.write(path, bytes) File.WriteAllBytes(path, bytes)
读取行 Files.readAllLines(path, StandardCharsets.UTF_8) File.ReadAllLines(path) / File.ReadLines(path)(流式)
写入行 Files.write(path, lines) File.WriteAllLines(path, lines)
遍历目录 Files.list(path) / Files.walk(path) Directory.EnumerateFiles(path)
文件属性 Files.getLastModifiedTime(path) File.GetLastWriteTime(path)
文件大小 Files.size(path) new FileInfo(path).Length
临时文件 Files.createTempFile("prefix", ".tmp") / Files.createTempDirectory(prefix) Path.GetTempFileName() / Path.Combine(Path.GetTempPath(), ...)

1.2 数据流图

#mermaid-svg-9uTVCydxwpMmsVoH{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-9uTVCydxwpMmsVoH .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-9uTVCydxwpMmsVoH .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-9uTVCydxwpMmsVoH .error-icon{fill:#552222;}#mermaid-svg-9uTVCydxwpMmsVoH .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-9uTVCydxwpMmsVoH .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-9uTVCydxwpMmsVoH .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-9uTVCydxwpMmsVoH .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-9uTVCydxwpMmsVoH .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-9uTVCydxwpMmsVoH .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-9uTVCydxwpMmsVoH .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-9uTVCydxwpMmsVoH .marker{fill:#333333;stroke:#333333;}#mermaid-svg-9uTVCydxwpMmsVoH .marker.cross{stroke:#333333;}#mermaid-svg-9uTVCydxwpMmsVoH svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-9uTVCydxwpMmsVoH p{margin:0;}#mermaid-svg-9uTVCydxwpMmsVoH .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-9uTVCydxwpMmsVoH .cluster-label text{fill:#333;}#mermaid-svg-9uTVCydxwpMmsVoH .cluster-label span{color:#333;}#mermaid-svg-9uTVCydxwpMmsVoH .cluster-label span p{background-color:transparent;}#mermaid-svg-9uTVCydxwpMmsVoH .label text,#mermaid-svg-9uTVCydxwpMmsVoH span{fill:#333;color:#333;}#mermaid-svg-9uTVCydxwpMmsVoH .node rect,#mermaid-svg-9uTVCydxwpMmsVoH .node circle,#mermaid-svg-9uTVCydxwpMmsVoH .node ellipse,#mermaid-svg-9uTVCydxwpMmsVoH .node polygon,#mermaid-svg-9uTVCydxwpMmsVoH .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-9uTVCydxwpMmsVoH .rough-node .label text,#mermaid-svg-9uTVCydxwpMmsVoH .node .label text,#mermaid-svg-9uTVCydxwpMmsVoH .image-shape .label,#mermaid-svg-9uTVCydxwpMmsVoH .icon-shape .label{text-anchor:middle;}#mermaid-svg-9uTVCydxwpMmsVoH .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-9uTVCydxwpMmsVoH .rough-node .label,#mermaid-svg-9uTVCydxwpMmsVoH .node .label,#mermaid-svg-9uTVCydxwpMmsVoH .image-shape .label,#mermaid-svg-9uTVCydxwpMmsVoH .icon-shape .label{text-align:center;}#mermaid-svg-9uTVCydxwpMmsVoH .node.clickable{cursor:pointer;}#mermaid-svg-9uTVCydxwpMmsVoH .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-9uTVCydxwpMmsVoH .arrowheadPath{fill:#333333;}#mermaid-svg-9uTVCydxwpMmsVoH .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-9uTVCydxwpMmsVoH .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-9uTVCydxwpMmsVoH .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9uTVCydxwpMmsVoH .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-9uTVCydxwpMmsVoH .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9uTVCydxwpMmsVoH .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-9uTVCydxwpMmsVoH .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-9uTVCydxwpMmsVoH .cluster text{fill:#333;}#mermaid-svg-9uTVCydxwpMmsVoH .cluster span{color:#333;}#mermaid-svg-9uTVCydxwpMmsVoH div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-9uTVCydxwpMmsVoH .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-9uTVCydxwpMmsVoH rect.text{fill:none;stroke-width:0;}#mermaid-svg-9uTVCydxwpMmsVoH .icon-shape,#mermaid-svg-9uTVCydxwpMmsVoH .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9uTVCydxwpMmsVoH .icon-shape p,#mermaid-svg-9uTVCydxwpMmsVoH .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-9uTVCydxwpMmsVoH .icon-shape .label rect,#mermaid-svg-9uTVCydxwpMmsVoH .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9uTVCydxwpMmsVoH .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-9uTVCydxwpMmsVoH .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-9uTVCydxwpMmsVoH :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-9uTVCydxwpMmsVoH .scene>*{fill:#fef3c7!important;stroke:#d97706!important;}#mermaid-svg-9uTVCydxwpMmsVoH .scene span{fill:#fef3c7!important;stroke:#d97706!important;} Path 接口

代表路径
Files 工具类

静态方法
DirectoryStream

目录迭代
Read 读
Write 写
Delete 删
List 列
Create 建
WatchService

事件队列
park 场景

1.3 代码对照

java 复制代码
// Java NIO.2 文件系统
package com.aspnet2java.demo.lang;

import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class FileService {
    private static final Path BASE_DIR = Paths.get("/data/parking");

    // 1. 创建目录
    public void createParkDir(Long parkId) throws IOException {
        Path parkDir = BASE_DIR.resolve(String.valueOf(parkId));
        Files.createDirectories(parkDir);
    }

    // 2. 写文件
    public void writeFile(Long parkId, String filename, String content) throws IOException {
        Path file = BASE_DIR.resolve(String.valueOf(parkId)).resolve(filename);
        // 关键:用 TRUNCATE_EXISTING 截断旧文件,否则二次写入会变成追加。
        // REPLACE_EXISTING / TRUNCATE_EXISTING 与 ATOMIC_MOVE 不可同用(后者只在 Files.move 里)。
        Files.writeString(file, content,
            StandardOpenOption.CREATE,
            StandardOpenOption.TRUNCATE_EXISTING);
    }

    // 3. 读文件
    public String readFile(Long parkId, String filename) throws IOException {
        Path file = BASE_DIR.resolve(String.valueOf(parkId)).resolve(filename);
        return Files.readString(file);
    }

    // 4. 遍历目录
    public List<Path> listFiles(Long parkId) throws IOException {
        Path parkDir = BASE_DIR.resolve(String.valueOf(parkId));
        try (Stream<Path> stream = Files.list(parkDir)) {
            return stream.filter(Files::isRegularFile).collect(Collectors.toList());
        }
    }

    // 5. 文件监听(注意:WatchService 不递归,子目录需单独 register;
    //    必须处理 OVERFLOW 事件,否则高频写入会丢事件)
    public void watchParkDir(Long parkId) throws IOException, InterruptedException {
        Path parkDir = BASE_DIR.resolve(String.valueOf(parkId));
        try (WatchService watchService = FileSystems.getDefault().newWatchService()) {
            parkDir.register(watchService,
                StandardWatchEventKinds.ENTRY_CREATE,
                StandardWatchEventKinds.ENTRY_MODIFY,
                StandardWatchEventKinds.ENTRY_DELETE);

            while (!Thread.currentThread().isInterrupted()) {
                WatchKey key = watchService.take();
                for (WatchEvent<?> event : key.pollEvents()) {
                    if (event.kind() == StandardWatchEventKinds.OVERFLOW) {
                        System.err.println("WatchService overflow, events lost");
                        continue;
                    }
                    System.out.println("Event: " + event.kind() + " - " + event.context());
                }
                if (!key.reset()) break;
            }
        }
    }

    // 6. 临时文件
    public Path createTempFile() throws IOException {
        return Files.createTempFile("iot-", ".tmp");
    }
}
csharp 复制代码
// C# System.IO 文件系统
using System;
using System.Collections.Generic;
using System.IO;

namespace AspNet2Java.Demo.Lang {
    public class FileService {
    private static readonly string BaseDir = @"D:\data\parking";

    // 1. 创建目录
    public void CreateParkDir(long parkId) {
        var parkDir = Path.Combine(BaseDir, parkId.ToString());
        Directory.CreateDirectory(parkDir);  // 自动递归创建
    }

    // 2. 写文件(默认 UTF-8 无 BOM;.NET Framework 上需 Encoding.UTF8)
    public void WriteFile(long parkId, string filename, string content) {
        var file = Path.Combine(BaseDir, parkId.ToString(), filename);
        File.WriteAllText(file, content, System.Text.Encoding.UTF8);  // 显式 UTF-8
    }

    // 3. 读文件
    public string ReadFile(long parkId, string filename) {
        var file = Path.Combine(BaseDir, parkId.ToString(), filename);
        return File.ReadAllText(file);
    }

    // 4. 遍历目录
    public IEnumerable<string> ListFiles(long parkId) {
        var parkDir = Path.Combine(BaseDir, parkId.ToString());
        return Directory.EnumerateFiles(parkDir);  // 流式
    }

    // 5. 文件监听(生产建议:using 释放 + InternalBufferSize 调大 + 处理 Renamed + Error)
    public void WatchParkDir(long parkId) {
        var parkDir = Path.Combine(BaseDir, parkId.ToString());
        using var watcher = new FileSystemWatcher(parkDir) {
            InternalBufferSize = 65536,  // 默认 8192,停车场 OTA 高频写入会丢
            NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite | NotifyFilters.Size
        };
        watcher.Created  += (s, e) => Console.WriteLine($"Created:  {e.Name}");
        watcher.Changed  += (s, e) => Console.WriteLine($"Changed:  {e.Name}");
        watcher.Deleted  += (s, e) => Console.WriteLine($"Deleted:  {e.Name}");
        watcher.Renamed  += (s, e) => Console.WriteLine($"Renamed:  {e.OldName} -> {e.Name}");
        watcher.Error    += (s, e) => Console.Error.WriteLine($"Watcher error: {e.GetException().Message}");
        watcher.EnableRaisingEvents = true;
    }

    // 6. 临时文件
    public string CreateTempFile() {
        return Path.GetTempFileName();
    }
}
}

重点

.NET System.IO 全部是静态类File / Directory / Path),调用更简洁。

Java java.nio.file.Files / Paths 是工具类(也静态)。

路径处理 :Java Path 是逻辑路径,按 FileSystem.getDefault().getSeparator() 处理,跨平台一致;.NET Path.Combine 在 .NET Core+ 上同样按平台分隔符拼接,Windows 上输出 \、Linux/macOS 输出 /

文件监听 :.NET FileSystemWatcher 与 Java WatchService 都依赖底层文件系统通知机制,实际差异更多来自平台与事件缓冲配置,而非"事件回调 vs 轮询"这种简单二分;.NET 8 上 FileSystemWatcher 用 inotify(Linux)/ ReadDirectoryChangesW(Windows)/ FSEvents(macOS),均有内部缓冲区(默认 8KB),高频写入会丢事件,生产建议显式设置 InternalBufferSize = 65536 并处理 Error 事件。

1.4 FAQ

Q1:Files 与 File 区别?

Java 旧版 java.io.File(Java 1.0)只表示路径,操作能力有限 ;新版本 java.nio.file.Files(Java 7+,NIO.2)提供完整静态方法。生产推荐 Files (File 仅作为遗留兼容)。.NET File / Directory 全部是静态类,对应 Files 工具类。
Q2:Path 与 Paths 区别?

Java Path接口 (代表路径),Paths工厂类Paths.get() 创建 Path 实例)。C# Path静态类(提供 Combine / GetFileName 等静态方法)。
Q3:WatchService vs FileSystemWatcher?

Java WatchService 通过事件队列通知文件变化,take() 会阻塞等待事件;C# FileSystemWatcher 通过事件回调通知变化。两者都依赖操作系统或运行时的文件系统通知机制,实际可靠性和性能取决于平台、监听范围、事件数量以及缓冲区配置。跨平台场景下都需要处理事件重复、事件丢失和文件尚未写完等问题。

相关推荐
xqqxqxxq1 小时前
吃透 Spring 高频注解(包含SpringMVC Spring Boot)
java·spring boot·spring
仙人球部落 揞殺11 小时前
细说ASP.NET的各种异步操作
后端·asp.net
攻城有术14 小时前
专项攻克——spring、springMVC、springBoot、springCloud的启动流程
spring boot·spring·spring cloud
markinmarkin16 小时前
Spring 中有哪些方式可以把Bean 注入到IOC 容器?
java·spring
爱吃土豆的马铃薯ㅤㅤㅤㅤㅤㅤㅤㅤㅤ18 小时前
Spring‑AI Document 对象 JSON 字段详解
人工智能·spring·json
麻雀聊技术19 小时前
一重启 AI 就失忆?用 Spring AI + PostgreSQL 3步搞定“长期记忆”持久化(附完整代码)
java·spring·openai
he___H19 小时前
Spring-Configur注解
java·spring
铃木之影21 小时前
Java 版本 RAG 示例(Spring AI + Milvus)
java·人工智能·spring
legendary_bruce1 天前
六、工具调用
java·spring·prompt