收藏 分享(赏)

C# 温故而知新:Stream篇(四)FileStream.doc

上传人:dreamzhangning 文档编号:2648985 上传时间:2018-09-24 格式:DOC 页数:21 大小:211.50KB
下载 相关 举报
C# 温故而知新:Stream篇(四)FileStream.doc_第1页
第1页 / 共21页
C# 温故而知新:Stream篇(四)FileStream.doc_第2页
第2页 / 共21页
C# 温故而知新:Stream篇(四)FileStream.doc_第3页
第3页 / 共21页
C# 温故而知新:Stream篇(四)FileStream.doc_第4页
第4页 / 共21页
C# 温故而知新:Stream篇(四)FileStream.doc_第5页
第5页 / 共21页
点击查看更多>>
资源描述

1、FileStream目录: 如何去理解 FileStream? FileStream 的重要性 FileStream 常用构造函数(重要) 非托管参数 SafeFileHandle 简单介绍 FileStream 常用属性介绍 FileStream 常用方法介绍 FileStream 示例 1:*文件的新建和拷贝(主要演示文件同步和异步操作) FileStream 示例 2:*实现文件本地分段上传 本章总结 如何去理解 FileStream?通过前 3 章的学习相信大家对于 Stream 已经有一定的了解,但是又如何去理解 FileStream呢?http:/ 请看下图 我们磁盘的中任何文件都

2、是通过 2 进制组成,最为直观的便是 记事本了,当我们新建一个记事本时,它的大小是 0KB, 我们每次输入一个数字或字母时文件便会自动增大 4kb,可见随着我们输入的内容越来越多,文件也会相应增大,同理当我们删除文件内容时,文件也会相应减小,对了,聪明的你肯定会问:谁将内容以怎么样的形式放到文件中去了?好问题,还记得第一篇流的概念么?对了,http:/ 真实世界的一群鱼可以通过河流来往于各个地方,FileStream 也是一样,byte 可以通过 FileStream 进行传输,这样我们便能在计算机上对任何文件进行一系列的操作了。FileStream 的重要性FileStream 顾名思义文件

3、流,我们电脑上的文件都可以通过文件流进行操作,例如文件的复制,剪切,粘贴,删除, 本地文件上传,下载,等许多重要的功能都离不开文件流,所以文件流不仅在本机上非常重要,在如今的网络世界也是万万不能缺少的,想象一下我们开启虚机后,直接从本地复制一个文件到虚机上,是多么方便,如果没有文件流,这个将难以想象。(大家别误解,文件流无法直接通过网络进行传输,而是通过网络流将客户端上传的文件传到服务器端接收,然后通过文件流进行处理,下载正好相反)FileStream 常用构造函数介绍 ( 可能理解上有点复杂,请大家务必深刻理解)*1: FileStream(SafeFileHandle, FileAcces

4、s)非托管参数 SafeFileHandle 简单介绍SafeFileHandle :是一个文件安全句柄,这样的解释可能大家一头雾水,别急,大家先不要去理睬这深邃的含义,只要知道这个类型是 c#非托管资源,也就是说它能够调用非托管资源的方法,而且不属于 c#回收机制,所以我们必须使用 GC 手动或其他方式(Finalize 或 Dispose 方法)进行非托管资源的回收,所以SafeFileHandle 是个默默无闻的保镖 ,一直暗中保护FileStream 和文件的安全为了能让大家更好的理解这个保镖,请看第一段代码:会什么会报错呢?其实程序被卡在 Console.ReadLine()这里,F

5、ileStream 并没有被释放,系统不知道这个文件是否还有用 所以帮我们保护这个文件(那个非托管资源 SafeFileHandle 所使用的内存还被程序占用着)所以 SafeFileHandled 在内部保护了这个文件从而报出了这个异常如果我们将流关闭后,这个问题也就不存在了可以看见 stream.SafeFileHandle 的 IsClose 属性变成true 了,也就是说这时候可以安全的删除文件了 所以又回到了一个老问题上面,我们每次使用完FileStream 后都必须将他关闭并释放资源*2: FileStream(String, FileMode)String 参数表示文件所在的地址

6、, FIleMode 是个枚举,表示确定如何打开或创建文件。FileMode 枚举参数包含以下内容:成员名称 说明Append 打开现有文件并查找到文件尾,或创建新文件。FileMode.Append 只能同 FileAccess.Write 一起使用。Create指定操作系统应创建新文件。如果文件已存在,它将被改写。这要求 FileIOPermissionAccess.Write。System.IO.FileMode.Create 等效于这样的请求:如果文件不存在,则使用 CreateNew;否则使用 Truncate。CreateNew 指定操作系统应创建新文件。此操作需要 FileIOP

7、ermissionAccess.Write。如果文件已存在,则将引发 IOException。Open指定操作系统应打开现有文件。打开文件的能力取决于 FileAccess 所指定的值。如果该文件不存在,则引发 System.IO.FileNotFoundException。OpenOrCreate指定操作系统应打开文件(如果文件存在);否则,应创建新文件。如果用 FileAccess.Read 打开文件,则需要FileIOPermissionAccess.Read。如果文件访问为 FileAccess.Write 或 FileAccess.ReadWrite,则需要 FileIOPermis

8、sionAccess.Write。如果文件访问为 FileAccess.Append,则需要 FileIOPermissionAccess.Append。Truncate指定操作系统应打开现有文件。文件一旦打开,就将被截断为零字节大小。此操作需要 FileIOPermissionAccess.Write。试图从使用 Truncate 打开的文件中进行读取将导致异常。*3: FileStream(IntPtr, FileAccess, Boolean ownsHandle)FileAccess 参数也是一个枚举, 表示对于该文件的操作权限ReadWrite 对文件的读访问和写访问。可从文件读取数

9、据和将数据写入文件Write 文件的写访问。可将数据写入文件。同 Read 组合即构成读/写访问权Read 对文件的读访问。可从文件中读取数据。同 Write 组合即构成读写访问权参数 ownsHandle:也就是类似于前面和大家介绍的 SafeFileHandler,有2 点必须注意 :1 对于指定的文件句柄,操作系统不允许所请求的 access,例如,当 access 为 Write 或 ReadWrite 而文件句柄设置为只读访问时,会报出异常。所以 ownsHandle 才是老大, FileAccess 的权限应该在 ownsHandle 的范围之内2. FileStream 假定它对

10、句柄有独占控制权。当 FileStream 也持有句柄时,读取、写入或查找可能会导致数据破坏。为了数据的安全,请使用句柄前调用 Flush,并避免在使用完句柄后调用 Close 以外的任何方法。*4: FileStream(String, FileMode, FileAccess, FileShare)FileShare:同样是个枚举类型:确定文件如何由进程共享。 Delete 允许随后删除文件。Inheritable 使文件句柄可由子进程继承。Win32 不直接支持此功能。None 谢绝共享当前文件。文件关闭前,打开该文件的任何请求(由此进程或另一进程发出的请求)都将失败。Read 允许随后

11、打开文件读取。如果未指定此标志,则文件关闭前,任何打开该文件以进行读取的请求(由此进程或另一进程发出的请求)都将失败。但是,即使指定了此标志,仍可能需要附加权限才能够访问该文件。ReadWrite 允许随后打开文件读取或写入。如果未指定此标志,则文件关闭前,任何打开该文件以进行读取或写入的请求(由此进程或另一进程发出)都将失败。但是,即使指定了此标志,仍可能需要附加权限才能够访问该文件。Write 允许随后打开文件写入。如果未指定此标志,则文件关闭前,任何打开该文件以进行写入的请求(由此进程或另一进过程发出的请求)都将失败。但是,即使指定了此标志,仍可能需要附加权限才能够访问该文件。*5: F

12、ileStream(String, FileMode, FileAccess, FileShare, Int32, Boolean async )Int32:这是一个缓冲区的大小,大家可以按照自己的需要定制,Boolean async:是否异步读写,告诉 FileStream 示例,是否采用异步读写*6: FileStream(String, FileMode, FileAccess, FileShare, Int32, FileOptions)FileOptions:这是类似于 FileStream 对于文件操作的高级选项FileStream 常用属性介绍*1:CanRead :指示 Fil

13、eStream 是否可以读操作*2:CanSeek :指示 FileStream 是否可以跟踪查找流操作*3:IsAsync:FileStream 是否同步工作还是异步工作*4:Name:FileStream 的名字 只读属性*5:ReadTimeout :设置读取超时时间*6:SafeFileHandle : 文件安全句柄 只读属性*7:position :当前 FileStream 所在的流位置FileStream 常用方法介绍以下方法重写了 Stream 的一些虚方法(*这里大家点击这里可以参考第一篇来温故下,这里不再叙述)1:IAsyncResult BeginRead 异步读取2:I

14、AsyncResult BeginWrite 异步写3:void Close 关闭当前 FileStream4: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

15、 GetAccessControl()这个不是很常用,FileSecurity 是文件安全类,直接表达当前文件的访问控制列表(ACL )的符合当前文件权限的项目,ACL 大家有个了解就行,以后会单独和大家讨论下 ACL 方面的知识*2: void Lock(long position,long length)这个 Lock 方法和线程中的 Look 关键字很不一样,它能够锁住文件中的某一部分,非常的强悍!用了这个方法我们能够精确锁定住我们需要锁住的文件的部分内容*3: void SetAccessControl(FileSecurity fileSecurity)和 GetAccessCont

16、rol 很相似,ACL 技术会在以后单独介绍*4: void Unlock (long position,long length)正好和 lock 方法相反,对于文件部分的解锁文件的新建和拷贝(主要演示文件同步和异步操作)首先我们尝试 DIY 一个 IFileConfig/ / 文件配置接口/ public interface IFileConfigstring FileName get; set; bool IsAsync get; set; 创建文件配置类 CreateFileConfig,用于添加文件一些配置设置,实现添加文件的操作/ / 创建文件配置类/ public class Cr

17、eateFileConfig : IFileConfig/ 文件名public string FileName get; set; /是否异步操作public bool IsAsync get; set; /创建文件所在 urlpublic string CreateUrl get; set; 让我们定义一个文件流测试类:FileStreamTest 来实现文件的操作/ / FileStreamTest 类/ public class FileStreamTest在该类中实现一个简单的 Create 方法用来同步或异步的实现添加文件,FileStream 会根据配置类去选择相应的构造函数,实现

18、异步或同步的添加方式/ / 添加文件方法/ / 创建文件配置类public void Create(IFileConfig config)lock (_lockObject)/得到创建文件配置类对象var createFileConfig = config as CreateFileConfig;/检查创建文件配置类是否为空if (this.CheckConfigIsError(config) return;/假设创建完文件后写入一段话,实际项目中无需这么做,这里只是一个演示char insertContent = “HellowWorld“.ToCharArray();/转化成 byteby

19、te 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(create

20、FileConfig.CreateUrl, FileMode.Create);using (stream)/ 如果不注释下面代码会抛出异常,google 上提示是 WriteTimeout 只支持网络流/ stream.WriteTimeout = READ_OR_WRITE_TIMEOUT;/如果该流是同步流并且可写if (!stream.IsAsync else if (stream.CanWrite)/异步流并且可写stream.BeginWrite(byteArrayContent, 0, byteArrayContent.Length, this.End_CreateFileCall

21、Back, stream);stream.Close();如果采用异步的方式则最后会进入 End_CreateFileCallBack 回调方法,result.AsyncState 对象就是上图 stream.BeginWrite()方法的最后一个参数还有一点必须注意的是每一次使用 BeginWrite()方法事都要带上 EndWrite()方法,Read 方法也一样/ / 异步写文件 callBack 方法/ / IAsyncResultprivate void End_CreateFileCallBack(IAsyncResult result)/从 IAsyncResult 对象中得到原

22、来的 FileStreamvar stream = result.AsyncState as FileStream;/结束异步写Console.WriteLine(“异步创建文件地址:0“, stream.Name);stream.EndWrite(result);Console.ReadLine();文件复制的方式思路比较相似,首先定义复制文件配置类,由于在异步回调中用到该配置类的属性,所以新增了文件流对象和相应的字节数组/ / 文件复制/ public class CopyFileConfig : IFileConfig/ 文件名public string FileName get; se

23、t; /是否异步操作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 方法实现文件的

24、复制功能/ / 复制方法/ / 拷贝文件复制public void Copy(IFileConfig config)lock (_lockObject)/得到 CopyFileConfig 对象var copyFileConfig = config as CopyFileConfig;/ 检查 CopyFileConfig 类对象是否为空或者 OrginalFileUrl 是否为空if (CheckConfigIsError(copyFileConfig) | !File.Exists(copyFileConfig.OrginalFileUrl) return;/创建同步或异步流FileStr

25、eam 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 bytestream.Length;using (stream)/ str

26、eam.ReadTimeout = READ_OR_WRITE_TIMEOUT;/如果异步流if (stream.IsAsync)/将该流和读出的 byte数据放入配置类,在 callBack 中可以使用copyFileConfig.OriginalFileStream = stream;copyFileConfig.OriginalFileBytes = orignalFileBytes;if (stream.CanRead)/异步开始读取,读完后进入 End_ReadFileCallBack 方法,该方法接受 copyFileConfig 参数stream.BeginRead(origna

27、lFileBytes, 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 (copySt

28、ream)/ copyStream.WriteTimeout = READ_OR_WRITE_TIMEOUT;/将源文件的内容写进新文件copyStream.Write(orignalFileBytes, 0, orignalFileBytes.Length);copyStream.Close();stream.Close();Console.ReadLine();最后,如果采用异步的方式,则会进入 End_ReadFileCallBack 回调函数进行异步读取和异步写操作/ / 异步读写文件方法/ / private void End_ReadFileCallBack(IAsyncResul

29、t 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);u

30、sing (copyStream)Console.WriteLine(“异步复制原文件地址:0“, config.OriginalFileStream.Name);Console.WriteLine(“复制后的新文件地址:0“, config.DestinationFileUrl);/调用异步写方法 CallBack 方法为 End_CreateFileCallBack,参数是 copyStreamcopyStream.BeginWrite(config.OriginalFileBytes, 0, config.OriginalFileBytes.Length, this.End_Create

31、FileCallBack,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 CopyFileConfi

32、gOrginalFileUrl = “d:8.jpg“,DestinationFileUrl = “d:9.jpg“,IsAsync = true;test.Create(createFileConfig);test.Copy(copyFileConfig);输出结果:实现文件本地分段上传上面的例子是将一个文件作为整体进行操作,这样会带来一个问题,当文件很大或者网络不是很稳定的时候会发生意想不到的错误那我们该怎么解决这一问题呢?其实有种思路还是不错的,那就是分段传输:那就 DIY 一个简单的分段传输的例子,我们先将处理每一段的逻辑先整理好/ / 分段上传例子/ public class UpF

33、ileSingleTest/我们定义 Buffer 为 1000public const int BUFFER_COUNT = 1000;/ / 将文件上传至服务器(本地),由于采取分段传输所以,/ 每段必须有一个起始位置和相对应该数据段的数据/ / 服务器上文件地址/ 分段起始位置/ 每段的数据private void WriteToServer(string filePath,int startPositon,byte btArray) FileStream fileStream = new FileStream(filePath, FileMode.OpenOrCreate);using

34、 (fileStream) /将流的位置设置在该段起始位置fileStream.Position = startPositon;/将该段数据通过 FileStream 写入文件中,每次写一段的数据,就好比是个水池,分段蓄水一样,直到蓄满为止fileStream.Write(btArray, 0, btArray.Length);/ / 处理单独一段本地数据上传至服务器的逻辑,根据客户端传入的 startPostion/ 和 totalCount 来处理相应段的数据上传至服务器(本地)/ / 本地需要上传的文件地址/ 服务器(本地)目标地址/ 该段起始位置/ 该段最大数据量public void

35、 UpLoadFileFromLocal(string localFilePath,string uploadFilePath,int startPostion,int totalCount) /if(!File.Exists(localFilePath)return;/每次临时读取数据数int tempReadCount = 0;int tempBuffer = 0;/定义一个缓冲区数组byte bufferByteArray = new byteBUFFER_COUNT;/定义一个 FileStream 对象FileStream fileStream = new FileStream(lo

36、calFilePath,FileMode.Open);/将流的位置设置在每段数据的初始位置fileStream.Position = startPostion;using (fileStream)/循环将该段数据读出在写入服务器中while (tempReadCount totalCount) /缓冲区的数据为 totalCount-tempReadCount tempBuffer = totalCount-tempReadCount;/读取该段数据放入 bufferByteArray 数组中fileStream.Read(bufferByteArray, 0, tempBuffer);if

37、(tempBuffer 0) byte newTempBtArray = new bytetempBuffer;Array.Copy(bufferByteArray, 0, newTempBtArray, 0, tempBuffer);/将缓冲区的数据上传至服务器this.WriteToServer(uploadFilePath, writeStartPosition, newTempBtArray);/如果缓冲区的数据量小于该段数据量,并且 tempBuffer=设定 BUFFER_COUNT 时,通过/while 循环每次读取一样的 buffer 值的数据写入服务器中,直到将该段数据全部处

38、理完毕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 UpFileSingl

39、eTest();FileInfo info = new FileInfo(“G:Skyrim20080204173728108.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 perFileLengh ?

40、perFileLengh : (int)(fileLegth - perFileLengh * i);/上传该段数据test.UpLoadFileFromLocal(“G:Skyrim20080204173728108.torrent“, “G:Skyrim20080204173728109.torrent“, startPosition, i = divide ? divide : totalCount);上传结果:总的来说,分段传输比直接传输复杂许多,我会在今后的例子中加入多线程,这样的话每段数据的传输都能通过一个线程单独处理,能够提升上传性能和速度本章总结本章介绍了 Stream 中最关键的派生类 FileStream 的概念,属性,方法,构造函数等重要的概念,包括一些难点和重要点都一一列举出来,最后 2 个例子让大家在温故下FileStream 的使用方法, 包括 FileStream 异步同步操作和分段传输操作。如果大家喜欢我的文章,请大家多多关注下,下一章将会介绍MemoryStream,敬请期待!

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高等教育 > 大学课件

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:道客多多官方知乎号:道客多多

经营许可证编号: 粤ICP备2021046453号世界地图

道客多多©版权所有2020-2025营业执照举报