Unity本地存储之二进制存档

本文介绍了一种使用二进制格式在Unity中保存和加载游戏数据的方法。通过BinaryFormatter序列化Data对象到文件流,实现了游戏状态的存档功能;反序列化过程则将文件流还原为Data对象,恢复游戏状态。

 //二进制方法:存档
    private void SaveByBin()
    {
        //序列化过程(将Data对象转换为字节流)
        //创建对象并保存当前游戏状态
        Data data= new Data();
        //创建一个二进制格式化程序
        BinaryFormatter bf = new BinaryFormatter();
        //创建一个文件流
        FileStream fileStream = File.Create(Application.dataPath + "/StreamingFile" + "/byBin.txt");
        //用二进制格式化程序的序列化方法来序列化Data对象,参数:创建的文件流和需要序列化的对象
        bf.Serialize(fileStream, data);
        //关闭流
        fileStream.Close();

        //如果文件存在,则显示保存成功
        if (File.Exists(Application.dataPath + "/StreamingFile" + "/byBin.txt"))
        {
            UIManager._instance.ShowMessage("保存成功");
        }
    }

 //二进制方法:读档

 private void LoadByBin()
    {
        if(File.Exists(Application.dataPath + "/StreamingFile" + "/byBin.txt"))
        {
            //反序列化过程
            //创建一个二进制格式化程序
            BinaryFormatter bf = new BinaryFormatter();
            //打开一个文件流
            FileStream fileStream = File.Open(Application.dataPath + "/StreamingFile" + "/byBin.txt", FileMode.Open);
            //调用格式化程序的反序列化方法,将文件流转换为一个Data对象
            Data data= (Data)bf.Deserialize(fileStream);
            //关闭文件流
            fileStream.Close();
            SetData(data);
            UIManager._instance.ShowMessage("");
        }
        else
        {
            UIManager._instance.ShowMessage("存档文件不存在");
        } 
    }

<think> 我们正在讨论的是如何在Unity中分流读取二进制文件。根据之前的引用,用户提到了使用FileStream、BinaryReader等类来读取二进制文件,以及在Unity中的一些实践(如使用ScriptableObject封装数据)。现在用户的需求是“分流读取”,我认为这指的是流式加载(Streaming)或分块读取(Chunked Reading)大型二进制文件,以避免一次性加载整个文件造成内存压力。 根据引用[1][2][3][4][5],我们可以总结出Unity中处理二进制文件的常用方法: 1. 使用FileStream、BinaryReader进行基础读写。 2. 对于大文件,采用分块读取(每次读取一小部分数据)以避免内存峰值。 3. 使用异步操作(协程)来避免主线程阻塞。 4. 对于配置文件,可以考虑使用ScriptableObject进行封装和序列化,方便Unity管理。 因此,分流读取二进制文件的实现思路如下: 1. **使用FileStream打开文件**:以异步方式(FileStream的异步方法)或通过协程分帧读取。 2. **分块读取**:每次读取一定大小的字节块(例如4KB、16KB),然后处理该块数据。 3. **异步处理**:使用协程(IEnumerator)来实现分帧读取,这样就不会在一帧内加载整个大文件。 4. **进度反馈**:在读取过程中可以更新进度条,提升用户体验。 具体实现步骤: 步骤1:创建文件流 使用`FileStream`打开文件,注意使用`FileMode.Open`和`FileAccess.Read`。 步骤2:定义缓冲区 创建一个字节数组作为缓冲区,大小根据需求设定(如4096字节)。 步骤3:循环读取 在循环中,使用`FileStream.ReadAsync`(异步方法)或`FileStream.Read`(同步方法,在协程中分帧)读取数据到缓冲区。 步骤4:处理数据 将读取的缓冲区数据转换为目标格式(如字符串、结构体等),并处理。如果是分块处理,注意处理块与块之间的衔接(例如,如果数据是跨块的,需要特殊处理)。 步骤5:关闭流 读取完成后关闭流。 考虑到Unity的主线程限制,我们使用协程来实现分帧读取。即使使用同步读取,在协程中分帧也不会阻塞主线程太久。 示例代码(使用协程分块读取二进制文件): ```csharp using UnityEngine; using System.IO; using System.Text; using System.Collections; public class BinaryFileStreamReader : MonoBehaviour { public string filePath = "largefile.bin"; // 文件路径,放在StreamingAssets或PersistentDataPath IEnumerator Start() { // 构建完整路径 string fullPath = Path.Combine(Application.streamingAssetsPath, filePath); // 检查文件是否存在 if (!File.Exists(fullPath)) { Debug.LogError("文件不存在: " + fullPath); yield break; } // 定义缓冲区大小(4KB) const int bufferSize = 4096; byte[] buffer = new byte[bufferSize]; // 打开文件流 using (FileStream fs = new FileStream(fullPath, FileMode.Open, FileAccess.Read)) { long totalBytes = fs.Length; long bytesRead = 0; // 循环读取直到文件末尾 while (bytesRead < totalBytes) { // 读取一块数据 int readCount = fs.Read(buffer, 0, bufferSize); if (readCount == 0) // 没有读到数据,结束 break; // 处理读取的数据块(这里只是示例,将字节转换为字符串) // 注意:如果二进制文件不是文本,不要这样转换。这里仅作演示。 string chunk = Encoding.UTF8.GetString(buffer, 0, readCount); ProcessChunk(chunk); bytesRead += readCount; // 更新进度(可选) float progress = (float)bytesRead / totalBytes; Debug.Log($"读取进度: {progress:P}"); // 每读取一块就暂停一帧,避免卡顿 yield return null; } } Debug.Log("文件读取完成"); } // 处理数据块的方法 private void ProcessChunk(string chunk) { // 这里处理每一块数据,例如拼接到一个大的字符串,或者解析二进制结构等。 // 注意:如果文件是结构化的二进制数据,需要更复杂的解析逻辑。 } } ``` 注意事项: 1. **文件路径**:在Unity中,不同平台的文件路径不同。对于只读文件,使用`Application.streamingAssetsPath`;对于可读写文件,使用`Application.persistentDataPath`[^3]。 2. **编码问题**:如果二进制文件是文本,注意使用正确的编码(如UTF-8)[^4]。如果是非文本的二进制数据(如游戏存档、地图数据),则不需要转换为字符串,而是直接解析为二进制结构。 3. **大文件处理**:对于非常大的文件(如100MB以上),分块读取可以避免内存峰值,但要注意不要分得太小(如每块4KB可能会造成过多的协程切换),可以根据实际情况调整块大小(如64KB、1MB)[^5]。 4. **结构化二进制数据**:如果文件是结构化的(如包含多个字段的数据记录),则需要在读取时解析结构。可以使用`BinaryReader`来帮助读取特定类型的数据(如int, float, string等)。但是注意,`BinaryReader`不支持分块读取,所以我们需要自己管理缓冲区,确保每次读取完整的数据结构。 对于结构化二进制数据的读取,我们可以结合`BinaryReader`和分块读取: 思路:一次性读取整个文件(如果文件不是特别大)可能更简单,但如果文件很大,我们可以将文件分成多个块,每个块包含若干条记录。这样,我们可以逐块读取并处理。 例如,假设二进制文件由多个相同结构的记录组成,每个记录大小为128字节。我们可以设置块大小为128*100=12800字节(即每次读取100条记录)。这样,每块读取后,我们用`BinaryReader`解析这100条记录。 示例代码(结构化数据): ```csharp using (FileStream fs = new FileStream(fullPath, FileMode.Open, FileAccess.Read)) { // 假设每条记录128字节 int recordSize = 128; int recordsPerChunk = 100; int chunkSize = recordSize * recordsPerChunk; byte[] chunkBuffer = new byte[chunkSize]; long totalBytes = fs.Length; long bytesRead = 0; while (bytesRead < totalBytes) { int bytesToRead = (int)Mathf.Min(chunkSize, totalBytes - bytesRead); int readCount = fs.Read(chunkBuffer, 0, bytesToRead); if (readCount == 0) break; // 使用MemoryStream和BinaryReader来解析这个块 using (MemoryStream ms = new MemoryStream(chunkBuffer, 0, readCount)) using (BinaryReader reader = new BinaryReader(ms)) { // 计算这个块中有多少条记录 int recordsInChunk = readCount / recordSize; for (int i = 0; i < recordsInChunk; i++) { // 读取一条记录 int id = reader.ReadInt32(); float value = reader.ReadSingle(); // ... 其他字段 // 处理记录 ProcessRecord(id, value); } } bytesRead += readCount; yield return null; } } ``` 这种方法结合了分块读取和结构化解析,适用于大型结构化二进制文件。 总结: - Unity中分流读取二进制文件的核心是分块读取和异步处理(协程)。 - 对于非结构化数据(如纯文本),可以按字节块读取并逐步处理。 - 对于结构化数据,可以按记录块读取,然后使用`BinaryReader`解析。 - 注意文件路径和平台差异,以及编码问题。 相关问题: 1. 如何在Unity中安全地写入二进制文件? 2. 处理大型二进制文件时如何避免内存碎片? 3. Unity中如何实现二进制文件的断点续传? 4. 如何加密Unity中存储的二进制文件? 5. 如何优化二进制文件的读取速度? </think> ### Unity中分流读取二进制文件的实现方法 在Unity中分流读取二进制文件的核心是使用**流式读取(Streaming)** 技术,通过分块加载避免一次性加载大文件造成内存压力。以下是具体实现方案: #### 1. 基础分流读取流程 ```mermaid graph TD A[打开文件流] --> B[分块读取数据] B --> C[处理当前数据块] C --> D{是否读取完成?} D -->|否| B D -->|是| E[关闭文件流] ``` #### 2. 分块读取实现代码 ```csharp IEnumerator StreamReadBinaryFile(string filePath) { // 完整文件路径(支持各平台) string fullPath = Path.Combine(Application.streamingAssetsPath, filePath); using (FileStream fs = new FileStream(fullPath, FileMode.Open, FileAccess.Read)) { long fileSize = fs.Length; byte[] buffer = new byte[4096]; // 4KB缓冲区 int bytesRead; long totalRead = 0; // 分块读取循环 while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0) { // 处理当前数据块 ProcessDataChunk(buffer, bytesRead); totalRead += bytesRead; float progress = (float)totalRead / fileSize; // 更新进度显示(可选) UpdateProgressUI(progress); yield return null; // 每帧处理一块 } } Debug.Log("二进制文件读取完成"); } void ProcessDataChunk(byte[] chunk, int size) { // 示例:处理二进制数据块 // 可根据实际需求解析为特定结构 using (MemoryStream ms = new MemoryStream(chunk, 0, size)) using (BinaryReader reader = new BinaryReader(ms)) { while (ms.Position < size) { int dataType = reader.ReadInt32(); // 根据类型解析后续数据... } } } ``` #### 3. 关键技术要点 1. **缓冲区优化** - 动态调整缓冲区大小: ```csharp int bufferSize = Mathf.Min(1024 * 1024, (int)(fs.Length / 100)); // 最大1MB,最小文件1% ``` - 针对平台优化: ```csharp #if UNITY_IOS bufferSize = 16384; // iOS建议16KB块 #endif ``` 2. **异步处理** - 使用`FileStream.BeginRead`实现真异步: ```csharp IAsyncResult result = fs.BeginRead(buffer, 0, bufferSize, null, null); while (!result.IsCompleted) { yield return null; } int bytesRead = fs.EndRead(result); ``` 3. **内存映射文件** 适合超大文件(>100MB): ```csharp using (var mmf = MemoryMappedFile.CreateFromFile(fullPath)) using (var accessor = mmf.CreateViewAccessor()) { for (long offset = 0; offset < fileSize; offset += chunkSize) { byte[] chunk = new byte[chunkSize]; accessor.ReadArray(offset, chunk, 0, chunkSize); // 处理数据块... } } ``` 4. **数据解析优化** - 结构体直接映射: ```csharp [StructLayout(LayoutKind.Sequential, Pack = 1)] struct BinHeader { public int version; public uint checksum; } // 直接内存映射读取 var header = accessor.Read<BinHeader>(0); ``` #### 4. 各平台注意事项 1. **Android平台** 使用`UnityWebRequest`替代直接文件访问: ```csharp UnityWebRequest request = UnityWebRequest.Get(Path.Combine(Application.streamingAssetsPath, filePath)); yield return request.SendWebRequest(); byte[] data = request.downloadHandler.data; ``` 2. **加密二进制文件** 添加AES解密层: ```csharp using (Aes aes = Aes.Create()) { aes.Key = encryptionKey; using (CryptoStream cs = new CryptoStream(fs, aes.CreateDecryptor(), CryptoStreamMode.Read)) { // 从cs读取解密数据 } } ``` 3. **性能监控** ```csharp System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); sw.Start(); // 读取操作... sw.Stop(); Debug.Log($"读取耗时: {sw.ElapsedMilliseconds}ms"); ``` > ⚠️ **注意事项** > 1. 使用`Application.persistentDataPath`存储动态生成的二进制文件[^3] > 2. 处理跨区块的结构体数据时需特殊处理字段边界 > 3. 使用`using`语句确保资源释放 > 4. 大端序/小端序需统一处理(`BitConverter.IsLittleEndian`) ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值