Stream篇(四)

FileStream

如何去理解FileStream?

通过前3章的学习相信大家对于Stream已经有一定的了解,但是又如何去理解FileStream呢?请看下图   
这里写图片描述
我们磁盘的中任何文件都是通过2进制组成,最为直观的便是记事本了,当我们新建一个记事本时,它的大小是0KB, 我们每次输入一个数字或

字母时文件便会自动增大4kb,可见随着我们输入的内容越来越多,文件也会相应增大,同理当我们删除文件内容时,文件也会相应减小,对了,

聪明的你肯定会问:谁将内容以怎么样的形式放到文件中去了?好问题,还记得第一篇流的概念么?对了,真实世界的一群鱼可以通过河流来

往于各个地方,FileStream也是一样,byte可以通过FileStream进行传输,这样我们便能在计算机上对任何文件进行一系列的操作了。

FileStream 的重要性

FileStream 顾名思义文件流,我们电脑上的文件都可以通过文件流进行操作,例如文件的复制,剪切,粘贴,删除, 本地文件上传,下载,等许

多重要的功能都离不开文件流,所以文件流不仅在本机上非常重要,在如今的网络世界也是万万不能缺少的,想象一下我们开启虚机后,直接从本

地复制一个文件到虚机上,是多么方便,如果没有文件流,这个将难以想象。(大家别误解,文件流无法直接通过网络进行传输,而是

通过网络流将客户端上传的文件传到服务器端接收,然后通过文件流进行处理,下载正好相反)

FileStream 常用构造函数介绍(可能理解上有点复杂,请大家务必深刻理解)

*1: FileStream(SafeFileHandle, FileAccess)

           非托管参数SafeFileHandle简单介绍

                       SafeFileHandle :是一个文件安全句柄,这样的解释可能大家一头雾水,
  • 1
  • 2
  • 3
  • 4

别急,大家先不要去理睬这深邃的含义,只要知道这个类型是c#非托管资源,

也就是说它能够调用非托管资源的方法,而且不属于c#回收机制,所以我们必须

使用GC手动或其他方式(Finalize 或Dispose方法)进行非托管资源的回收,所以

SafeFileHandle是个默默无闻的保镖 ,一直暗中保护FileStream和文件的安全

为了能让大家更好的理解这个保镖,请看第一段代码: 
这里写图片描述
会什么会报错呢?其实程序被卡在 Console.ReadLine()这里,FileStream并没有

被释放,系统不知道这个文件是否还有用﹐所以帮我们保护这个文件

(那个非托管资源SafeFileHandle所使用的内存还被程序占用着)

所以SafeFileHandled 在内部保护了这个文件从而报出了这个异常

这里写图片描述
可以看见stream.SafeFileHandle的IsClose属性变成true了,也就是说这时候可以安全的删除文件了

所以又回到了一个老问题上面,我们每次使用完FileStream后都必须将他关闭并释放资源 
*2: FileStream(String, FileMode)

String 参数表示文件所在的地址,FIleMode是个枚举,表示确定如何打开或创建文件。

FileMode枚举参数包含以下内容: 
这里写图片描述
这里写图片描述

参数ownsHandle:也就是类似于前面和大家介绍的SafeFileHandler,有2点必须注意:

  1对于指定的文件句柄,操作系统不允许所请求的 access,例如,当 access 为 Write 或 ReadWrite 而文件句柄设置为只读访问时,会报出异常。

   所以 ownsHandle才是老大,FileAccess的权限应该在ownsHandle的范围之内

        2. FileStream 假定它对句柄有独占控制权。当 FileStream 也持有句柄时,读取、写入或查找可能会导致数据破坏。为了数据的安全,请使用

        句柄前调用 Flush,并避免在使用完句柄后调用 Close 以外的任何方法。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

这里写图片描述
*5: FileStream(String, FileMode, FileAccess, FileShare, Int32, Boolean async )

Int32:这是一个缓冲区的大小,大家可以按照自己的需要定制,

Boolean async:是否异步读写,告诉FileStream示例,是否采用异步读写

*6: FileStream(String, FileMode, FileAccess, FileShare, Int32, FileOptions)

  FileOptions:这是类似于FileStream对于文件操作的高级选项
  • 1
  • 2

FileStream 常用属性介绍

*1:CanRead :指示FileStream是否可以读操作

*2:CanSeek:指示FileStream是否可以跟踪查找流操作

*3:IsAsync:FileStream是否同步工作还是异步工作

*4:Name:FileStream的名字 只读属性

*5:ReadTimeout :设置读取超时时间

*6:SafeFileHandle : 文件安全句柄 只读属性

*7:position:当前FileStream所在的流位置

FileStream 常用方法介绍

以下方法重写了Stream的一些虚方法(**这里大家点击这里可以参考第一篇来温故下,这里不再叙述)

1:IAsyncResult BeginRead 异步读取

2:IAsyncResult BeginWrite 异步写

3:void Close 关闭当前FileStream

4:void EndRead 异步读结束

5:void EndWrite 异步写结束

6:void Flush 立刻释放缓冲区,将数据全部导出到基础流(文件中)

7:int Read 一般读取

8:int ReadByte 读取单个字节

9:long Seek 跟踪查找流所在的位置

10:void SetLength 设置FileStream的长度

11:void Write 一般写

12:void WriteByte写入单个字节

属于FileStream独有的方法

*1:FileSecurity GetAccessControl()

这个不是很常用,FileSecurity 是文件安全类,直接表达当前文件的访问控制列表(ACL)的符合当前文件权限的项目,ACL大家有个了解就行,以后会单独和大家讨论下ACL方面的知识

*2: void Lock(long position,long length)

这个Lock方法和线程中的Look关键字很不一样,它能够锁住文件中的某一部分,非常的强悍!用了这个方法我们能够精确锁定住我们需要锁住的文件的部分内容

*3: void SetAccessControl(FileSecurity fileSecurity)

和GetAccessControl很相似,ACL技术会在以后单独介绍

*4: void Unlock (long position,long length)

正好和lock方法相反,对于文件部分的解锁

文件的新建和拷贝(主要演示文件同步和异步操作)

首先我们尝试DIY一个IFileConfig

     /// <summary>
    /// 文件配置接口
    /// </summary>
    public interface IFileConfig
    {
        string FileName { get; set; }
        bool IsAsync { get; set; }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

创建文件配置类CreateFileConfig,用于添加文件一些配置设置,实现添加文件的操作

/// <summary>
    /// 创建文件配置类
    /// </summary>
    public class CreateFileConfig : IFileConfig
    {
        // 文件名
        public string FileName { get; set; }
        //是否异步操作
        public bool IsAsync { get; set; }
        //创建文件所在url
        public string CreateUrl { get; set; }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

让我们定义一个文件流测试类:FileStreamTest 来实现文件的操作

  /// <summary>
    /// FileStreamTest 类
    /// </summary>
    public class FileStreamTest
  • 1
  • 2
  • 3
  • 4
  • 5

在该类中实现一个简单的Create方法用来同步或异步的实现添加文件,FileStream会根据配置类去选择相应的构造函数,实现异步或同步的添加方式

/// <summary>
        /// 添加文件方法
        /// </summary>
        /// <param name="config"> 创建文件配置类</param>
        public void Create(IFileConfig config)
        {
            lock (_lockObject)
            {
                //得到创建文件配置类对象
                var createFileConfig = config as CreateFileConfig;
                //检查创建文件配置类是否为空
                if (this.CheckConfigIsError(config)) return;
                //假设创建完文件后写入一段话,实际项目中无需这么做,这里只是一个演示
                char[] insertContent = "HellowWorld".ToCharArray();
                //转化成 byte[]
                byte[] byteArrayContent = Encoding.Default.GetBytes(insertContent, 0, insertContent.Length);
                 //根据传入的配置文件中来决定是否同步或异步实例化stream对象
                FileStream stream = createFileConfig.IsAsync ?
                    new FileStream(createFileConfig.CreateUrl, FileMode.Create, FileAccess.ReadWrite, FileShare.None, 4096, true)
                    : new FileStream(createFileConfig.CreateUrl, FileMode.Create);
                using (stream)
                {
                    // 如果不注释下面代码会抛出异常,google上提示是WriteTimeout只支持网络流
                   // stream.WriteTimeout = READ_OR_WRITE_TIMEOUT;
                    //如果该流是同步流并且可写
                    if (!stream.IsAsync && stream.CanWrite)
                        stream.Write(byteArrayContent, 0, byteArrayContent.Length);
                    else if (stream.CanWrite)//异步流并且可写
                        stream.BeginWrite(byteArrayContent, 0, byteArrayContent.Length, this.End_CreateFileCallBack, stream);

                    stream.Close();
                }
            }
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

如果采用异步的方式则最后会进入End_CreateFileCallBack回调方法,result.AsyncState对象就是上图stream.BeginWrite()方法的最后一个参数

还有一点必须注意的是每一次使用BeginWrite()方法事都要带上EndWrite()方法,Read方法也一样

/// <summary>
        ///  异步写文件callBack方法
        /// </summary>
        /// <param name="result">IAsyncResult</param>
        private void End_CreateFileCallBack(IAsyncResult result)
        {
            //从IAsyncResult对象中得到原来的FileStream
            var stream = result.AsyncState as FileStream;
            //结束异步写

            Console.WriteLine("异步创建文件地址:{0}", stream.Name);
            stream.EndWrite(result);
            Console.ReadLine();
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

文件复制的方式思路比较相似,首先定义复制文件配置类,由于在异步回调中用到该配置类的属性,所以新增了文件流对象和相应的字节数组

/// <summary>
    /// 文件复制
    /// </summary>
    public class CopyFileConfig : IFileConfig
    {
        // 文件名
        public string FileName { get; set; }
        //是否异步操作
        public bool IsAsync { get; set; }
        //原文件地址
        public string OrginalFileUrl { get; set; }
        //拷贝目的地址
        public string DestinationFileUrl { get; set; }
        //文件流,异步读取后在回调方法内使用
        public FileStream OriginalFileStream { get; set; }
        //原文件字节数组,异步读取后在回调方法内使用
        public byte[] OriginalFileBytes { get; set; }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

然后在FileStreamTest 类中新增一个Copy方法实现文件的复制功能

/// <summary>
        /// 复制方法
        /// </summary>
        /// <param name="config">拷贝文件复制</param>
        public void Copy(IFileConfig config)
        {
            lock (_lockObject)
            {
                //得到CopyFileConfig对象
                var copyFileConfig = config as CopyFileConfig;
                // 检查CopyFileConfig类对象是否为空或者OrginalFileUrl是否为空
                if (CheckConfigIsError(copyFileConfig) || !File.Exists(copyFileConfig.OrginalFileUrl)) return;
                //创建同步或异步流
                FileStream stream = copyFileConfig.IsAsync ?
                 new FileStream(copyFileConfig.OrginalFileUrl, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true)
                 : new FileStream(copyFileConfig.OrginalFileUrl, FileMode.Open);
                //定义一个byte数组接受从原文件读出的byte数据
                byte[] orignalFileBytes = new byte[stream.Length];
                using (stream)
                {
                   // stream.ReadTimeout = READ_OR_WRITE_TIMEOUT;
                    //如果异步流
                    if (stream.IsAsync)
                    {
                        //将该流和读出的byte[]数据放入配置类,在callBack中可以使用
                        copyFileConfig.OriginalFileStream = stream;
                        copyFileConfig.OriginalFileBytes = orignalFileBytes;
                        if (stream.CanRead)
                         //异步开始读取,读完后进入End_ReadFileCallBack方法,该方法接受copyFileConfig参数
                        stream.BeginRead(orignalFileBytes, 0, orignalFileBytes.Length, End_ReadFileCallBack, copyFileConfig);
                    }
                    else//否则同步读取
                    {
                        if (stream.CanRead)
                        {
                            //一般读取原文件
                            stream.Read(orignalFileBytes, 0, orignalFileBytes.Length);
                        }
                        //定义一个写流,在新位置中创建一个文件
                        FileStream copyStream = new FileStream(copyFileConfig.DestinationFileUrl, FileMode.CreateNew);
                        using (copyStream)
                        {
                          //  copyStream.WriteTimeout = READ_OR_WRITE_TIMEOUT;
                            //将源文件的内容写进新文件
                            copyStream.Write(orignalFileBytes, 0, orignalFileBytes.Length);
                            copyStream.Close();
                        }
                    }
                    stream.Close();
                    Console.ReadLine();
                }
            }


        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

最后,如果采用异步的方式,则会进入End_ReadFileCallBack回调函数进行异步读取和异步写操作

/// <summary>
        /// 异步读写文件方法
        /// </summary>
        /// <param name="result"></param>
        private void End_ReadFileCallBack(IAsyncResult result) 
        {
            //得到先前的配置文件
            var config = result.AsyncState as CopyFileConfig;
            //结束异步读
            config.OriginalFileStream.EndRead(result);
            //异步读后立即写入新文件地址
            if (File.Exists(config.DestinationFileUrl)) File.Delete(config.DestinationFileUrl);
            FileStream copyStream = new FileStream(config.DestinationFileUrl, FileMode.CreateNew);
            using (copyStream)
            {
                Console.WriteLine("异步复制原文件地址:{0}", config.OriginalFileStream.Name);
                Console.WriteLine("复制后的新文件地址:{0}", config.DestinationFileUrl);
                //调用异步写方法CallBack方法为End_CreateFileCallBack,参数是copyStream
                copyStream.BeginWrite(config.OriginalFileBytes, 0, config.OriginalFileBytes.Length, this.End_CreateFileCallBack,copyStream);
                copyStream.Close();

            }

        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

最后让我们在main函数调用下:

static void Main(string[] args)
        {
            FileStreamTest test = new FileStreamTest();
            //创建文件配置类
            CreateFileConfig createFileConfig = new CreateFileConfig { CreateUrl = @"d:\MyFile.txt", IsAsync = true };
            //复制文件配置类
            CopyFileConfig copyFileConfig = new CopyFileConfig
            {
                OrginalFileUrl = @"d:\8.jpg",
                DestinationFileUrl = @"d:\9.jpg",
                IsAsync = true
            };
            test.Create(createFileConfig);
            test.Copy(copyFileConfig);
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

这里写图片描述

这里写图片描述

/// <summary>
    /// 分段上传例子
    /// </summary>
    public class UpFileSingleTest
    {
        //我们定义Buffer为1000
        public const int BUFFER_COUNT = 1000;

        /// <summary>
        /// 将文件上传至服务器(本地),由于采取分段传输所以,
        /// 每段必须有一个起始位置和相对应该数据段的数据
        /// </summary>
        /// <param name="filePath">服务器上文件地址</param>
        /// <param name="startPositon">分段起始位置</param>
        /// <param name="btArray">每段的数据</param>
        private void WriteToServer(string filePath,int startPositon,byte[] btArray) 
        {
            FileStream fileStream = new FileStream(filePath, FileMode.OpenOrCreate);
            using (fileStream) 
            {
                //将流的位置设置在该段起始位置
                fileStream.Position = startPositon;
                //将该段数据通过FileStream写入文件中,每次写一段的数据,就好比是个水池,分段蓄水一样,直到蓄满为止
                fileStream.Write(btArray, 0, btArray.Length);
            }
        }


        /// <summary>
        /// 处理单独一段本地数据上传至服务器的逻辑,根据客户端传入的startPostion
        /// 和totalCount来处理相应段的数据上传至服务器(本地)
        /// </summary>
        /// <param name="localFilePath">本地需要上传的文件地址</param>
        /// <param name="uploadFilePath">服务器(本地)目标地址</param>
        /// <param name="startPostion">该段起始位置</param>
        /// <param name="totalCount">该段最大数据量</param>
        public void UpLoadFileFromLocal(string localFilePath,string uploadFilePath,int startPostion,int totalCount) 
        {
            //if(!File.Exists(localFilePath)){return;}
           //每次临时读取数据数
            int tempReadCount = 0;
            int tempBuffer = 0;
            //定义一个缓冲区数组
            byte[] bufferByteArray = new byte[BUFFER_COUNT];
            //定义一个FileStream对象
            FileStream fileStream = new FileStream(localFilePath,FileMode.Open);
            //将流的位置设置在每段数据的初始位置
            fileStream.Position = startPostion;
            using (fileStream)
            {
                //循环将该段数据读出在写入服务器中
                while (tempReadCount < totalCount)
                {

                    tempBuffer = BUFFER_COUNT;
                    //每段起始位置+每次循环读取数据的长度
                    var writeStartPosition = startPostion + tempReadCount;
                    //当缓冲区的数据加上临时读取数大于该段数据量时,
                    //则设置缓冲区的数据为totalCount-tempReadCount 这一段的数据
                    if (tempBuffer + tempReadCount > totalCount) 
                    {
                        //缓冲区的数据为totalCount-tempReadCount 
                        tempBuffer = totalCount-tempReadCount;
                        //读取该段数据放入bufferByteArray数组中
                        fileStream.Read(bufferByteArray, 0, tempBuffer);
                        if (tempBuffer > 0) 
                        {
                            byte[] newTempBtArray = new byte[tempBuffer];
                            Array.Copy(bufferByteArray, 0, newTempBtArray, 0, tempBuffer);
                            //将缓冲区的数据上传至服务器
                            this.WriteToServer(uploadFilePath, writeStartPosition, newTempBtArray);
                        }

                    }
                    //如果缓冲区的数据量小于该段数据量,并且tempBuffer=设定BUFFER_COUNT时,通过
                    //while 循环每次读取一样的buffer值的数据写入服务器中,直到将该段数据全部处理完毕
                    else if (tempBuffer == BUFFER_COUNT) 
                    {
                        fileStream.Read(bufferByteArray, 0, tempBuffer);
                        this.WriteToServer(uploadFilePath, writeStartPosition, bufferByteArray);
                    }

                    //通过每次的缓冲区数据,累计增加临时读取数
                    tempReadCount += tempBuffer;
                }
            }
        }

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89

一切准备就绪,我们剩下的就是将文件切成几段进行上传了

static void Main(string[] args)
        {
            UpFileSingleTest test=new UpFileSingleTest();
            FileInfo info = new FileInfo(@"G:\\Skyrim\20080204173728108.torrent");
            //取得文件总长度
            var fileLegth = info.Length;
            //假设将文件切成5段
            var divide = 5;
            //取到每个文件段的长度
            var perFileLengh = (int)fileLegth / divide;
            //表示最后剩下的文件段长度比perFileLengh小
            var restCount = (int)fileLegth % divide;
            //循环上传数据
            for (int i = 0; i < divide+1; i++)
            {
                //每次定义不同的数据段,假设数据长度是500,那么每段的开始位置都是i*perFileLength
                var startPosition = i * perFileLengh;
                //取得每次数据段的数据量
                var totalCount = fileLegth - perFileLengh * i > perFileLengh ? perFileLengh : (int)(fileLegth - perFileLengh * i);
                //上传该段数据
                test.UpLoadFileFromLocal(@"G:\\Skyrim\\20080204173728108.torrent", @"G:\\Skyrim\\20080204173728109.torrent", startPosition, i == divide ? divide : totalCount);
            }

        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

这里写图片描述

http://www.cnblogs.com/JimmyZheng/archive/2012/04/03/2430585.html



from:http://blog.csdn.net/younghaiqing/article/details/54729606




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值