我最近用C#写一个山寨fastcopy,原理就是通过给kernal32.dll传参,设定系统的CreatFile函数为不使用系统托管缓存模式,再手动在内存里开辟byte数组作为缓冲区,为了加速,我采用了双重缓冲read和write并发执行的方法:利用互斥锁和信号量使read一号缓冲区时write二号缓冲区这么交替进行(当然在每个缓冲区再没有read完或write完时是不允许write或read的),这些都没有问题,实测能比系统的文件拷贝快20%到30%。
    但是后来我发现,我使用的BinaryReader.Read 方法 (缓冲区, 缓冲区起点, 每次读的长度),再文件不是缓冲区大小的整数倍时会多写数据进去(文件写入收尾时把整个最后缓冲区都写进去了),我试图在最后一次read的时候只read剩下的那一点长度,可是VS2010抛出   “句柄不支持同步操作。可能需要更改 FileStream 构造函数的参数,以指示此句柄是以异步方式打开的(即为重叠 I/O 显式地打开)。”   异常。
    现在我非常确定不是read和write并发执行导致的问题,因为就算我用for循环单缓冲区每次read完成以后再write,最后到文件末尾时还是不能read或者write我想要的长度(只能read或write之前缓冲区的长度)这是我给系统CreatFile传参的代码
using System;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
using System.Threading;namespace SharpCopy
{
    /// <summary>
    /// 手动缓存双缓冲式多线程拷贝
    /// </summary>
    public class FastCopyPrototype
    {
        //下面的常量值是设定CreatFile工作方式的参数
        private const short FILE_ATTRIBUTE_NORMAL = 0x80;//128-The file does not have other attributes set. This attribute is valid only if used alone
        private const short INVALID_HANDLE_VALUE = -1;
        private const uint GENERIC_READ = 0x80000000;//requested access
        private const uint GENERIC_WRITE = 0x40000000;//requested access
        private const uint CREATE_NEW = 1;
        private const uint CREATE_ALWAYS = 2;
        private const uint OPEN_EXISTING = 3;
        private const uint FILE_FLAG_NO_BUFFERING = 0x20000000;//The file or device is being opened with no system caching for data reads and writes. This flag does not affect hard disk caching or memory mapped files.
        private const uint FILE_FLAG_WRITE_THROUGH = 0x80000000;//Write operations will not go through any intermediate cache, they will go directly to disk.
        private const uint FILE_SHARE_READ = 0x00000001;//1-Enables subsequent open operations on a file or device to request read access.
        private const uint FILE_SHARE_WRITE = 0x00000002;//2-Enables subsequent open operations on a file or device to request write access.
        private const uint FILE_FLAG_OVERLAPPED = 0x40000000;//The file or device is being opened or created for asynchronous I/O.        private static bool[] threadlocker = new bool[2];//双缓冲read同步锁
        private static int bufferSize; //= 1024 * 1024 * 48;//独立缓冲区大小为32M
        private  byte[][] buffer = new byte[2][];
        private static bool useBuffer = false;        //需要预判需要操作的缓存数
        private static Int64 len;//获取目标文件长度
        private static Int64 cycle = 0;// R/W轮数
        private static Int64 position = 0;//read同步轮数计数器
        private static Int64 position2 = 0;//write同步轮计数器
        private static Int32 lastBuffer = 0;//同步缓存用的计数器
        private static Int32 lastBufferSize = 0;//释放最后一个缓存用计数器        //使用系统自带的内核动态库最大限度的保证了兼容性,又能在系统自带CreatFile的基础上提高性能
        //经测试win7-SP1 x64下性能比WinXP-SP3 x86下强30%至40%
        //相同系统情况下,比系统自带性能提升约30%
        [DllImport(@"kernel32.dll", SetLastError = true)]
        //原型接口中的DWORD用uint32位代替
        static extern SafeFileHandle CreateFile(
            string lpFileName,//目标文件路径
            uint dwDesiredAccess,//访问目标文件需要的权限
            uint dwShareMode,//文件共享模式
            IntPtr lpSecurityAttributes,//可选安全标示符,决定是否返回的句柄可以通过子进程继承
            uint dwCreationDisposition,//文件打开模式
            uint dwFlagsAndAttributes,//加速的关键参数,操作文件的模式
            IntPtr hTemplateFile//文件模板属性,设置为空
            );        private static FileStream fstreamR;
        private static BinaryReader binR;
        private static FileStream fstreamW;
        private static BinaryWriter binW;        private static CountdownEvent even = new CountdownEvent(1);//用于挂起拷贝主函数的信号量,初始值0,最大值1        public static string sourcepath;
        public static string destpath;        public FastCopyPrototype(string sp,string dp,int bs)
        {
            sourcepath = sp;
            destpath = dp;
            bufferSize = bs;
            buffer[0] = new byte[bufferSize];//划分缓冲区
            buffer[1] = new byte[bufferSize];
            position = 0;
            position2 = 0;
            lastBuffer = 0;
            lastBufferSize = 0;
            even.Reset();//初始化信号量
        }
        
        public void Execute()
        {                         
            threadlocker[0] = true;
            threadlocker[1] = true;            SafeFileHandle fr = CreateFile(
            sourcepath,
            GENERIC_READ,//只用读权限
            FILE_SHARE_READ,//目标文件句柄被释放前只能被其他程序以只读模式访问
            IntPtr.Zero,//NULL
            OPEN_EXISTING,//仅打开存在的文件
            useBuffer ? 0 : (FILE_FLAG_NO_BUFFERING),//读文件时强制系统不用自动缓存
                //因为系统可能会有其他多种dwFlagsAndAttributes,所以用一个始终为false的三目运算符来强制设置
            IntPtr.Zero);            SafeFileHandle fw = CreateFile(
            destpath,
            GENERIC_WRITE,//只用写权限
            FILE_SHARE_READ,
            IntPtr.Zero,
            CREATE_ALWAYS,//创建文件时如果有同名文件则进行覆盖
            useBuffer ? 0 : (FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH),//写文件时采用双模式(不用自动缓存+直接写入模式)来绕过系统自己的缓存加快写文件速度
            IntPtr.Zero);            fstreamR = new FileStream(fr, FileAccess.Read);
            binR = new BinaryReader(fstreamR);
            fstreamW = new FileStream(fw, FileAccess.Write);
            binW = new BinaryWriter(fstreamW);            //现在预判需要操作的缓存数,避免每次循环判断使IO操作进入等待状态,最大限度提高IO速度
            len = fstreamR.Length;
            cycle = len / bufferSize + 1;
            lastBuffer = ((len % bufferSize == 0) ? 0 : 1);
            lastBufferSize = Convert.ToInt32(len % bufferSize);            System.Threading.Thread readThread = new System.Threading.Thread(READ);
            System.Threading.Thread writeThread = new System.Threading.Thread(WRITE);
            //并发执行
            readThread.Start();
            writeThread.Start();            //挂起Execute函数
            even.Wait();
            
            //释放文件句柄
            binR.Close();
            binW.Close();
            fstreamW.Close();
            fstreamR.Close();
            return;
        }        private void READ()
        {
            while (position < cycle)
            {
                if (threadlocker[position % 2])//判断缓冲是否可用
                {                    if (position == cycle - 1)//不够一个缓存的小文件
                    {
                        //bufferSize事先算好,就这里出问题了,不能手动指定最后一次读的长度,write也是同样症状
                        int readCount = binR.Read(buffer[lastBuffer], 0, lastBufferSize);
                        threadlocker[position % 2] = false;
                        return;
                    }
                    else
                    {
                        binR.Read(buffer[position % 2], 0, bufferSize);
                        threadlocker[position % 2] = false;
                        position++;
                    }
                }
                else
                {
                    System.Threading.Thread.Sleep(1);//在read和write速度差不多并且CPU性能较弱时可以防止read线程过分空转浪费系统资源
                }
            }
        }        private void WRITE()
        {
            while (position2 < cycle)
            {
                if (!threadlocker[position2 % 2])
                {
                    if (position2 == cycle - 1)//不够一个缓存的小文件
                    {
                        //bufferSize事先算好
                        binW.Write(buffer[lastBuffer], 0, lastBufferSize);
                        threadlocker[position2 % 2] = true;
                        //弹出信号量解除Execute函数挂起状态
                        FastCopyPrototype.even.Signal();
                        return;
                    }
                    else
                    {
                        binW.Write(buffer[position2 % 2], 0, bufferSize);
                        threadlocker[position2 % 2] = true;
                        position2++;
                    }
                }
                else
                {
                    System.Threading.Thread.Sleep(3);//减少线程过分空转耗尽CPU资源
                }
            }
        }    }
}
求大神解救啊,或者教我一个方法,可以吧二进制文件末尾不要的数据裁掉也可以啊

解决方案 »

  1.   

    我感觉这段代码很牛逼,直到我看见System.Threading.Thread.Sleep(3);
      

  2.   

    那个是提高性能用的,特殊情况下read比write快太多,不加的话会使线程里面while()循环过度空转耗费CPU资源。
      

  3.   

    BinaryReader binR;这个类的Read方法如果文件尾不够缓存的大小的话,应该最后面的数据都是byte b=0;Read的时候还是给缓存的长度,只不过最后的一次读取你要知道你的文件流的有效数据是到哪
      

  4.   

        现在问题就纠结到这里了,虽然read到最后文件尾巴的时候缓存后面全是0x0,可是write的时候C#也把这些0x0给写到文件里面去了结果就导致复制出的文件总比原来大那么一点,视频图片还好,遇到exe RAR这种使用时需要校验的文件马上报错。
         现在我想用文件映射的方法修改文件大小(流的话要重新写一遍文件,太慢了),把尾巴上多出来的一点裁掉,可是C#文件映射的资料太少了不知道哪位大神能给我个裁剪文件的示例。
      

  5.   

                          //bufferSize事先算好,就这里出问题了,不能手动指定最后一次读的长度,write也是同样症状
                            int readCount = binR.Read(buffer[lastBuffer], 0, lastBufferSize);变通:
    在最后一次读之前先另外定义一个 buffer变量 = new byte[lastBufferSize];
    把最后内容读入此buffer变量.
    然后int readCount = binR.Read(buffer变量0, lastBufferSize);
      

  6.   


    SafeFileHandle fr = CreateFile(
          sourcepath,
          GENERIC_READ,//只用读权限
          FILE_SHARE_READ,//目标文件句柄被释放前只能被其他程序以只读模式访问
          IntPtr.Zero,//NULL
          OPEN_EXISTING,//仅打开存在的文件
          useBuffer ? 0 : (FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED),//读文件时强制系统不用自动缓存
            //因为系统可能会有其他多种dwFlagsAndAttributes,所以用一个始终为false的三目运算符来强制设置
          IntPtr.Zero);      SafeFileHandle fw = CreateFile(
          destpath,
          GENERIC_WRITE,//只用写权限
          FILE_SHARE_READ,
          IntPtr.Zero,
          CREATE_ALWAYS,//创建文件时如果有同名文件则进行覆盖
          useBuffer ? 0 : (FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH | FILE_FLAG_OVERLAPPED),//写文件时采用双模式(不用自动缓存+直接写入模式)来绕过系统自己的缓存加快写文件速度
          IntPtr.Zero);      fstreamR = new FileStream(fr, FileAccess.Read, 8,true);
          binR = new BinaryReader(fstreamR);
          fstreamW = new FileStream(fw, FileAccess.Write, 8, true);
          binW = new BinaryWriter(fstreamW);
    这样
      

  7.   

    这样也不对  这坑爹的异常信息  .net把所有的参数错误都抛出这个异常
    设置文件大小的话看看SetFileValidData和SetEndOfFile函数行不行
      

  8.   

    找到问题症结了,是我自己双缓冲read和write的同步互斥没处理好,等下我把能用代码贴出来
      

  9.   

    最笨的办法,文件长度求模除以FOR中那个I剩下多少,再减减映射确实快,就是整块内存的序列化,都不用sleep,while
      

  10.   

    你的 readCount 干什么用呢?整个程序中,没有见你用过。而用这个是非常重要的。假设buffer有10000个单元,而readCount等于9900,难道你后边还假设自己读取了10000个单元的数据?
      

  11.   

    readcount是记录read到的字节数,但是我的write方法如果指定最后只写入readcount个字节就会抛异常,只能把buffer全部写完
      

  12.   

    问题不是那样的,根本原因在于使用了FILE_FLAG_NO_BUFFERING 。这样一来读写只能是扇区字节的整数倍(512X),如果你的文件大小不是512的整数倍就会出现多读的情况,写的时候自然就出错了。即使你写入readCount个字节也一样。