逆时针の风 博客园 首页 博问 闪存 新随笔 联系 订阅 管理 随笔-24 文章-0 评论-394 C# 温故而知新:Stream篇 (四) C# 温故而知新:Stream篇(四) FileStream 目录: 如何去理解FileStream? FileStream的重要性 FileStream常用构造函数(重要) 非托管参数SafeFileHandle简单介绍 FileStream常用属性介绍 FileStream常用方法介绍 FileStream示例1:*文件的新建和拷贝(主要演示文件同步和异步操作) FileStream示例2:*实现文件本地分段上传 本章总结 如何去理解FileStream? 通过前3章的学习相信大家对于Stream已经有一定的了解,但是又如何去理解FileStream呢?请看下图 我们磁盘的中任何文件都是通过2进制组成,最为直观的便是记事本了,当我们新建一个记事本时,它的大小是0KB, 我们每次输入一个数字或 字母时文件便会自动增大4kb,可见随着我们输入的内容越来越多,文件也会相应增大,同理当我们删除文件内容时,文件也会相应减小,对了, 聪明的你肯定会问:谁将内容以怎么样的形式放到文件中去了?好问题,还记得第一篇流的概念么?对了,真实世界的一群鱼可以通过河流来 往于各个地方,FileStream也是一样,byte可以通过FileStream进行传输,这样我们便能在计算机上对任何文件进行一系列的操作了。 FileStream 的重要性 FileStream 顾名思义文件流,我们电脑上的文件都可以通过文件流进行操作,例如文件的复制,剪切,粘贴,删除, 本地文件上传,下载,等许 多重要的功能都离不开文件流,所以文件流不仅在本机上非常重要,在如今的网络世界也是万万不能缺少的,想象一下我们开启虚机后,直接从本 地复制一个文件到虚机上,是多么方便,如果没有文件流,这个将难以想象。(大家别误解,文件流无法直接通过网络进行传输,而是 通过网络流将客户端上传的文件传到服务器端接收,然后通过文件流进行处理,下载正好相反) FileStream 常用构造函数介绍(可能理解上有点复杂,请大家务必深刻理解) *1: FileStream(SafeFileHandle, FileAccess) 非托管参数SafeFileHandle简单介绍 SafeFileHandle :是一个文件安全句柄,这样的解释可能大家一头雾水, 别急,大家先不要去理睬这深邃的含义,只要知道这个类型是c#非托管资源, 也就是说它能够调用非托管资源的方法,而且不属于c#回收机制,所以我们必须 使用GC手动或其他方式(Finalize 或Dispose方法)进行非托管资源的回收,所以 SafeFileHandle是个默默无闻的保镖 ,一直暗中保护FileStream和文件的安全 为了能让大家更好的理解这个保镖,请看第一段代码: 会什么会报错呢?其实程序被卡在 Console.ReadLine()这里,FileStream并没有 被释放,系统不知道这个文件是否还有用﹐所以帮我们保护这个文件 (那个非托管资源SafeFileHandle所使用的内存还被程序占用着) 所以SafeFileHandled 在内部保护了这个文件从而报出了这个异常 如果我们将流关闭后,这个问题也就不存在了 可以看见stream.SafeFileHandle的IsClose属性变成true了,也就是说这时候可以安全的删除文件了 所以又回到了一个老问题上面,我们每次使用完FileStream后都必须将他关闭并释放资源 *2: FileStream(String, FileMode) String 参数表示文件所在的地址,FIleMode是个枚举,表示确定如何打开或创建文件。 FileMode枚举参数包含以下内容: 成员名称 说明 Append 打开现有文件并查找到文件尾,或创建新文件。FileMode.Append 只能同 FileAccess.Write 一起使用。 Create 指定操作系统应创建新文件。如果文件已存在,它将被改写。这要求 FileIOPermissionAccess.Write。 System.IO.FileMode.Create 等效于这样的请求:如果文件不存在,则使用 CreateNew;否则使用 Truncate。 CreateNew 指定操作系统应创建新文件。此操作需要 FileIOPermissionAccess.Write。如果文件已存在,则将引发 IOException。 Open 指定操作系统应打开现有文件。打开文件的能力取决于 FileAccess 所指定的值。如果该文件不存在, 则引发 System.IO.FileNotFoundException。 OpenOrCreate 指定操作系统应打开文件(如果文件存在);否则,应创建新文件。如果用 FileAccess.Read 打开文件,则需要 FileIOPermissionAccess.Read。如果文件访问为 FileAccess.Write 或 FileAccess.ReadWrite,则需要 FileIOPermissionAccess.Write。如果文件访问为 FileAccess.Append,则需要 FileIOPermissionAccess.Append。 Truncate 指定操作系统应打开现有文件。文件一旦打开,就将被截断为零字节大小。此操作需要 FileIOPermissionAccess.Write。 试图从使用 Truncate 打开的文件中进行读取将导致异常。 *3: FileStream(IntPtr, FileAccess, Boolean ownsHandle) FileAccess 参数也是一个枚举, 表示对于该文件的操作权限 ReadWrite 对文件的读访问和写访问。可从文件读取数据和将数据写入文件 Write 文件的写访问。可将数据写入文件。同 Read组合即构成读/写访问权 Read 对文件的读访问。可从文件中读取数据。同 Write组合即构成读写访问权 参数ownsHandle:也就是类似于前面和大家介绍的SafeFileHandler,有2点必须注意: 1对于指定的文件句柄,操作系统不允许所请求的 access,例如,当 access 为 Write 或 ReadWrite 而文件句柄设置为只读访问时,会报出异常。 所以 ownsHandle才是老大,FileAccess的权限应该在ownsHandle的范围之内 2. FileStream 假定它对句柄有独占控制权。当 FileStream 也持有句柄时,读取、写入或查找可能会导致数据破坏。为了数据的安全,请使用 句柄前调用 Flush,并避免在使用完句柄后调用 Close 以外的任何方法。 *4: FileStream(String, FileMode, FileAccess, FileShare) FileShare:同样是个枚举类型:确定文件如何由进程共享。 Delete 允许随后删除文件。 Inheritable 使文件句柄可由子进程继承。Win32 不直接支持此功能。 None 谢绝共享当前文件。文件关闭前,打开该文件的任何请求(由此进程或另一进程发出的请求)都将失败。 Read 允许随后打开文件读取。如果未指定此标志,则文件关闭前,任何打开该文件以进行读取的请求(由此进程或另一进程发出的请求)都将失败。但是,即使指定了此标志,仍可能需要附加权限才能够访问该文件。 ReadWrite 允许随后打开文件读取或写入。如果未指定此标志,则文件关闭前,任何打开该文件以进行读取或写入的请求(由此进程或另一进程发出)都将失败。但是,即使指定了此标志,仍可能需要附加权限才能够访问该文件。 Write 允许随后打开文件写入。如果未指定此标志,则文件关闭前,任何打开该文件以进行写入的请求(由此进程或另一进过程发出的请求)都将失败。但是,即使指定了此标志,仍可能需要附加权限才能够访问该文件。 *5: FileStream(String, FileMode, FileAccess, FileShare, Int32, Boolean async ) Int32:这是一个缓冲区的大小,大家可以按照自己的需要定制, Boolean async:是否异步读写,告诉FileStream示例,是否采用异步读写 *6: FileStream(String, FileMode, FileAccess, FileShare, Int32, FileOptions) FileOptions:这是类似于FileStream对于文件操作的高级选项 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; } } 创建文件配置类CreateFileConfig,用于添加文件一些配置设置,实现添加文件的操作 /// <summary> /// 创建文件配置类 /// </summary> public class CreateFileConfig : IFileConfig { // 文件名 public string FileName { get; set; } //是否异步操作 public bool IsAsync { get; set; } //创建文件所在url public string CreateUrl { get; set; } } 让我们定义一个文件流测试类:FileStreamTest 来实现文件的操作 /// <summary> /// FileStreamTest 类 /// </summary> public class FileStreamTest在该类中实现一个简单的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(); } } } 如果采用异步的方式则最后会进入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(); } 文件复制的方式思路比较相似,首先定义复制文件配置类,由于在异步回调中用到该配置类的属性,所以新增了文件流对象和相应的字节数组 /// <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; } } 然后在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(); } } } 最后,如果采用异步的方式,则会进入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(); } } 最后让我们在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); } 输出结果: 实现文件本地分段上传 上面的例子是将一个文件作为整体进行操作,这样会带来一个问题,当文件很大或者网络不是很稳定的时候会发生意想不到的错误 那我们该怎么解决这一问题呢?其实有种思路还是不错的,那就是分段传输: 那就DIY一个简单的分段传输的例子,我们先将处理每一段的逻辑先整理好 /// <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; } } } } 一切准备就绪,我们剩下的就是将文件切成几段进行上传了 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); } } 上传结果: 总的来说,分段传输比直接传输复杂许多,我会在今后的例子中加入多线程,这样的话每段数据的传输都能通过一个线程单独处理,能够提升上传性能和速度 本章总结 本章介绍了Stream中最关键的派生类FileStream的概念,属性,方法,构造函数等重要的概念,包括一些难点和重要点都一一列举出来,最后2个例子让大家在温故下 FileStream的使用方法,包括FileStream异步同步操作和分段传输操作。 如果大家喜欢我的文章,请大家多多关注下,下一章将会介绍MemoryStream,敬请期待! 分类: c# 绿色通道: 好文要顶 关注我 收藏该文与我联系 逆时针の风 关注 - 14 粉丝 - 589 荣誉:推荐博客 +加关注 30 0 (请您对文章做出评价) « 上一篇:C# 温故而知新:Stream篇(三) » 下一篇:C# 温故而知新:Stream篇(五) posted @ 2012-04-03 05:28 逆时针の风 阅读(8799) 评论(28) 编辑 收藏 评论列表 回复引用 #1楼 2012-04-03 07:52 yhexie stream对象和reader、writer对象之间是什么关系? 支持(0)反对(0) 回复引用 #2楼 2012-04-03 10:28 Fishboy 学习之 顺便问下LZ的英文字体是什么 支持(0)反对(0) 回复引用 #3楼 2012-04-03 10:50 海洋之 心 学习下吧 支持(0)反对(0) 回复引用 #4楼[楼主] 2012-04-03 13:32 逆时针の风 @Fishboy 谢谢支持!英文字体是Calibri (Body) 支持(0)反对(0) 回复引用 #5楼[楼主] 2012-04-03 13:45 逆时针の风 @yhexie 其实最关键的区别就是Stream我们一般操作的是字节 byte或字节数组,而Reader 或者Writer因为其父类TextReader 或者TextReader的关系,所以直接操作的是字符char或字符数组,如果要更精确的操作文件的话还是选择FileStream比较好 支持(0)反对(0) 回复引用 #6楼 2012-04-05 10:03 雨丸 谢谢分享 支持(0)反对(0) 回复引用 #7楼 2012-04-05 11:11 若木 表示刚进来看到天际的图片还以为进了3DM了。。。 写的很细,谢谢分享 支持(0)反对(0) 回复引用 #8楼 2012-04-05 11:25 今昭 玩天际的飘过~ 支持(0)反对(0) 回复引用 #9楼 2012-04-05 13:37 小小4 谢谢分享 支持(0)反对(0) 回复引用 #10楼 2012-04-05 15:22 匹配度 呵呵。。。 支持(0)反对(0) 回复引用 #11楼 2012-04-05 16:55 拉拉叟 赞一个 支持(0)反对(0) 回复引用 #12楼[楼主] 2012-04-05 23:56 逆时针の风 @雨丸 感谢支持! 支持(0)反对(0) 回复引用 #13楼[楼主] 2012-04-05 23:56 逆时针の风 感谢大家支持! 支持(0)反对(0) 回复引用 #14楼[楼主] 2012-04-06 00:04 逆时针の风 @若木 感谢支持,话说这个游戏不错 支持(0)反对(0) 回复引用 #15楼 2012-04-11 11:02 无名小碑 以前对这个Stream一窍不通,最近因为工作和个人需要,学习了一把,也算入门了。话说,楼主玩的这个上古卷轴天际,我也在玩,做了主线和大部分支线任务,就没追求了,喜欢3DM玲大的衣服。 支持(0)反对(0) 回复引用 #16楼 2012-04-15 08:05 文杰· 楼主 你好 首先非常感谢你这篇文章 照着你的例子写,报了很多错误。 我想你能不能发一份源码给我 我来参考一下。 我的邮箱86275954@qq.com 支持(0)反对(0) 回复引用 #17楼[楼主] 2012-04-15 18:57 逆时针の风 @文杰· 没关系,报了什么错呢?已经发给你了,如果你觉的麻烦可以Q聊:332829544 支持(0)反对(0) 回复引用 #18楼 2012-05-03 16:03 Richeir 果然 温故而知新,可以为师矣 期待更多的温故系列~ 支持(0)反对(0) 回复引用 #19楼 2012-06-10 16:08 高飞的鹰 @逆时针の风 引用@yhexie 其实最关键的区别就是Stream我们一般操作的是字节 byte或字节数组,而Reader 或者Writer因为其父类TextReader 或者TextReader的关系,所以直接操作的是字符char或字符数组,如果要更精确的操作文件的话还是选择FileStream比较好 可不可以这样理解:StreamReader和StreamWriter内部还是在操作传进去的流,不过是把流的字节数组形式变成了字符数组形式在进行读写操作。 支持(0)反对(0) 回复引用 #20楼[楼主] 2012-06-16 00:16 逆时针の风 @高飞的鹰 可以这么理解 支持(0)反对(0) 回复引用 #21楼 2012-07-27 11:09 NET求知 温故而知新,拜读了楼主的美文,尊称楼主一声,师傅辛苦了! 支持(0)反对(0) 回复引用 #22楼 2012-07-27 14:15 NET求知 想问请教一下楼主,我上传多个文件,其中有小到几M的图片,大的有200M这样,然后用你这儿分块上传的方法,花了15秒;修改一下,把BUFFER_COUNT改为100,花了一分多钟,改为10000,反而快了,后来干脆采用每个文件都一次传完,就是直接用你的WriteToServer方法一次上传,发现不到一秒搞定。我这是在本地调试,也没用到多线程。请分析一下为什么. 支持(0)反对(0) 回复引用 #23楼[楼主] 2012-07-30 01:12 逆时针の风 @NET求知 bufferCount 越小,文件被切割的越细,也就处理的越慢,但是丢包的可能性下降了,bufferCount 越大则相反 支持(0)反对(0) 回复引用 #24楼 2012-08-01 10:08 NET求知 文件从本地上传到服务器,这儿的本地localFilePath参数传入C:/test.txt,结果读到的还是服务器上的C盘下的test.txt文件呀?! 支持(0)反对(0) 回复引用 #25楼 2012-08-02 16:30 哭过的天空 最后一个示例 把UpFileSingleTest里的WriteToServer()和 UpLoadFileFromLocal()里的对FileStream的定义,提到UpFileSingleTest做一个私有成员变量如何?这样就不用每次调用方法时都要定义一个FileStream对象了。 频繁的创造对象会对性能有影响的,毕竟回收没那么及时。 FileStream里有几个构造器带缓冲大小的设置 比如 public FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize); 这个bufferSize和 public override int Read(byte[] array, int offset, int count)有关系吗? 昨天查了一下FileStream的源代码 这个bufferSize还是和Read(,,,)方法有关系的。如果没有在构造函数里指明bufferSize大小,那么系统默认的是)x1000,就是4K大小的缓冲区。 如果我们在public override int Read(byte[] array, int offset, int count) 传进count>bufferSize,那么就用count大小的缓冲区,否则就是bufferSize 支持(0)反对(0) 回复引用 #26楼 2012-11-16 15:14 Huang Xiao @NET求知 Buffer就相当于一个给定了内存(空间),然后一次性只能这么多,如果空间大了就传的多,所以在传小文件的时候一下子就可以了,而如果是非常大的文件上G了,那样非常耗内存的(空间),那电脑还做不做事啊? 支持(0)反对(0) 回复引用 #27楼 2013-03-06 16:29 copyCoder西卡西 我按你代码试了下发现个问题,异步读写文件的时候 即 执行End_ReadFileCallBack、End_CreateFileCallBack方法时,如果主线程先执行copyStream.Close();就会出现错误。不知道是不是我自己的问题 支持(0)反对(0) 回复引用 #28楼26546022013/4/10 22:54:37 2013-04-10 22:54 随风飘零 楼主,求吧源码发我一份,谢谢啊!402081060@qq.com 支持(0)反对(0) 刷新评论刷新页面返回顶部 发表评论 昵称: 评论内容: 不改了 注销 订阅评论 [使用Ctrl+Enter键快速提交] 找优秀程序员,就在博客园 博客园首页博问新闻闪存程序员招聘知识库 最新IT新闻: · 平板能干扰植入式除颤器,停止心跳 · Twitter音乐淡出用户视线 难道仅是实验品? · Tumblr早期投资人赚翻了!传获得5000倍回报 · Xbox One实现乔布斯生前梦想:客厅娱乐中心 · Debian GNU/Hurd 2013发布 » 更多新闻... 最新知识库文章: · 谈谈.NET中常见的内存泄露问题——GC、委托事件和弱引用 · BPM流程管理软件的五大创新 · IT固化是流程管理成功的重要手段 · 持续交付模式 · 关于软件开发的一些常识和思考 » 更多知识库文章... 公告 昵称:逆时针の风 园龄:3年 荣誉:推荐博客 粉丝:589 关注:14 +加关注 < 2012年4月 > 日 一 二 三 四 五 六 25 26 27 28 29 30 31 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 1 2 3 4 5 搜索 常用链接 我的随笔 我的评论 我的参与 最新评论 我的标签 随笔分类 Asp.net Asp.net MVC(1) c#(15) Data Sturcture(2) Design Pattern EF Html,Html5 IOC(1) Javascript JQuery(1) Silverlight(2) Sql2005,2008 SSIS,SSAS WCF 随笔档案 2012年11月 (1) 2012年8月 (1) 2012年7月 (1) 2012年6月 (1) 2012年5月 (1) 2012年4月 (3) 2012年3月 (8) 2012年2月 (1) 2011年8月 (1) 2010年8月 (1) 2010年7月 (1) 2010年6月 (4) 相册 55 kissnna3 积分与排名 积分 - 51632 排名 - 2444 最新评论 1. Re:(原创)c# 实用微型框架:FakeDataTool (Version 1) 超哥 有个东西叫做 nbuilder http://nbuilder.org/ --wlf 2. Re:C# 温故而知新:Stream篇(—) 个人觉得楼主的博文后面有点啰嗦, byte直接转换成string不行吗? readString = Encoding.Default.GetString(readBuffer); Console.WriteLine(readString); --笑笑菜鸟 3. Re:C# 温故而知新:Stream篇 (四) 楼主,求吧源码发我一份,谢谢啊!402081060@qq.com --随风飘零 4. Re:Win8 开发者训练营第一天的过程,感受和收获 :D WebClient 这个类还能在Win8中用?我怎么不行呢?有木有人告诉我一下呢? --湖西 5. Re:C# 温故而知新: 线程篇(四) 看了前几篇,感觉还不错,推荐一下,楼主加油 --Youg Lau 阅读排行榜 1. C# 温故而知新: 线程篇(一)(13045) 2. C# 温故而知新:Stream篇(—)(12460) 3. C# 温故而知新:Stream篇 (四)(8798) 4. C# 温故而知新:Stream篇(七)(7880) 5. C# 温故而知新:Stream篇(五)(7276) 评论排行榜 1. C# 温故而知新: 线程篇(一)(60) 2. C# 温故而知新:Stream篇(—)(47) 3. C# 温故而知新:Stream篇(七)(42) 4. C# 温故而知新: 线程篇(二)(39) 5. C# 温故而知新:线程篇(三)(34) 推荐排行榜 1. C# 温故而知新: 线程篇(一)(75) 2. C# 温故而知新:Stream篇(—)(58) 3. C# 温故而知新: 线程篇(二)(33) 4. C# 温故而知新:Stream篇 (四)(30) 5. C# 温故而知新:Stream篇(七)(23) Copyright ©2013 逆时针の风
逆时针の风
C# 温故而知新:Stream篇 (四)
C# 温故而知新:Stream篇(四)
FileStream
目录:
- 如何去理解FileStream?
- FileStream的重要性
- FileStream常用构造函数(重要)
- 非托管参数SafeFileHandle简单介绍
- FileStream常用属性介绍
- FileStream常用方法介绍
- FileStream示例1:*文件的新建和拷贝(主要演示文件同步和异步操作)
- FileStream示例2:*实现文件本地分段上传
- 本章总结
如何去理解FileStream?
通过前3章的学习相信大家对于Stream已经有一定的了解,但是又如何去理解FileStream呢?请看下图
我们磁盘的中任何文件都是通过2进制组成,最为直观的便是记事本了,当我们新建一个记事本时,它的大小是0KB, 我们每次输入一个数字或
字母时文件便会自动增大4kb,可见随着我们输入的内容越来越多,文件也会相应增大,同理当我们删除文件内容时,文件也会相应减小,对了,
聪明的你肯定会问:谁将内容以怎么样的形式放到文件中去了?好问题,还记得第一篇流的概念么?对了,真实世界的一群鱼可以通过河流来
往于各个地方,FileStream也是一样,byte可以通过FileStream进行传输,这样我们便能在计算机上对任何文件进行一系列的操作了。
FileStream 的重要性
FileStream 顾名思义文件流,我们电脑上的文件都可以通过文件流进行操作,例如文件的复制,剪切,粘贴,删除, 本地文件上传,下载,等许
多重要的功能都离不开文件流,所以文件流不仅在本机上非常重要,在如今的网络世界也是万万不能缺少的,想象一下我们开启虚机后,直接从本
地复制一个文件到虚机上,是多么方便,如果没有文件流,这个将难以想象。(大家别误解,文件流无法直接通过网络进行传输,而是
通过网络流将客户端上传的文件传到服务器端接收,然后通过文件流进行处理,下载正好相反)
FileStream 常用构造函数介绍(可能理解上有点复杂,请大家务必深刻理解)
*1: FileStream(SafeFileHandle, FileAccess)
非托管参数SafeFileHandle简单介绍
SafeFileHandle :是一个文件安全句柄,这样的解释可能大家一头雾水,
别急,大家先不要去理睬这深邃的含义,只要知道这个类型是c#非托管资源,
也就是说它能够调用非托管资源的方法,而且不属于c#回收机制,所以我们必须
使用GC手动或其他方式(Finalize 或Dispose方法)进行非托管资源的回收,所以
SafeFileHandle是个默默无闻的保镖 ,一直暗中保护FileStream和文件的安全
为了能让大家更好的理解这个保镖,请看第一段代码:
会什么会报错呢?其实程序被卡在 Console.ReadLine()这里,FileStream并没有
被释放,系统不知道这个文件是否还有用﹐所以帮我们保护这个文件
(那个非托管资源SafeFileHandle所使用的内存还被程序占用着)
所以SafeFileHandled 在内部保护了这个文件从而报出了这个异常
如果我们将流关闭后,这个问题也就不存在了
可以看见stream.SafeFileHandle的IsClose属性变成true了,也就是说这时候可以安全的删除文件了
所以又回到了一个老问题上面,我们每次使用完FileStream后都必须将他关闭并释放资源
*2: FileStream(String, FileMode)
String 参数表示文件所在的地址,FIleMode是个枚举,表示确定如何打开或创建文件。
FileMode枚举参数包含以下内容:
成员名称 | 说明 |
Append | 打开现有文件并查找到文件尾,或创建新文件。FileMode.Append 只能同 FileAccess.Write 一起使用。 |
Create | 指定操作系统应创建新文件。如果文件已存在,它将被改写。这要求 FileIOPermissionAccess.Write。 System.IO.FileMode.Create 等效于这样的请求:如果文件不存在,则使用 CreateNew;否则使用 Truncate。 |
CreateNew | 指定操作系统应创建新文件。此操作需要 FileIOPermissionAccess.Write。如果文件已存在,则将引发 IOException。 |
Open | 指定操作系统应打开现有文件。打开文件的能力取决于 FileAccess 所指定的值。如果该文件不存在, 则引发 System.IO.FileNotFoundException。 |
OpenOrCreate | 指定操作系统应打开文件(如果文件存在);否则,应创建新文件。如果用 FileAccess.Read 打开文件,则需要 FileIOPermissionAccess.Read。如果文件访问为 FileAccess.Write 或 FileAccess.ReadWrite,则需要 FileIOPermissionAccess.Write。如果文件访问为 FileAccess.Append,则需要 FileIOPermissionAccess.Append。 |
Truncate | 指定操作系统应打开现有文件。文件一旦打开,就将被截断为零字节大小。此操作需要 FileIOPermissionAccess.Write。 试图从使用 Truncate 打开的文件中进行读取将导致异常。 |
*3: FileStream(IntPtr, FileAccess, Boolean ownsHandle)
FileAccess 参数也是一个枚举, 表示对于该文件的操作权限
ReadWrite | 对文件的读访问和写访问。可从文件读取数据和将数据写入文件 |
Write | 文件的写访问。可将数据写入文件。同 Read组合即构成读/写访问权 |
Read | 对文件的读访问。可从文件中读取数据。同 Write组合即构成读写访问权 |
参数ownsHandle:也就是类似于前面和大家介绍的SafeFileHandler,有2点必须注意:
1对于指定的文件句柄,操作系统不允许所请求的 access,例如,当 access 为 Write 或 ReadWrite 而文件句柄设置为只读访问时,会报出异常。
所以 ownsHandle才是老大,FileAccess的权限应该在ownsHandle的范围之内
2. FileStream 假定它对句柄有独占控制权。当 FileStream 也持有句柄时,读取、写入或查找可能会导致数据破坏。为了数据的安全,请使用
句柄前调用 Flush,并避免在使用完句柄后调用 Close 以外的任何方法。
*4: FileStream(String, FileMode, FileAccess, FileShare)
FileShare:同样是个枚举类型:确定文件如何由进程共享。
Delete | 允许随后删除文件。 |
Inheritable | 使文件句柄可由子进程继承。Win32 不直接支持此功能。 |
None | 谢绝共享当前文件。文件关闭前,打开该文件的任何请求(由此进程或另一进程发出的请求)都将失败。 |
Read | 允许随后打开文件读取。如果未指定此标志,则文件关闭前,任何打开该文件以进行读取的请求(由此进程或另一进程发出的请求)都将失败。但是,即使指定了此标志,仍可能需要附加权限才能够访问该文件。 |
ReadWrite | 允许随后打开文件读取或写入。如果未指定此标志,则文件关闭前,任何打开该文件以进行读取或写入的请求(由此进程或另一进程发出)都将失败。但是,即使指定了此标志,仍可能需要附加权限才能够访问该文件。 |
Write | 允许随后打开文件写入。如果未指定此标志,则文件关闭前,任何打开该文件以进行写入的请求(由此进程或另一进过程发出的请求)都将失败。但是,即使指定了此标志,仍可能需要附加权限才能够访问该文件。 |
*5: FileStream(String, FileMode, FileAccess, FileShare, Int32, Boolean async )
Int32:这是一个缓冲区的大小,大家可以按照自己的需要定制,
Boolean async:是否异步读写,告诉FileStream示例,是否采用异步读写
*6: FileStream(String, FileMode, FileAccess, FileShare, Int32, FileOptions)
FileOptions:这是类似于FileStream对于文件操作的高级选项
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; }
}
创建文件配置类CreateFileConfig,用于添加文件一些配置设置,实现添加文件的操作
/// <summary>
/// 创建文件配置类
/// </summary>
public class CreateFileConfig : IFileConfig
{
// 文件名
public string FileName { get; set; }
//是否异步操作
public bool IsAsync { get; set; }
//创建文件所在url
public string CreateUrl { get; set; }
}
让我们定义一个文件流测试类:FileStreamTest 来实现文件的操作
/// <summary>
/// FileStreamTest 类
/// </summary>
public class FileStreamTest
在该类中实现一个简单的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();
}
}
}
如果采用异步的方式则最后会进入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();
}
文件复制的方式思路比较相似,首先定义复制文件配置类,由于在异步回调中用到该配置类的属性,所以新增了文件流对象和相应的字节数组
/// <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; }
}
然后在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();
}
}
}
最后,如果采用异步的方式,则会进入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();
}
}
最后让我们在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);
}
输出结果:
实现文件本地分段上传
上面的例子是将一个文件作为整体进行操作,这样会带来一个问题,当文件很大或者网络不是很稳定的时候会发生意想不到的错误
那我们该怎么解决这一问题呢?其实有种思路还是不错的,那就是分段传输:
那就DIY一个简单的分段传输的例子,我们先将处理每一段的逻辑先整理好
/// <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;
}
}
}
}
一切准备就绪,我们剩下的就是将文件切成几段进行上传了
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);
}
}
上传结果:
总的来说,分段传输比直接传输复杂许多,我会在今后的例子中加入多线程,这样的话每段数据的传输都能通过一个线程单独处理,能够提升上传性能和速度
本章总结
本章介绍了Stream中最关键的派生类FileStream的概念,属性,方法,构造函数等重要的概念,包括一些难点和重要点都一一列举出来,最后2个例子让大家在温故下
FileStream的使用方法,包括FileStream异步同步操作和分段传输操作。
如果大家喜欢我的文章,请大家多多关注下,下一章将会介绍MemoryStream,敬请期待!
把UpFileSingleTest里的WriteToServer()和 UpLoadFileFromLocal()里的对FileStream的定义,提到UpFileSingleTest做一个私有成员变量如何?这样就不用每次调用方法时都要定义一个FileStream对象了。
频繁的创造对象会对性能有影响的,毕竟回收没那么及时。
FileStream里有几个构造器带缓冲大小的设置
比如
public FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize);
这个bufferSize和 public override int Read(byte[] array, int offset, int count)有关系吗?
昨天查了一下FileStream的源代码
这个bufferSize还是和Read(,,,)方法有关系的。如果没有在构造函数里指明bufferSize大小,那么系统默认的是)x1000,就是4K大小的缓冲区。
如果我们在public override int Read(byte[] array, int offset, int count) 传进count>bufferSize,那么就用count大小的缓冲区,否则就是bufferSize
· 平板能干扰植入式除颤器,停止心跳
· Twitter音乐淡出用户视线 难道仅是实验品?
· Tumblr早期投资人赚翻了!传获得5000倍回报
· Xbox One实现乔布斯生前梦想:客厅娱乐中心
· Debian GNU/Hurd 2013发布
» 更多新闻...
» 更多知识库文章...
搜索
随笔分类
随笔档案
相册
kissnna3
积分与排名
- 积分 - 51632
- 排名 - 2444
最新评论
- 1. Re:(原创)c# 实用微型框架:FakeDataTool (Version 1)
- 超哥 有个东西叫做 nbuilder
http://nbuilder.org/ - 2. Re:C# 温故而知新:Stream篇(—)
- 个人觉得楼主的博文后面有点啰嗦,
byte直接转换成string不行吗?
readString = Encoding.Default.GetString(readBuffer);
Console.WriteLine(readString); - 3. Re:C# 温故而知新:Stream篇 (四)
- 楼主,求吧源码发我一份,谢谢啊!402081060@qq.com
- 4. Re:Win8 开发者训练营第一天的过程,感受和收获 :D
- WebClient 这个类还能在Win8中用?我怎么不行呢?有木有人告诉我一下呢?
- 5. Re:C# 温故而知新: 线程篇(四)
- 看了前几篇,感觉还不错,推荐一下,楼主加油