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 通过事件回调通知变化。两者都依赖操作系统或运行时的文件系统通知机制,实际可靠性和性能取决于平台、监听范围、事件数量以及缓冲区配置。跨平台场景下都需要处理事件重复、事件丢失和文件尚未写完等问题。

相关推荐
丘山一郎14 小时前
Spring 中的IOC控制反转 和DI依赖注入
java·后端·spring
MetaLite14 小时前
Spring-AOP自调用为什么失效-AopContext真能解决吗
java·后端·spring
luteres19 小时前
Spring学习笔记
java·后端·spring
让你三行代码QAQ1 天前
SpringAI-Advisors
spring·ai编程
杰哥哥不是个好叔叔1 天前
【Spring AI】Spring AI 2.0.1 新特性袭来
java·人工智能·spring
落魄实习生1 天前
Spring AI Alibaba入门-生态集成
java·人工智能·spring
山鬼龙王1 天前
从源码角度剖析:Spring如何用三级缓存解决循环依赖
spring
MetaLite1 天前
Spring-MVC微服务接口怎么分层-网关Controller与Service边界
spring·微服务·mvc
A_nanda1 天前
拆解ASP.NET Core 底层源码!从零手写
后端·asp.net
大模型码小白1 天前
Spring AI 框架中集成 MCP 的完整指南:从服务端到客户端的全流程实践
大数据·运维·数据库·人工智能·python·sql·spring