在 C# 中打开大量文件流的替代方法
Alternative to opening large number of file streams in C#
在 Unity 中,我正在制作一个项目,该项目按程序构建一个(特别复杂的)世界,并将所有生成的数据存储在磁盘文件中以备将来使用。我已经将每个世界块的文件大小降低到 8 KB,并且可以让它变得更小;但是快速连续打开和关闭如此多的文件流会产生额外的成本。
启动时,我创建了 2,970 个区块。我已经在 FX-8300 cpu 上使用相当快的 HDD 将加载时间缩短到大约 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;
}
读取二进制文件的最快方法是使用 File.ReadAllBytes
一次读取整个文件,如 here 所述。该方法也有对应的写入方法。
使用 Thread.Sleep(100)
是一种非常低效的等待结果的方式。我不熟悉 Unity,但请检查您是否能够使用 async-await C# 语法(以及 Task
对象)进行异步
操作。
此外,如果您有大量文件,研究一些面向文档的数据库可能会有所帮助,这些数据库将为您优化读写部分,您只需要处理与数据库的通信。 MongoDB 例如有用于二进制数据块的 GridFS,但可能有一些文档数据库更适合您的用例。
考虑到您没有任何关系,使用 SQL 数据库来解决您的问题毫无意义。但是,使用 SQLite 之类的东西仍然可能比使用多个文件更好。
在 Unity 中,我正在制作一个项目,该项目按程序构建一个(特别复杂的)世界,并将所有生成的数据存储在磁盘文件中以备将来使用。我已经将每个世界块的文件大小降低到 8 KB,并且可以让它变得更小;但是快速连续打开和关闭如此多的文件流会产生额外的成本。
启动时,我创建了 2,970 个区块。我已经在 FX-8300 cpu 上使用相当快的 HDD 将加载时间缩短到大约 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;
}
读取二进制文件的最快方法是使用 File.ReadAllBytes
一次读取整个文件,如 here 所述。该方法也有对应的写入方法。
使用 Thread.Sleep(100)
是一种非常低效的等待结果的方式。我不熟悉 Unity,但请检查您是否能够使用 async-await C# 语法(以及 Task
对象)进行异步
操作。
此外,如果您有大量文件,研究一些面向文档的数据库可能会有所帮助,这些数据库将为您优化读写部分,您只需要处理与数据库的通信。 MongoDB 例如有用于二进制数据块的 GridFS,但可能有一些文档数据库更适合您的用例。
考虑到您没有任何关系,使用 SQL 数据库来解决您的问题毫无意义。但是,使用 SQLite 之类的东西仍然可能比使用多个文件更好。