尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Unity文件读写全解析:跨平台路径、编码与异步操作实战

Unity文件读写全解析:跨平台路径、编码与异步操作实战 1. 项目概述为什么Unity开发者必须掌握文件读写在Unity项目开发中无论是保存玩家的游戏存档、记录调试日志、加载外部配置表还是实现一个简单的关卡编辑器都绕不开一个基础但至关重要的操作读写文本文件。很多新手开发者可能会觉得Unity提供了PlayerPrefs、ScriptableObject等更“高级”的数据存储方式读写txt是不是有点过时了恰恰相反txt文件以其通用性、轻量级和跨平台兼容性在特定场景下有着不可替代的优势。比如你需要导出一份运行时数据报告给策划分析或者需要读取一个由外部工具生成的配置文件txt往往是首选格式。这个项目标题“C#读取和写入txt文档在unity中示例”直指Unity开发中一项高频且核心的底层技能。它不仅仅是调用几个API那么简单背后涉及Unity特殊的运行时环境、跨平台路径处理、异步操作与性能、以及数据安全等一系列工程化考量。很多教程只告诉你用File.ReadAllText却没告诉你它在Android或WebGL平台会直接报错只告诉你写入成功却没考虑过文件被占用或写入中途崩溃导致数据损坏的问题。接下来我将结合十多年的踩坑经验为你拆解在Unity中安全、高效、跨平台地操作txt文件的完整方案让你不仅会“写”代码更懂得“为什么”这么写。2. 核心思路与方案选型在Unity的沙盒里操作文件在标准C#控制台或WPF程序中你可以用System.IO命名空间下的类在任意路径读写文件。但Unity是一个跨平台的游戏引擎它在不同平台如Windows、macOS、Android、iOS上有严格的沙盒Sandbox限制。应用程序不能随意访问操作系统的任意目录尤其是移动端。因此我们的核心思路必须围绕“在Unity允许的范围内找到稳定可靠的路径并使用兼容的API进行操作”来展开。2.1 关键路径解析你的文件该放在哪里这是第一个也是最重要的决策点。选错路径轻则功能失效重则应用审核被拒如iOS。1. Application.dataPath这是Unity项目Assets文件夹的绝对路径。在编辑器下它指向你的项目磁盘位置在打包后它指向应用包内的数据目录。用途主要用于编辑器开发阶段读取放置在Assets/Resources或Assets/StreamingAssets下的只读文件。切记在移动平台Android/iOS的发布包中此路径是只读的你无法向这里写入数据。示例Application.dataPath “/StreamingAssets/config.txt”2. Application.streamingAssetsPath专门用于存放不需要运行时修改的“流式资源”。打包时该文件夹下的内容会原封不动地拷贝到发布包中一个特定位置。特点所有平台下只读。适合存放初始配置、默认关卡数据、JSON/XML模板等。平台差异这是坑最多的地方。在Android平台文件实际存在于APK压缩包内不能直接用System.IO.File读取必须使用UnityWebRequest或WWW旧版类。而在iOS和PC平台可以直接用System.IO读取。3. Application.persistentDataPath这是写入用户数据的黄金路径。它指向一个操作系统分配的、应用专属的、可持久化的数据目录。用户卸载应用这个目录会被清除。特点所有平台下可读可写。用于保存游戏存档、用户设置、下载的资源、运行时日志等。示例Application.persistentDataPath “/save/slot1.txt”4. Application.temporaryCachePath临时缓存路径。系统可能在存储空间不足或应用关闭时清理此目录。适合存放短期内使用的临时文件如下载的图片缓存。核心经验对于需要动态写入的txt文件如存档、日志永远优先使用Application.persistentDataPath。对于需要打包进去的只读配置文件放入StreamingAssets并注意Android平台的读取方式。2.2 API选型同步 vs 异步 vs Unity特有确定了路径接下来选择用什么工具API来读写。1. 标准System.IO同步这是最基础、最直接的方式使用System.IO命名空间下的File静态类。优点代码简洁直观功能强大读写、追加、删除等。缺点同步阻塞。如果文件很大或磁盘慢会导致主线程卡顿游戏帧率下降甚至卡死。在移动设备上直接读取StreamingAssetsPath下的文件可能会因平台差异失败。适用场景编辑器工具开发、处理小文件几KB、在已知安全的路径如PersistentDataPath下进行简单操作。2. 标准System.IO异步使用FileStream、StreamReader/StreamWriter配合async/await关键字。优点非阻塞。文件操作在后台线程进行不会卡住主线程游戏体验流畅。缺点代码稍复杂需要处理异步回调。在Unity中需注意异步回调默认不在主线程如需更新UI如显示加载进度需要用到UnityEngine.Dispatcher或通过主线程队列执行。适用场景读写较大的文件如日志文件、导出数据、需要良好用户体验的任何文件操作。3. UnityWebRequest (针对特定路径)Unity提供的网络请求类但它也是在Android平台读取StreamingAssets文件夹内文件的唯一可靠方式。优点完美解决Android平台读取打包资源的问题。支持异步。缺点只能用于读取不能写入。只能用于特定URI格式的路径file://开头。适用场景跨平台尤其是Android读取StreamingAssets中的txt配置文件。方案决策流程图当你需要操作txt文件时可以按此流程选择文件性质是要写入的动态数据还是只读的打包资源写入 - 路径选Application.persistentDataPathAPI选System.IO同步/异步。只读 - 路径选Application.streamingAssetsPath。平台考量如果是只读目标平台是否包含Android是 - 使用UnityWebRequest读取。否 - 使用System.IO.File读取。性能与体验文件大小和操作频率如何文件大或操作频繁 - 优先使用异步API (StreamReader/StreamWriterwith async)。文件小或一次性操作 - 使用同步API (File.ReadAllText/WriteAllText) 更简单。3. 核心细节解析与实操要点理解了思路和方案我们深入到代码层面看看每个操作背后的细节和容易踩的坑。3.1 路径拼接与平台兼容性处理永远不要手动拼接路径字符串不同操作系统Windows, macOS, Linux的路径分隔符不同\vs/。使用Path.Combine()方法可以确保生成正确的路径。using System.IO; // 错误示范在Mac上会出错 string badPath Application.persistentDataPath “\\Save\\game.txt”; // 正确示范 string saveDirectory Path.Combine(Application.persistentDataPath, “Save”); string filePath Path.Combine(saveDirectory, “game.txt”);要点在操作文件前先检查目录是否存在不存在则创建。这是一个好习惯能避免DirectoryNotFoundException。if (!Directory.Exists(saveDirectory)) { Directory.CreateDirectory(saveDirectory); // 创建多级目录 }3.2 编码问题乱码的根源读取或写入txt时如果出现乱码99%是编码问题。System.IO默认使用系统的活动代码页在中文Windows上是GBK而Unity编辑器、移动设备或跨团队协作时更通用的编码是UTF-8。写入时指定编码明确使用Encoding.UTF8可以确保文件内容被正确存储并包含BOM字节顺序标记可选以帮助其他程序识别。读取时指定编码即使你知道是UTF-8也最好显式指定。对于来源不明的文件可以尝试Encoding.Default系统默认或自动检测。using System.Text; // 写入时指定UTF-8编码带BOM File.WriteAllText(filePath, “你好世界”, Encoding.UTF8); // 读取时也指定UTF-8编码 string content File.ReadAllText(filePath, Encoding.UTF8);一个常见坑如果你用Windows记事本保存了一个“UTF-8”文件它默认是带BOM的。而一些Linux工具或在线编辑器可能生成无BOM的UTF-8。在C#中Encoding.UTF8默认能处理这两种情况但如果你用new StreamReader(filePath)而不指定编码它可能会尝试检测BOM行为不够确定。因此显式指定编码是最佳实践。3.3 异常处理让你的代码更健壮文件操作是I/O操作充满了不确定性磁盘已满、文件被占用、路径无权限、设备突然拔出等。必须用try-catch块包裹核心操作。try { string content File.ReadAllText(filePath); // 处理content... } catch (FileNotFoundException) { Debug.LogError($“文件未找到: {filePath}将使用默认配置。”); // 初始化默认数据 } catch (IOException ex) // 这是一个更通用的I/O异常包括文件被占用、磁盘错误等 { Debug.LogError($“读写文件时发生I/O错误: {ex.Message}”); // 可能提示用户重试或进行降级处理 } catch (UnauthorizedAccessException) { Debug.LogError($“没有权限访问文件: {filePath}”); } catch (Exception ex) // 兜底 { Debug.LogError($“未知错误: {ex.Message}”); }进阶技巧对于可能被占用的文件如日志文件正在被另一个进程读取写入时可以考虑使用FileShare.Read模式打开FileStream允许其他进程读取但这样会增加复杂度。对于游戏存档通常独占写入即可。4. 实操过程与核心环节实现下面我们通过几个典型的Unity场景将上述理论转化为可运行的代码。4.1 场景一读写玩家游戏存档使用PersistentDataPath这是最常见的场景。我们将实现一个简单的存档系统保存玩家金币、等级和最后登录时间。using UnityEngine; using System.IO; using System.Text; public class GameSaveManager : MonoBehaviour { private string saveFilePath; [System.Serializable] // 必须标记为可序列化 public class SaveData { public int goldCoins; public int playerLevel; public string lastLoginTime; // 使用字符串存储时间方便序列化 } void Awake() { // 构造存档路径 string saveDir Path.Combine(Application.persistentDataPath, “Saves”); if (!Directory.Exists(saveDir)) { Directory.CreateDirectory(saveDir); } saveFilePath Path.Combine(saveDir, “player_save.json”); // 虽然存txt但内容用JSON格式 } // 同步保存 public void SaveGame(SaveData data) { try { // 将数据对象转换为JSON字符串 string jsonString JsonUtility.ToJson(data, true); // true参数使JSON格式化便于阅读 // 将JSON字符串写入txt文件 File.WriteAllText(saveFilePath, jsonString, Encoding.UTF8); Debug.Log($“游戏已保存至: {saveFilePath}”); } catch (System.Exception e) { Debug.LogError($“保存游戏失败: {e.Message}”); } } // 同步加载 public SaveData LoadGame() { SaveData loadedData new SaveData(); // 创建默认数据 if (File.Exists(saveFilePath)) { try { string jsonString File.ReadAllText(saveFilePath, Encoding.UTF8); loadedData JsonUtility.FromJsonSaveData(jsonString); Debug.Log(“游戏加载成功。”); } catch (System.Exception e) { Debug.LogError($“加载游戏失败将使用默认数据: {e.Message}”); // 可以在这里初始化一个默认的存档 loadedData.goldCoins 100; loadedData.playerLevel 1; loadedData.lastLoginTime System.DateTime.Now.ToString(); } } else { Debug.Log(“存档文件不存在创建新存档。”); // 初始化新存档 loadedData.goldCoins 100; loadedData.playerLevel 1; loadedData.lastLoginTime System.DateTime.Now.ToString(); SaveGame(loadedData); // 保存初始存档 } return loadedData; } // 异步保存示例避免卡顿 public async System.Threading.Tasks.Task SaveGameAsync(SaveData data) { string jsonString JsonUtility.ToJson(data, true); string tempFilePath saveFilePath “.tmp”; // 先写入临时文件 try { using (StreamWriter writer new StreamWriter(tempFilePath, false, Encoding.UTF8)) { await writer.WriteAsync(jsonString); } // 写入成功用临时文件替换原文件这是一个原子操作能防止写入中途崩溃导致原文件损坏 File.Replace(tempFilePath, saveFilePath, null); Debug.Log($“游戏已异步保存。”); } catch (System.Exception e) { Debug.LogError($“异步保存失败: {e.Message}”); // 清理可能的临时文件 if (File.Exists(tempFilePath)) File.Delete(tempFilePath); } } }代码解析与技巧使用JSON格式虽然文件扩展名是.txt但内容采用JSON格式。JsonUtility是Unity内置的高效序列化工具比直接拼接字符串更结构化更易于扩展和维护。异步保存SaveGameAsync方法展示了如何使用StreamWriter和async/await进行非阻塞写入。注意Unity的JsonUtility不是线程安全的所以序列化操作ToJson仍在主线程完成只有文件写入是异步的。原子性操作先写入临时文件.tmp成功后用File.Replace替换原文件。这能保证即使写入过程中游戏崩溃原存档文件也不会被损坏最多只是丢失本次保存的数据。路径检查在Awake中创建了存档目录确保后续写入不会因目录不存在而失败。4.2 场景二读取StreamingAssets中的配置文件处理Android平台假设我们有一个StreamingAssets/Config/game_config.txt文件里面是JSON格式的配置。using UnityEngine; using System.IO; using UnityEngine.Networking; using System.Threading.Tasks; public class ConfigLoader : MonoBehaviour { [System.Serializable] public class GameConfig { public float musicVolume; public string language; public int initialLives; } public GameConfig loadedConfig; async void Start() { loadedConfig await LoadConfigFromStreamingAssets(“Config/game_config.txt”); if (loadedConfig ! null) { Debug.Log($“配置加载成功: 音量{loadedConfig.musicVolume}, 语言{loadedConfig.language}”); // 应用配置... } } private async TaskGameConfig LoadConfigFromStreamingAssets(string relativePath) { string filePath Path.Combine(Application.streamingAssetsPath, relativePath); string jsonText null; #if UNITY_ANDROID !UNITY_EDITOR // Android平台特殊处理 jsonText await ReadFileAndroid(filePath); #else // 其他平台Editor, iOS, Windows, macOS等可以直接用System.IO if (File.Exists(filePath)) { jsonText File.ReadAllText(filePath, System.Text.Encoding.UTF8); } else { Debug.LogError($“配置文件不存在于StreamingAssets: {filePath}”); return null; } #endif if (!string.IsNullOrEmpty(jsonText)) { try { return JsonUtility.FromJsonGameConfig(jsonText); } catch (System.Exception e) { Debug.LogError($“解析配置文件JSON失败: {e.Message}”); return null; } } return null; } private async Taskstring ReadFileAndroid(string uriPath) { // 在Android上StreamingAssets路径需要加上 “file://” 前缀并且文件在APK压缩包内 string uri “file://” uriPath; using (UnityWebRequest request UnityWebRequest.Get(uri)) { var operation request.SendWebRequest(); while (!operation.isDone) { await Task.Yield(); // 等待一帧避免阻塞 } if (request.result UnityWebRequest.Result.Success) { return request.downloadHandler.text; } else { Debug.LogError($“从StreamingAssets读取失败({uri}): {request.error}”); return null; } } } }跨平台关键点预处理指令#if UNITY_ANDROID !UNITY_EDITOR用于区分Android真机环境和其它环境包括编辑器。在编辑器中即使平台设为Android我们也能直接用System.IO读取项目目录下的文件方便调试。但真机上必须用UnityWebRequest。UnityWebRequest这是Unity推荐的用于Android平台读取StreamingAssets的方式。注意其URI格式和异步操作。异步等待使用async/await和Task.Yield()来避免阻塞主线程保持游戏流畅。4.3 场景三写入运行时日志文件日志对于调试线上问题至关重要。我们需要一个能持续追加写入、自动管理文件大小/数量的日志系统。using UnityEngine; using System.IO; using System.Text; using System.Collections.Generic; public class GameLogger : MonoBehaviour { private static GameLogger instance; private StreamWriter logWriter; private string logFilePath; private Queuestring logQueue new Queuestring(); private bool isWriting false; public int maxLogFileSizeKB 1024; // 单个日志文件最大1MB public int maxLogFilesCount 5; // 最多保留5个历史日志文件 void Awake() { if (instance null) { instance this; DontDestroyOnLoad(gameObject); InitializeLogger(); } else { Destroy(gameObject); } } void InitializeLogger() { string logDir Path.Combine(Application.persistentDataPath, “Logs”); if (!Directory.Exists(logDir)) Directory.CreateDirectory(logDir); logFilePath Path.Combine(logDir, $“game_log_{System.DateTime.Now:yyyyMMdd_HHmmss}.txt”); // 检查并滚动日志文件 ManageLogFiles(logDir); try { // 第二个参数为true表示追加模式 logWriter new StreamWriter(logFilePath, true, Encoding.UTF8); logWriter.AutoFlush true; // 自动刷新缓冲区减少数据丢失风险 Application.logMessageReceived HandleLog; // 捕获Unity自身的Debug.Log Debug.Log($“日志系统初始化完成文件位于: {logFilePath}”); } catch (System.Exception e) { Debug.LogError($“初始化日志文件失败: {e.Message}”); } } void HandleLog(string logString, string stackTrace, LogType type) { string formattedLog $“[{System.DateTime.Now:HH:mm:ss.fff}] [{type}] {logString}”; if (type LogType.Exception || type LogType.Error) { formattedLog “\n” stackTrace; } EnqueueLog(formattedLog); } void EnqueueLog(string log) { lock (logQueue) // 加锁因为Log可能从多线程调用 { logQueue.Enqueue(log); } if (!isWriting) { WriteLogsToFile(); } } async void WriteLogsToFile() { isWriting true; while (logQueue.Count 0) { string logToWrite null; lock (logQueue) { if (logQueue.Count 0) logToWrite logQueue.Dequeue(); } if (logToWrite ! null logWriter ! null) { try { await logWriter.WriteLineAsync(logToWrite); } catch (System.Exception e) { Debug.LogError($“写入日志失败: {e.Message}”); // 可以尝试重新初始化Writer或降级处理 } } await System.Threading.Tasks.Task.Yield(); // 写一行让出一帧避免密集IO阻塞 } isWriting false; } void ManageLogFiles(string logDir) { try { // 1. 检查当前目录下所有日志文件 var logFiles Directory.GetFiles(logDir, “game_log_*.txt”); ListFileInfo fileInfoList new ListFileInfo(); foreach (var file in logFiles) { fileInfoList.Add(new FileInfo(file)); } // 按最后写入时间排序最新的在前面 fileInfoList.Sort((a, b) b.LastWriteTime.CompareTo(a.LastWriteTime)); // 2. 如果文件太多删除最旧的 while (fileInfoList.Count maxLogFilesCount) { var oldFile fileInfoList[fileInfoList.Count - 1]; File.Delete(oldFile.FullName); fileInfoList.RemoveAt(fileInfoList.Count - 1); Debug.Log($“删除旧日志文件: {oldFile.Name}”); } // 3. 检查最新文件大小如果过大则重命名归档新日志会创建新文件 if (fileInfoList.Count 0) { var latestFile fileInfoList[0]; if (latestFile.Length maxLogFileSizeKB * 1024) { string archivedName Path.Combine(logDir, $“game_log_{System.DateTime.Now:yyyyMMdd_HHmmss}_full.txt”); File.Move(latestFile.FullName, archivedName); Debug.Log($“日志文件已满已归档: {latestFile.Name} - {Path.GetFileName(archivedName)}”); } } } catch (System.Exception e) { Debug.LogError($“管理日志文件时出错: {e.Message}”); } } void OnDestroy() { if (logWriter ! null) { Application.logMessageReceived - HandleLog; logWriter.Close(); logWriter.Dispose(); } } }日志系统设计要点异步队列写入HandleLog可能在任何线程被调用尤其是异常日志。我们通过一个队列(logQueue)和锁(lock)来安全地收集日志然后在主线程或通过async中异步写入文件避免I/O阻塞和线程冲突。自动文件管理ManageLogFiles方法实现了日志滚动Log Rotation。它检查文件数量和大小自动删除旧文件或重命名已满的文件防止日志无限增长占用磁盘空间。捕获全局日志通过Application.logMessageReceived事件可以捕获所有Debug.Log、Debug.LogError等输出的信息并将其重定向到我们的日志文件非常强大。资源释放在OnDestroy中关闭并释放StreamWriter确保文件句柄被正确释放避免资源泄漏。5. 常见问题与排查技巧实录即使按照最佳实践编写代码在实际开发中仍会遇到各种问题。以下是我总结的常见“坑”及其解决方案。5.1 问题一在Android/WebGL平台读取文件失败返回空或报错症状在编辑器里运行正常打包到Android或WebGL后读取StreamingAssets或PersistentDataPath下的文件失败。排查步骤确认路径首先在代码中打印出你正在使用的完整路径Debug.Log(filePath)。在Android上PersistentDataPath通常类似/storage/emulated/0/Android/data/package.name/files。确认路径符合预期。平台差异Android读取StreamingAssets必须使用UnityWebRequest且URI以“file://”开头。直接使用System.IO.File会失败因为文件在APK压缩包内。WebGLWebGL对文件系统的访问限制极大。PersistentDataPath对应浏览器的IndexedDB是一个异步的虚拟文件系统。读写操作必须使用Unity提供的Application.persistentDataPath配合File.ReadAllText等Unity会通过Emscripten将其转换为异步JS调用但行为可能与标准.NET不一致。对于StreamingAssets在WebGL中它位于服务器的数据目录需要通过UnityWebRequest从网络加载。文件是否存在在Android上打包前确保文件在Assets/StreamingAssets目录下。在WebGL上确保文件在构建输出的StreamingAssets文件夹中并且服务器配置正确MIME类型。权限对于PersistentDataPath确保应用有相应的存储权限Android需要在Manifest中声明WRITE_EXTERNAL_STORAGE权限且Android 10需要适配分区存储。5.2 问题二写入文件成功但内容乱码或格式错乱症状打开保存的txt文件中文字符显示为“???”或“锟斤拷”或者JSON格式不对导致无法解析。排查步骤检查编码确保读写时使用相同的编码强烈推荐始终使用Encoding.UTF8。检查你的写入代码File.WriteAllText,StreamWriter构造函数和读取代码是否都指定了Encoding.UTF8。检查BOM某些旧版软件可能无法识别无BOM的UTF-8。Encoding.UTF8默认会写入BOM。如果你需要无BOM的UTF-8例如给某些特定解析器使用可以使用new UTF8Encoding(false)来创建编码器实例。检查内容格式如果是结构化数据如JSON写入后可以用文本编辑器打开检查格式是否正确括号匹配引号完整。可以使用在线JSON验证工具检查。特殊字符转义如果文本中包含换行符\n、制表符\t或引号等在JSON序列化时JsonUtility会自动处理转义。但如果是自己拼接字符串需要手动处理例如将\n替换为\\n。5.3 问题三异步文件操作导致Unity对象状态错误或崩溃症状在使用async/await进行文件读写后尝试修改Unity的GameObject、Transform或访问UnityEngine.Object时报错或无效。原因Unity的API不是线程安全的。async/await的延续await之后的代码默认可能在线程池线程上执行而不是主线程。解决方案使用UnitySynchronizationContext推荐在Unity 2022.3 LTS及以上版本可以通过await一个YieldInstruction如await AsyncOperation,await Task.Delay来让后续代码回到主线程。更通用的方法是使用UnityEngine.Dispatcher需通过包管理器安装com.unity.dispatcher或自己维护一个主线程任务队列。在回调中回到主线程对于旧的UnityWebRequest协程方式其回调本身就在主线程。对于Task可以在await之后用MainThreadDispatcher执行UI更新。简化处理如果操作很快或者不涉及Unity对象可以继续在后台线程执行。如果必须更新UI则将文件读取的结果存储在一个变量中然后设置一个标志位在Update()方法中检查这个标志位并在主线程进行后续处理。// 示例确保异步操作后回到主线程更新UI (假设有MainThreadDispatcher) public async void LoadAndUpdateUI() { string data await LoadLargeFileAsync(); // 这个async方法可能在后台线程完成 // 此时不在主线程不能直接操作UI // 方案1通过Dispatcher await MainThreadDispatcher.Instance.EnqueueAsync(() { textComponent.text data; // 在主线程安全更新UI }); // 方案2在Update中检查状态略 }5.4 问题四文件被占用无法写入或删除症状尝试写入文件时抛出IOException提示“文件正由另一进程使用”。原因最常见的原因是没有正确关闭或释放文件流。例如用StreamReader/StreamWriter或FileStream打开了文件但在异常发生时没有在finally块中关闭或者没有使用using语句。解决方案始终使用using语句using语句能确保即使在发生异常时Dispose()方法也会被调用从而释放文件句柄。using (StreamWriter writer new StreamWriter(filePath, false, Encoding.UTF8)) { await writer.WriteAsync(content); } // 这里writer会自动Dispose文件句柄释放检查其他进程是否你的游戏有多个实例在运行或者是否有杀毒软件、云同步软件如OneDrive锁定了文件暂时关闭这些软件进行测试。重试机制对于非关键性写入如日志可以实现简单的重试逻辑。int retries 3; while (retries 0) { try { File.WriteAllText(path, content); break; // 成功则跳出循环 } catch (IOException) { retries--; await Task.Delay(100); // 等待100毫秒再试 if (retries 0) throw; // 重试次数用完抛出异常 } }5.5 性能优化与内存管理对于频繁或处理大文件的读写操作性能至关重要。缓冲BufferingStreamWriter默认有缓冲区不要频繁地打开、写入、关闭文件。对于日志这类追加写入的场景保持一个StreamWriter实例并复用设置AutoFlush true有轻微性能损耗但数据更安全或定期调用Flush()。避免一次性读取超大文件不要用File.ReadAllText读取一个几百MB的日志文件这会导致巨大的内存分配和长时间的GC暂停。应该使用StreamReader并逐行(ReadLine)或按块读取。使用StringBuilder拼接字符串如果需要将多行内容拼接成一个字符串再写入使用StringBuilder比反复使用操作符高效得多。异步操作的权衡异步操作能提升响应速度但会引入额外的开销状态机、调度。对于非常小的文件1KB同步操作可能反而更快。根据实际情况进行性能测试。文件读写是Unity开发中看似简单实则暗藏玄机的基础操作。从路径选择、API选型、编码处理、异常防御到跨平台适配和性能优化每一个环节都需要根据具体场景仔细考量。希望这篇结合了大量实战经验的解析能帮助你构建出健壮、高效、可维护的文件处理模块让你在未来的项目中不再为txt文件的读写而头疼。记住好的基础设施代码是项目稳定的基石。
返回列表