System.IO.FileStream在巨大的文件上超级慢

时间:2010-07-11 07:59:42

标签: c#

我有一段代码需要能够在文件末尾修改几个字节。问题是文件很大。高达100+ Gb。

我需要尽可能快地进行操作,但是经过几个小时的Googeling后,看起来.Net在这里相当有限了吗?

我大部分时间都在尝试使用System.IO.FileStream并且不知道其他任何方法。一个“反向”文件流可以做,但我知道如何创建一个(从结束而不是从头开始写)。

以下是我的工作:(注意:关闭流时花费的时间)

    static void Main(string[] args)
    {    
        //Simulate a large file
        int size = 1000 * 1024 * 1024;
        string filename = "blah.dat";
        FileStream fs = new FileStream(filename, FileMode.Create);
        fs.SetLength(size);
        fs.Close();

        //Modify the last byte
        fs = new FileStream(filename, FileMode.Open);

        //If I don't seek, the modification happens instantly
        fs.Seek(-1, SeekOrigin.End);
        fs.WriteByte(255);

        //Now, since I am modifying the last byte, 
        //this last step is very slow
        fs.Close();
    }
}

4 个答案:

答案 0 :(得分:11)

就像Darin已经注意到的那样,这是一个大型文件“模拟”的工件。

延迟是实际上“填满”文件,延迟只在第一次发生。如果您将部分从//Modify the last byte重复到fs.Close();,则会非常快。

答案 1 :(得分:4)

我进行了一些测试,结果有点令人困惑。如果您创建文件并在同一程序中修改它,则速度很慢:

static void Main(string[] args)
{
    //Simulate a large file
    int size = 100 * 1024 * 1024;
    string filename = "blah.datn";
    using (var fs = new FileStream(filename, FileMode.Create))
    {
        fs.SetLength(size);
    }

    using (var fs = new FileStream(filename, FileMode.Open))
    {
        fs.Seek(-1, SeekOrigin.End);
        fs.WriteByte(255);
    }
}

但是如果文件存在而您只是尝试修改最后一个字节,那么它很快:

static void Main(string[] args)
{
    string filename = "blah.datn";
    using (var fs = new FileStream(filename, FileMode.Open))
    {
        fs.Seek(-1, SeekOrigin.End);
        fs.WriteByte(255);
    }
}

<击>嗯...


更新:

请忽略我之前的观察结果,并取消标记为答案,因为它完全错误

进一步调查这个问题我注意到以下模式。假设您分配给定大小的文件,其零字节如下:

using (var stream = File.OpenWrite("blah.dat"))
{
    stream.SetLength(100 * 1024 * 1024);
}

此操作非常快,它会创建一个填充零的100MB文件。

现在,如果在某个其他程序中尝试修改最后一个字节,关闭流将会很慢:

using (var stream = File.OpenWrite("blah.dat"))
{
    stream.Seek(-1, SeekOrigin.End);
    stream.WriteByte(255);
}

我不知道文件系统的内部工作方式或者这个文件是如何创建的,但我觉得它没有完全初始化,直到你尝试修改它并关闭句柄会很慢。

为了确认这一点,我在非托管代码中进行了测试(随意修复任何异常,因为我的C非常生锈):

void main()
{
    int size = 100 * 1024 * 1024 - 1;
    FILE *handle = fopen("blah.dat", "wb");
    if (handle != NULL) {
        fseek(handle, size, SEEK_SET);
        char buffer[] = {0};
        fwrite(buffer, 1, 1, handle);
        fclose(handle);
    }
}

这与.NET =&gt;中的行为相同它分配一个100MB的文件,用零填充,速度非常快。

现在,当我尝试修改此文件的最后一个字节时:

void main()
{
    int size = 100 * 1024 * 1024 - 1;
    FILE *handle = fopen("blah.datn", "rb+");
    if (handle != NULL) {
        fseek(handle, -1, SEEK_END);
        char buffer[] = {255};
        fwrite(buffer, 1, 1, handle);
        fclose(handle);
    }
}

最后fclose(handle)很慢。我希望有些专家能为此带来一些启示。

似乎使用以前的方法修改真实文件的最后一个字节(非稀疏)非常快。

答案 2 :(得分:3)

使用MemoryMappedFile时使用大文件的最快方法。内存映射文件是一个映射(未加载)到虚拟内存中的文件,因此您可以访问其中的随机字节,而无需寻找特定位置,加载缓冲区等。您还可以直接从文件中读取整个结构而无需进行反序列化。

以下代码直接来自MSDN,在512MB文件的中间加载并存储MyColor结构:

static void Main(string[] args)
{
    long offset = 0x10000000; // 256 megabytes
    long length = 0x20000000; // 512 megabytes

    // Create a memory-mapped view of a portion of 
    // an extremely large image, from the 256th megabyte (the offset)
    // to the 768th megabyte (the offset plus length).
    using (var mmf = 
        MemoryMappedFile.CreateFromFile(@"c:\ExtremelyLargeImage.data",
                                                    FileMode.Open,"ImgA"))
    {
        using (var accessor = mmf.CreateViewAccessor(offset, length))
        {

            int colorSize = Marshal.SizeOf(typeof(MyColor));
            MyColor color;

            // Make changes to the view.
            for (long i = 0; i < length; i += colorSize)
            {
                accessor.Read(i, out color);
                color.Brighten(10);
                accessor.Write(i, ref color);
            }
        }
    }

}

public struct MyColor
{
    public short Red;
    public short Green;
    public short Blue;
    public short Alpha;

    // Make the view brigher.
    public void Brighten(short value)
    {
        Red = (short)Math.Min(short.MaxValue, (int)Red + value);
        Green = (short)Math.Min(short.MaxValue, (int)Green + value);
        Blue = (short)Math.Min(short.MaxValue, (int)Blue + value);
        Alpha = (short)Math.Min(short.MaxValue, (int)Alpha + value);
    }
}

您可以在Memory-Mapped Files

找到更多信息和示例

答案 3 :(得分:2)

我建议您使用真实文件而不是“模拟”文件进行尝试。 可能是.net正在使用一些稀疏分配机制,并且只将文件写出到实际写入的最后一个字节。

因此,当你写入文件的开头时,它只需要写出几个字节,但是当你写到文件的末尾时,它实际上必须写出整个文件。