How to create fast and efficient filestream writes on large sparse files

后端 未结 2 1106
小蘑菇
小蘑菇 2021-02-08 10:09

I have an application that writes large files in multiple segments. I use FileStream.Seek to position each wirte. It appears that when I call FileStream.Write at a deep position

2条回答
  •  暗喜
    暗喜 (楼主)
    2021-02-08 10:49

    Here is some code to use sparse files:

    using System;
    using System.ComponentModel;
    using System.IO;
    using System.Runtime.InteropServices;
    using System.Text;
    using System.Threading;
    
    using Microsoft.Win32.SafeHandles;
    
    public static class SparseFiles
    {
        private const int FILE_SUPPORTS_SPARSE_FILES = 64;
    
        private const int FSCTL_SET_SPARSE = 0x000900c4;
    
        private const int FSCTL_SET_ZERO_DATA = 0x000980c8;
    
        public static void MakeSparse(this FileStream fileStream)
        {
            var bytesReturned = 0;
            var lpOverlapped = new NativeOverlapped();
            var result = DeviceIoControl(
                fileStream.SafeFileHandle, 
                FSCTL_SET_SPARSE, 
                IntPtr.Zero, 
                0, 
                IntPtr.Zero, 
                0, 
                ref bytesReturned, 
                ref lpOverlapped);
    
            if (!result)
            {
                throw new Win32Exception();
            }
        }
    
        public static void SetSparseRange(this FileStream fileStream, long fileOffset, long length)
        {
            var fzd = new FILE_ZERO_DATA_INFORMATION();
            fzd.FileOffset = fileOffset;
            fzd.BeyondFinalZero = fileOffset + length;
            var lpOverlapped = new NativeOverlapped();
            var dwTemp = 0;
    
            var result = DeviceIoControl(
                fileStream.SafeFileHandle, 
                FSCTL_SET_ZERO_DATA, 
                ref fzd, 
                Marshal.SizeOf(typeof(FILE_ZERO_DATA_INFORMATION)), 
                IntPtr.Zero, 
                0, 
                ref dwTemp, 
                ref lpOverlapped);
            if (!result)
            {
                throw new Win32Exception();
            }
        }
    
        public static bool SupportedOnVolume(string filename)
        {
            var targetVolume = Path.GetPathRoot(filename);
            var fileSystemName = new StringBuilder(300);
            var volumeName = new StringBuilder(300);
            uint lpFileSystemFlags;
            uint lpVolumeSerialNumber;
            uint lpMaxComponentLength;
    
            var result = GetVolumeInformationW(
                targetVolume, 
                volumeName, 
                (uint)volumeName.Capacity, 
                out lpVolumeSerialNumber, 
                out lpMaxComponentLength, 
                out lpFileSystemFlags, 
                fileSystemName, 
                (uint)fileSystemName.Capacity);
            if (!result)
            {
                throw new Win32Exception();
            }
    
            return (lpFileSystemFlags & FILE_SUPPORTS_SPARSE_FILES) == FILE_SUPPORTS_SPARSE_FILES;
        }
    
        [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool DeviceIoControl(
            SafeFileHandle hDevice, 
            int dwIoControlCode, 
            IntPtr InBuffer, 
            int nInBufferSize, 
            IntPtr OutBuffer, 
            int nOutBufferSize, 
            ref int pBytesReturned, 
            [In] ref NativeOverlapped lpOverlapped);
    
        [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool DeviceIoControl(
            SafeFileHandle hDevice, 
            int dwIoControlCode, 
            ref FILE_ZERO_DATA_INFORMATION InBuffer, 
            int nInBufferSize, 
            IntPtr OutBuffer, 
            int nOutBufferSize, 
            ref int pBytesReturned, 
            [In] ref NativeOverlapped lpOverlapped);
    
        [DllImport("kernel32.dll", EntryPoint = "GetVolumeInformationW")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool GetVolumeInformationW(
            [In] [MarshalAs(UnmanagedType.LPWStr)] string lpRootPathName, 
            [Out] [MarshalAs(UnmanagedType.LPWStr)] StringBuilder lpVolumeNameBuffer, 
            uint nVolumeNameSize, 
            out uint lpVolumeSerialNumber, 
            out uint lpMaximumComponentLength, 
            out uint lpFileSystemFlags, 
            [Out] [MarshalAs(UnmanagedType.LPWStr)] StringBuilder lpFileSystemNameBuffer, 
            uint nFileSystemNameSize);
    
        [StructLayout(LayoutKind.Sequential)]
        private struct FILE_ZERO_DATA_INFORMATION
        {
            public long FileOffset;
    
            public long BeyondFinalZero;
        }
    }
    

    And sample code to test the above class.

    class Program
    {
        static void Main(string[] args)
        {
            using (var fileStream = new FileStream("test", FileMode.Create, FileAccess.ReadWrite, FileShare.None))
            {
                fileStream.SetLength(1024 * 1024 * 128);
                fileStream.MakeSparse();
                fileStream.SetSparseRange(0, fileStream.Length);
            }
        }
    }
    

    Hope this helps

提交回复
热议问题