FileStream相关知识分享
一、如何理解FIleStream
通过前3章的学些,相信大家对于Stream已经有一定的了解,但是又如何去理解FileStream呢?请看下图:
我们磁盘中的任何文件都是通过二进制数组组成,最为直观的就是记事本了,当我们新建一个记事本时,它的大小时0KB,我们每次输入一个数字或字母时,文件便会自动增大到4KB,可见,随着我们输入的内容越来越多,文件也会越来越大,同理,当我们删除文件内容时,文件也会相应的减小,对了,聪明的你肯定会问:谁将内容以怎样的形式放到文件中去了?
好问题,还记得第一章流的概念嘛?对了,真实世界的一群鱼可以通过河流往前往各个地方,FileStream也一样,byte可以通过FileStream进行传输,这样我们便能在计算机上对任何文件进行一系列操作了。
二、FileStream的重要性
FileStream顾名思义文件流,我们电脑上的文件都可以通过文件流进行操作,例如文件的复制、简介、粘贴、删除、本地文件上传、下载、等许多重要的功能都离不开文件流。所以文件流不仅在本机上非常重要,在如今的网络世界上也万万不能缺少的,想象一下我们开启虚拟机后,直接从本地复制一个文件到虚拟机上,时多么的方便,如果没有文件流,这个将难以想象。(大家别误解,文件流通过网络流将客户都安上传的文件传到服务器端接收,然后通过文件流进行处理,下载正好相反)
三、FileStream常用构造函数介绍
1、FileStream(SafeFileHandle safeFileHandle,FileAccess fileAccess)
非托管参数SafeFileHandle简单介绍
SafeFileHandle:是一个文件安全句柄,这样的解释可能大家一头雾水,别急,大家先不要去理睬这深邃的含义,只要知道这个类型是C#非托管资源,也就是说它能够调用非托管资源的方法,而且不属于C#回收机制,所以我们必须使用GC手动或其他方式(Finalize或Dispose方法)进行非托管资源的回收,所以SafeFileHandle是一个默默无闻的保镖,一直暗中保护FileStream和文件的安全,为了让大家更好的理解这个保镖,请看第一段代码:
static void Main(string[] args)
{
var rootPath = Environment.CurrentDirectory;
var fileName = Path.Combine(rootPath, "TextFile1.txt");//@"TextFile1.txt";
FileStream fileStream = new FileStream(fileName, FileMode.OpenOrCreate);
Console.ReadLine();
File.Delete(fileName);
Console.ReadKey();
}
我们运行一下,结果报错了,我看看一下错误:
为什么会报错呢?其实程序被卡在了Console.ReadLine()这里,FileStream并没有被释放,系统不知道这个文件是否还有用,所以帮我们保护这个文件(那个非托管资源SafeFileHandle所使用的内存还被占用着)所以SafeFileHandle在内部保护了这个文件从而报出了这个异常,如果我们将流关闭后,这个问题就不存在了。
所以,我们又回到了一个老问题上面,我们每次使用完FileStream后都必须将他关闭并释放资源。
2、FileStream(string str,FileModel model)
string 参数表示文件所在的地址,FileMode是个枚举,表示确定如何打开或创建文件 。
FileModel枚举参数包含以下内容:
成员名称 |
说明 |
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 intPtr,FIleAccess fileAccess,Boolean ownsHandle)
FileAccess参数也是一个枚举,表示对该文件的操作权限:
参数ownsHandle:也就是类似于前面和大家介绍的SafeFileHandler,有2点必须注意:(1)对于指定的文件句柄,操作系统不允许所请求的access,例如:当access为Write或ReadWrite而文件句柄设置为只读访问的时候,会出现异常。所以ownsHandle才是老大,FileAccess的权限应该在ownsHandle的范围内。(2)FileStream假定它的句柄有独占控制权,当FileStream也持有句柄时,读取、写入或查找可能会导致数据破坏,为了数据安全,请使用句柄前调用Flush,并避免在使用完句柄后调用Close以外的任何方法。
4、FileStream(string str,FileModel model,FileAccess,fileAccess,FileShare fileShare)
FileShare:同样时一个枚举类型,确定文件如何由进程共享。
Delete |
允许随后删除文件。 |
Inheritable |
使文件句柄可由子进程继承。Win32 不直接支持此功能。 |
None |
谢绝共享当前文件。文件关闭前,打开该文件的任何请求(由此进程或另一进程发出的请求)都将失败。 |
Read |
允许随后打开文件读取。如果未指定此标志,则文件关闭前,任何打开该文件以进行读取的请求(由此进程或另一进程发出的请求)都将失败。但是,即使指定了此标志,仍可能需要附加权限才能够访问该文件。 |
ReadWrite |
允许随后打开文件读取或写入。如果未指定此标志,则文件关闭前,任何打开该文件以进行读取或写入的请求(由此进程或另一进程发出)都将失败。但是,即使指定了此标志,仍可能需要附加权限才能够访问该文件。 |
Write |
允许随后打开文件写入。如果未指定此标志,则文件关闭前,任何打开该文件以进行写入的请求(由此进程或另一进过程发出的请求)都将失败。但是,即使指定了此标志,仍可能需要附加权限才能够访问该文件。 |
5、FileStream(string str,FileMode mode,FileAccess fileAccess,FileShare fileShare,Int32 i,Boolean async)
Int32:这是一个缓冲区的大小,大家可以按照自己的需要定制;
Boolean async:是否异步读写,告诉FileStream示例,是否采用异步读写
6、FileStream(string str,FileMode mode,FileShare fileShare,Int32 i,FileOption fileOption)
FileOption:这是类似于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方法和线程中的Lock关键字很不一样,它能够锁住文件中的某一部分,非常的强悍!用了这个方法我们能够精确锁定住我们要锁住的文件的部分内容
3、void SetAccessControl(FileSecurity fileSecurity)
和GetAccessControl很相似,ACL技术会再以后单独介绍
4、void Unlock(long position,long length)
正好和lock方法相反,对于文件部分的解锁
七、文件的新建和拷贝(主要演示文件同步和异步操作)
首先我们尝试DIY一个IFileCOnfig
public interface IFileConfig
{
string FileName { get; set; }
bool IsAsync { get; set; }
}
创建文件配置类CreateFileConfig,用于添加文件一些配置设置,实现添加文件的操作
public class CreateFileConfig : IFileConfig
{
/// <summary>
/// 文件名称
/// </summary>
public string FileName { get; set; }
/// <summary>
/// 是否异步
/// </summary>
public bool IsAsync { get; set; }
/// <summary>
/// 创建文件所在Url
/// </summary>
public string CreateUrl { get; set; }
}
让我们定义一个文件流测试类:FileStreamTest来实现文件的操作。
/// <summary>
/// 文件测试类
/// </summary>
public class FileStreamTest
在该类中实现一个简单的Create方法,用来同步或异步的实现添加文件,FileStream会根据配置类去选择相应的构造函数,实现异步或同步的添加方式
/// <summary>
/// 添加文件方法
/// </summary>
/// <param name="config"></param>
public void Create(IFileConfig config)
{
lock (_lockObject)
{
//得到创建文件配置的对象
var createFileConfig = config as CreateFileConfig;
//假设创建完文件后写入一段话,实际项目中无需这么做,这里只是演示
char[] insertContent = "HellowWord".ToCharArray();
if (createFileConfig == null)
{
return;
}
//转化成byte[]
byte[] byteArrayContent = Encoding.Default.GetBytes(insertContent, , insertContent.Length);
//根据传入的配置文件来决定是否同步或者异步实例化Stream对象
FileStream stream = createFileConfig.IsAsync
? new FileStream(createFileConfig.CreateUrl, FileMode.Create, FileAccess.ReadWrite, FileShare.None,
, 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, , byteArrayContent.Length);
}
else if (stream.CanWrite)//异步可写
{
stream.BeginWrite(byteArrayContent, , byteArrayContent.Length, End_CreateFileCallBack, stream);
}
}
}
}
如果采用异步的方式则最后会进入End_CreateFileCallBack回调方法,result AsyncState 对象就是上图stream.BeginWrite()方法的最后一个参数。还有一点必须注意的是每一次使用BeginWrite()方法都要带上EndWrite()方法,Read方法也一样
/// <summary>
/// 异步写文件callBack方法
/// </summary>
/// <param name="result"></param>
private void End_CreateFileCallBack(IAsyncResult result)
{
//从IAsyncResult对象中得到原来的FileStream
var stream = result.AsyncState as FileStream;
//结束异步写
if (stream != null)
{
Console.WriteLine("异步创建文件地址{0}", stream.Name);
stream.EndWrite(result);
} Console.ReadKey();
}
文件复制的方式思路比较相似,首先定义复制文件配置类,由于在异步回调中用到该配置类的属性,所以新增了文件流对象和相应的字节数组
/// <summary>
/// 异步读文件方法
/// </summary>
/// <param name="result"></param>
private void End_ReadFileCallBack(IAsyncResult result)
{
//得到先前的配置文件
var config = result.AsyncState as CopyFileConfig;
//结束异步读
config?.OriginalFileStream.EndRead(result);
//异步读后立即写入新文件地址
if (config != null)
{
FileStream copyStream = new FileStream(config.DestinationFileUrl, FileMode.CreateNew, FileAccess.Write, FileShare.Write, , true);
using (copyStream)
{
Console.WriteLine("异步复制原文件地址:{0}", config.OriginalFileStream.Name);
Console.WriteLine("复制后的新文件地址:{0}", config.DestinationFileUrl);
//调用异步写方法callBack方法为End_CreateFileCallBack,参数是copyStream
copyStream.BeginWrite(config.OriginalFileBytes, , config.OriginalFileBytes.Length,
End_CreateFileCallBack, copyStream);
}
}
}
然后在FileStreamTest类中新增一个Copy方法实现文件的复制功能
/// <summary>
/// 复制文件
/// </summary>
/// <param name="config"></param>
public void Copy(IFileConfig config)
{
lock (_lockObject)
{
//得到CopyFileConfig对象
var copyFileConfig = config as CopyFileConfig;
if (copyFileConfig == null)
{
return;
}
//创建同步或异步流
FileStream stream = copyFileConfig.IsAsync
? new FileStream(copyFileConfig.OriginalFileUrl, FileMode.Open, FileAccess.Read, FileShare.Read,
, true)
: new FileStream(copyFileConfig.OriginalFileUrl, FileMode.Open);
//定义一个byte数组接收从原文件读取的byte数据
byte[] originalFileBytes = new byte[stream.Length];
using (stream)
{
//如果异步流
if (stream.IsAsync)
{
//将该流和流独处的byte[]数据放入配置类,在callback中可以使用
copyFileConfig.OriginalFileStream = stream;
copyFileConfig.OriginalFileBytes = originalFileBytes;
if (stream.CanRead)
{
//异步开始读取,读取完后进入End_ReadFileCallBack方法,该方法接收copyFileConfig参数
stream.BeginRead(originalFileBytes, , originalFileBytes.Length, End_ReadFileCallBack,
copyFileConfig);
}
else//否则同步读取
{
if (stream.CanRead)
{
//读取原文件
stream.Read(originalFileBytes, , originalFileBytes.Length);
}
//定义一个写流,在新位置中创建一个文件
FileStream copyStream = new FileStream(copyFileConfig.DestinationFileUrl, FileMode.CreateNew);
using (copyStream)
{
//将原文件的内容写进新文件
copyStream.Write(originalFileBytes, , originalFileBytes.Length);
}
} 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 (config != null)
{
FileStream copyStream = new FileStream(config.DestinationFileUrl, FileMode.CreateNew, FileAccess.Write, FileShare.Write, , true);
using (copyStream)
{
Console.WriteLine("异步复制原文件地址:{0}", config.OriginalFileStream.Name);
Console.WriteLine("复制后的新文件地址:{0}", config.DestinationFileUrl);
//调用异步写方法callBack方法为End_CreateFileCallBack,参数是copyStream
copyStream.BeginWrite(config.OriginalFileBytes, , config.OriginalFileBytes.Length,
End_CreateFileCallBack, copyStream);
}
}
}
最有让我们在Main函数调用一下:
static void Main(string[] args)
{
//文件操作测试
FileStreamTest test = new FileStreamTest();
//创建文件配置类
CreateFileConfig createFileConfig = new CreateFileConfig
{
CreateUrl = @"E:\自己的\MyTest\Word\新建的.txt",
IsAsync = true
};
//复制文件配置类
CopyFileConfig copyFileConfig = new CopyFileConfig
{
OriginalFileUrl = @"E:\自己的\MyTest\Word\TextFile1.txt",
DestinationFileUrl = @"E:\自己的\MyTest\Word\TextFile1-副本.txt",
IsAsync = true
};
//test.Create(createFileConfig);
test.Copy(copyFileConfig);
Console.ReadKey();
}
输出结果:
好了,FileStream的相关知识就分享到这里了。
FileStream相关知识分享的更多相关文章
- MemoryStream相关知识分享
一.简单介绍一下MemoryStream MemoryStream是内存流,为系统内存提供读写操作,由于MemoryStream是通过无符号字节数组组成的,可以说MemoryStream的性能可以算比 ...
- 【Stream—6】BufferedStream相关知识分享
一.简单介绍以下BufferedStream 在前几章的讲述中,我们已经能够掌握流的基本特性和特点,一般进行对流的处理时,系统肩负着IO所带来的开销,调用十分频繁,这时候就应该想个办法减少这种开销,而 ...
- 【Stream—7】NetworkStream相关知识分享
一.NetworkStream的作用 和先前的流有所不同,NetworkStream的特殊性可以在它的命名空间中得以了解(System.Net.Sockets),聪明的你马上就会反应过来:既然是在网络 ...
- StreamWriter 相关知识分享
在介绍StreamWriter之前,我们首先来了解一下它的父类TextWriter. 一.TextWriter 1.TextWriter的构造函数和常用属性方法 下面是TextWriter的构造函数: ...
- XML的相关基础知识分享(二)
前面我们讲了一下XML相关的基础知识(一),下面我们在加深一下,看一下XML高级方面. 一.命名空间 1.命名冲突 XML命名空间提供避免元素冲突的方法. 命名冲突:在XML中,元素名称是由开发者定义 ...
- XML的相关基础知识分享
XML和Json是两种最常用的在网络中数据传输的数据序列化格式,随着时代的变迁,XML序列化用于网络传输也逐渐被Json取代,前几天,单位系统集成开发对接接口时,发现大部分都用的WebService技 ...
- 关于StreamReader的知识分享
今天我们来简单的介绍一下StreamReader,在将StreamReader之前,我们先来了解一下他的父类:TextReader.对于TextReader,大家可能比较陌生,下面我们来看一下Text ...
- listener监听器的相关知识
从别人的博客上我学习了listener的相关知识现在分享给大家 1.概念: 监听器就是一个实现特定接口的普通java程序,这个程序专门用于监听另一个java对象的方法调用或属性改变,当被监听对象发生上 ...
- 分享吉林大学机械科学与工程学院,zhao jun 博士的Halcon学习过程及知识分享
分享吉林大学机械科学与工程学院,zhao jun 博士的Halcon学习过程及知识分享 全文转载zhao jun 博士的新浪博客,版权为zhaojun博士所有 原文地址:http://blog.sin ...
随机推荐
- python入门经典_好资源送不停
Python入门经典(2K超清_送书) https://study.163.com/course/courseMain.htm?courseId=1006183019&share=2& ...
- 算法学习之剑指offer(十)
一 题目描述 请实现一个函数用来判断字符串是否表示数值(包括整数和小数).例如,字符串"+100","5e2","-123","3 ...
- Java8新特性之Lambda
为什么要Lambda Java8应该是目前最大的一次更新了,更新后我们迎来了很多新特性,其中便包括Lambda表达式,函数式编程的思想正式进入Java,让我们看一个经典案例. 例1 按照两个人的年龄排 ...
- luogu P4035 [JSOI2008]球形空间产生器
[返回模拟退火略解] 题目描述 今有 n+1n+1n+1 个 nnn 维的点,它们都在一个球上.求它们所在球的球心. Solution 4035\text{Solution 4035}Solution ...
- SpringBoot项目配置Tomcat和JVM参数
设置Tomcat端口号和连接数等 使用application.properties配置文件有一些参数无法设置,所以推荐创建一个类文件来配置,如下: package com.qipai.springbe ...
- LeetCode初级算法--树01:二叉树的最大深度
LeetCode初级算法--树01:二叉树的最大深度 搜索微信公众号:'AI-ming3526'或者'计算机视觉这件小事' 获取更多算法.机器学习干货 csdn:https://blog.csdn.n ...
- Springboot项目的jar包目录结构
上图为一个由Luyten工具反编译后的一个jar包 说明: SpringBoot提供的bootstrap的类是放到包的最外面,比如上面的org.springframework.boot.loader. ...
- pytest中unicode编码问题(如test_fix.py::Test1::test_s1[\u6d4b\u8bd5-\u6d4b\u8bd5])
现象: 采用如下方式可将其正确显示为中文 ss = r"test_fix.py::Test1::test_s1[\u6d4b\u8bd5-\u6d4b\u8bd5]" print( ...
- SpringBoot系列教程之Bean加载顺序之错误使用姿势辟谣
在网上查询 Bean 的加载顺序时,看到了大量的文章中使用@Order注解的方式来控制 bean 的加载顺序,不知道写这些的博文的同学自己有没有实际的验证过,本文希望通过指出这些错误的使用姿势,让观文 ...
- 不想用锐捷怎么办?锐捷出问题|锐捷不能用怎么办?用menohust代替吧
首先获取 MentoHUST(代替锐捷网络认证客户端) V4.1.0.2001 绿色免费版 解压到任意目录 用管理员身份 启动 安装&卸载 .bat(右键用管理员运行) 这个文件可能乱码了 ...