在 C# 中打开大量文件流的替代方法

Posted

技术标签:

【中文标题】在 C# 中打开大量文件流的替代方法【英文标题】:Alternative to opening large number of file streams in C# 【发布时间】:2018-07-15 23:33:49 【问题描述】:

在 Unity 中,我正在制作一个项目,该项目在程序上构建一个(特别复杂的)世界,并将所有生成的数据存储在磁盘文件中以供将来使用。我已将每个世界块的文件大小降低到 8 KB,并且可能会变得更小;但是快速连续打开和关闭如此多的文件流会产生额外的成本。

在启动时,我创建了 2,970 个块。在具有相当快的 HDD 的 FX-8300 cpu 上,我将加载时间缩短到大约 20 秒。缩小文件在这里不太可能对我有帮助;我似乎在打开和关闭文件流时遇到了固定成本(乘以近 3,000!)

所以,我正在寻找替代方案。我最近的大部分编程经验是 Java、Python、javascript 和 D;所以我可能会错过房间里的一头大象。 LTS 肯定必须是本地的。是否可以加速 FileStreams,将它们放入某种对象池中?或者我可以使用某种 SQLite 系统吗?那里有更好的东西吗?

Unity 目前似乎限制我使用 .NET 2.0 功能,但海量文件管理是一项相当普遍的任务(在更广泛的意义上),我不禁觉得我这样做很天真方式。

感谢您的所有意见!

代码很多,但相关的部分大概就是这个。如果您需要查看其他内容,请告诉我。

public bool record(BlockData data) 
    string name = BuildChunkFileName(data.Origin);
    try 
        FileStream stream = new FileStream(BuildChunkFilePath(data.Origin), FileMode.OpenOrCreate);

        ushort[] arrayData = new ushort[Chunk.blockSize * Chunk.blockSize * Chunk.blockSize];

        int index = 0;
        foreach(BlockProperties props in data.Properties) 
            arrayData[index] = props.isOpaque ? (ushort)1 : (ushort)0;
            index++;
        

        byte[] byteData = new byte[(Chunk.blockSize * Chunk.blockSize * Chunk.blockSize) * sizeof(ushort)];
        Buffer.BlockCopy(arrayData, 0, byteData, 0, (Chunk.blockSize * Chunk.blockSize * Chunk.blockSize) * sizeof(ushort));
        IAsyncResult result = stream.BeginWrite(byteData, 0, byteData.Length, null, null);

        while(!result.IsCompleted) 
            Thread.Sleep(100);
        

        stream.Close();
     catch(Exception e) 
        Debug.LogException (e);
        return false;
    
    return true;


public bool read(BlockData data) 
    int x = 0, y = 0, z = 0;
    int i = 0;

    string name = BuildChunkFileName (data.Origin);
    string path = BuildChunkFilePath (data.Origin);

    try 
        FileStream stream = new FileStream(path, FileMode.Open);

        byte[] byteData = new byte[(Chunk.blockSize * Chunk.blockSize * Chunk.blockSize) * sizeof(ushort)];
        IAsyncResult result = stream.BeginRead(byteData, 0, byteData.Length, null, null);

        while(!result.IsCompleted) 
            Thread.Sleep(100);
        

        ushort[] arrayData = new ushort[Chunk.blockSize * Chunk.blockSize * Chunk.blockSize];
        Buffer.BlockCopy(byteData, 0, arrayData, 0, byteData.Length);

        for(i = 0; i < arrayData.Length; i++) 
            x = i % Chunk.blockSize;
            y = (i / (Chunk.blockSize)) % Chunk.blockSize;
            z = i / (Chunk.blockSize * Chunk.blockSize);
            data.Properties [x, y, z].isOpaque = arrayData [i] == 0 ? false : true;
        

        stream.Close();
     catch(Exception) 
        // a lot of specific exception handling here, the important part
        // is that I return false so I know there was a problem.
        return false;
    

    return true;

【问题讨论】:

如果你需要快速访问文件,试试内存映射文件 我应该注意,我确实打算最终使用 using 语句打开流;但一次只做一件事。 读取二进制文件的最快方法是使用 File.ReadAllBytes 一次读取整个文件,如下所述:***.com/a/10239650/7821979(是的,它有对应的写入) 另外Thread.Sleep(100) 是一种非常低效的等待结果的方法,请检查您是否能够使用 async-await C# 语法来处理异步操作 此外,如果您有大量文件,那么查看一些面向文档的数据库(例如 MongoDB 具有用于二进制数据块的 GridFS)可能会有所帮助,这将优化读取并为您编写部分,您只需要处理与数据库的通信 【参考方案1】:

读取二进制文件的最快方法是使用File.ReadAllBytes 一次读取整个文件,如here 所述。该方法也有对应的写方法。

使用Thread.Sleep(100) 是一种非常低效的等待结果的方法。我不熟悉 Unity,但请检查您是否能够使用 async-await C# 语法(以及 Task 对象)进行异步 操作。

 

此外,如果您有大量文件,那么研究一些面向文档的数据库可能会有所帮助,这些数据库将为您优化读写部分,您只需要处理与数据库的通信即可。例如,MongoDB 具有用于二进制数据块的 GridFS,但可能有一些文档数据库更适合您的用例。

考虑到您没有任何关系,使用 SQL 数据库来解决您的问题是没有意义的。但是,使用 SQLite 之类的东西仍然可能比使用多个文件更好。

【讨论】:

谢谢,我会调查的!这是早期的代码;我不喜欢 Thread.sleep 中一致的 100 毫秒等待。我也会研究异步等待。 这就是我要找的一切。它摆脱了新的声明,我很确定这是放缓的很大一部分。顺便说一句,您的方法还消除了对 Thread.Sleep 的需要。我应该指出,在此方法的范围之外,我的代码中确实存在关系——主要是通过纯粹确定的 Perlin 噪声函数在调查坐标和块数据之间建立关系。我仍然觉得 SQLite 可能有一些好处,即使只是管理单个文件。

以上是关于在 C# 中打开大量文件流的替代方法的主要内容,如果未能解决你的问题,请参考以下文章

重工作的Asynctask替代方案(在UI中添加大量片段)

在 C# 中以编程方式从 Excel 文件中大量导入数据到 Access

标准 C# DataGrid 的免费替代品?

如何把大量数据导入EXCEL

C# 将大文件写入网络流的问题。。。

对于大量选择,ModelChoiceField 都有哪些替代方法?