如何去理解 FileStream ?

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

FileStream 的重要性

FileStream 顾名思义文件流,我们电脑上的文件都可以通过文件流进行操作,例如文件的复制,剪切,粘贴,删除, 本地文件上传,下载,等许多重要的功能都离不开文件流,所以文件流不仅在本机上非常重要,在如今的网络世界也是万万不能缺少的。

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

FileStream 常用构造函数

FileStream(SafeFileHandle, FileAccess)

参数 SafeFileHandle是当前 FileStream 对象将封装的文件的文件句柄。这样的解释可能大家一头雾水,别急,大家先不要去理睬这深邃的含义,只要知道这个类型是c#非托管资源,它能够调用非托管资源的方法,而且不属于c#回收机制,所以我们必须使用GC手动或其他方式(Finalize 或Dispose方法)进行非托管资源的回收,所以SafeFileHandle是个默默无闻的保镖 ,一直暗中保护FileStream和文件的安全。

为了能让大家更好的理解这个保镖,请看第一段代码:

static void Main(string[] args)
{
    FileStream fs = new FileStream(@"d:\000.txt", FileMode.OpenOrCreate);          
    Console.ReadLine();
    
    // 下面这条代码将会抛出异常
    File.Delete(@"d:\000.txt");
}

会什么会报错呢?因为FileStream并没有被释放,系统不知道这个文件是否还有用﹐所以帮我们保护这个文件。(非托管资源SafeFileHandle所使用的内存还被程序占用着) 所以SafeFileHandled 在内部保护了这个文件从而报出了这个异常,如果我们将流关闭后,这个问题也就不存在了。

static void Main(string[] args)
{
    FileStream fs = new FileStream(@"d:\000.txt", FileMode.OpenOrCreate);
    var data = fs.SafeFileHandle;            
    Console.ReadLine();
    fs.Close();
    File.Delete(@"d:\000.txt");
}

可以看见stream.SafeFileHandle的IsClose属性变成true了,也就是说这时候可以安全的删除文件了。所以又回到了一个老问题上面,我们每次使用完FileStream后都必须将他关闭并释放资源。

FileStream(String, FileMode)

String:文件所在的地址

FIleMode:是个枚举,表示如何打开或创建文件

成员名称

说明

Append 打开现有文件并查找到文件尾,或创建新文件。FileMode.Append 只能同 FileAccess.Write 一起使用。
Create 指定操作系统应创建新文件。如果文件已存在,它将被改写。(等效于这样的请求:如果文件不存在,则使用 CreateNew;否则使用 Truncate。)
CreateNew 指定操作系统应创建新文件。如果文件已存在,则将引发 IOException。
Open 指定操作系统应打开现有文件。如果该文件不存在,则引发 System.IO.FileNotFoundException。
OpenOrCreate 指定操作系统应打开文件(如果文件存在);否则,应创建新文件。
Truncate 指定操作系统应打开现有文件。文件一旦打开,就将被截断为零字节大小。试图从使用 Truncate 打开的文件中进行读取将导致异常。

FileStream(IntPtr, FileAccess, Boolean ownsHandle)

FileAccess:是一个枚举, 表示对于该文件的操作权限。

ReadWrite 对文件的读访问和写访问。可从文件读取数据和将数据写入文件。
Write 文件的写访问。可将数据写入文件。同 Read组合即构成读写访问权。
Read 对文件的读访问。可从文件中读取数据。同 Write组合即构成读写访问权。

ownsHandle:类似于前面和大家介绍的SafeFileHandler。

对于指定的文件句柄,操作系统不允许所请求的 access,例如,access 为 Write 或 ReadWrite 而文件句柄设置为只读访问时,会报出异常所以 ownsHandle 才是老大,FileAccess的权限应该在ownsHandle的范围之内。

FileStream(String, FileMode, FileAccess, FileShare)

FileShare:同样是个枚举类型:确定文件如何由进程共享。

Delete 允许随后删除文件。
Inheritable 使文件句柄可由子进程继承。Win32 不直接支持此功能
None 谢绝共享当前文件。文件关闭前,打开该文件的任何请求(由此进程或另一进程发出的请求)都将失败。
Read 允许随后打开文件读取。如果未指定此标志,则文件关闭前,任何打开该文件以进行读取的请求(由此进程或另一进程发出的请求)都将失败。但是,即使指定了此标志,仍可能需要附加权限才能够访问该文件。
ReadWrite 允许随后打开文件读取或写入。如果未指定此标志,则文件关闭前,任何打开该文件以进行读取或写入的请求(由此进程或另一进程发出)都将失败。但是,即使指定了此标志,仍可能需要附加权限才能够访问该文件。
Write 允许随后打开文件写入。如果未指定此标志,则文件关闭前,任何打开该文件以进行写入的请求(由此进程或另一进过程发出的请求)都将失败。但是,即使指定了此标志,仍可能需要附加权限才能够访问该文件。

FileStream(String, FileMode, FileAccess, FileShare, Int32, Boolean async )

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

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

FileStream(String, FileMode, FileAccess, FileShare, Int32, FileOptions)

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

FileStream 常用属性

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

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

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

Name:FileStream的名字,只读属性

ReadTimeout :设置读取超时时间

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

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独有的方法

FileSecurity  GetAccessControl()

不是很常用,FileSecurity 是文件安全类,直接表达当前文件的访问控制列表(ACL)的符合当前文件权限的项目。

void Lock(long position,long length)

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

void SetAccessControl(FileSecurity fileSecurity)

和GetAccessControl很相似。

void Unlock (long position,long length)

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

示例:文件本地分段上传

将一个文件作为整体进行操作,这样会带来一个问题,当文件很大或者网络不是很稳定的时候会发生意想不到的错误。那我们该怎么解决这一问题呢?其实有种思路还是不错的,那就是分段传输:

做一下这个示例进行测试:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
 
namespace ConsoleApplication1
{
    /// <summary>
    /// 文件分段上传示例
    /// </summary>
    class UpFileSingleTest
    {
        /// <summary>
        /// 定义Buffer为1000
        /// </summary>
        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)
        {
            // 每次临时读取数据数
            int tempReadCount = 0;
            int tempBuffer = 0;
 
            // 定义一个缓冲区数组
            byte[] bufferByteArray = new byte[BUFFER_COUNT];
 
            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(@"d:\902421284.rmvb");
    // 取得文件总长度
    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(@"d:\902421284.rmvb", @"d:\902421284_new.rmvb", startPosition, i == divide ? divide : totalCount);
    }
}

分段传输比直接传输复杂许多,再加入多线程,这样的话每段数据的传输都能通过一个线程单独处理,能够提升上传性能和速度。

Stream Part.4相关推荐

  1. stream流对象的理解及使用

    我的理解:用stream流式处理数据,将数据用一个一个方法去 . (点,即调用) 得到新的数据结果,可以一步达成. 有多种方式生成 Stream Source: 从 Collection 和数组 Co ...

  2. Cuda Stream流 分析

    Cuda Stream流分析 Stream 一般来说,cuda c并行性表现在下面两个层面上: • Kernel level • Grid level Stream和event简介 Cuda stre ...

  3. CUDA 7 Stream流简化并发性

    CUDA 7 Stream流简化并发性 异构计算是指高效地使用系统中的所有处理器,包括 CPU 和 GPU .为此,应用程序必须在多个处理器上并发执行函数. CUDA 应用程序通过在 streams ...

  4. 关于Adodb.Stream的使用说明

    组件:"Adodb.Stream" 有下列方法: Cancel 方法      使用方法如下      Object.Cancel      说明:取消执行挂起的异步 Execut ...

  5. stream map方法_Java Stream中map和flatMap方法

    最近看到一篇讲stream语法的文章,学习Java中map()和flatMap()方法之间的区别. 虽然看起来这两种方法都做同样的事情,都是做的映射操作,但实际上差之毫厘谬以千里. 通过演示Demo中 ...

  6. stream流map 多个字段_stream流根据对象指定字段去重

    先封装一个去重的方法 import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.fun ...

  7. Java8 Stream应用:Map合并、过滤、遍历、values int求和等

    1. Java多个Map合并 // 多个Map<Long,Integer>, 根据key相同的,value累积求和: public static Map mapCombine(List&l ...

  8. stream filter 用法_JDK1.8新特性Stream和Collectors19个常用示例总结

    关于Stream和Collectors的用法,这应该是总结的最全的文章了,大家可以收藏一下. 一:简介 java.util.Stream 表示能应用在一组元素上一次执行的操作序列.Stream 操作分 ...

  9. RTMP协议中的Chunk Stream ID (CID)的作用

    一.协议分层 RTMP包是以Message的结构封装的,结构如下所示: 1)Message Type ID在1-7的消息用于协议控制,这些消息一般是RTMP协议自身管理要使用的消息,用户一般情况下无需 ...

  10. java父子表_数据库二维表转父子关系,java,stream,list

    需求描述:把数据库中的省市二维表,查询到内存中后,转换为父子层级关系.通过jdk8中的stream方式实现. 数据关系: 320004    福建省    320507    南平市 430000   ...

最新文章

  1. ECLIPSE在线安装SVN
  2. Tensorflow:TF模型文件(checkpoint文件夹下ckpt文件之data、index、meta)保存、模型导入、恢复并fine-tuning之详细攻略
  3. cuda第一次计算耗时_CUDA优化的冷知识10 | GPU卡和Jetson上显存优化的特色
  4. 总是想逃避不想去面对(又是发牢骚的一天)
  5. python3的float数精度_python浮点数精度问题
  6. 27、jdbc操作数据库(4)
  7. 谷歌翻译无法连接网络_window10无法连接网络
  8. python中os模块详解_Python OS模块(常见文件操作示例)
  9. vue页面乱码_项目部署到weblogic后页面乱码问题
  10. win10如何修改鼠标指针样式
  11. echarts 世界地图标点_echarts中国地图3D各个城市标点demo
  12. 01读书笔记:《编码》-隐匿在计算机软硬件背后的语言(01-11章)
  13. 关于lombok和mapstruct整合报无参构造函数错误
  14. 取消iphone 自动更新提示
  15. C语言之#include用法详解
  16. 关于联想笔记本不能连接无线网(wifi),注销后重新登录才可以连接
  17. Error connecting to SMTP server “localhost: 25“centos+sendmail+squirrelmail:server error :(13)
  18. vue集成汉字转拼音(附多音字解决方案)
  19. qq一笔画c语言,qq红包一笔画图形大全答案合集下载
  20. 心理学计算机学什么,心理学专业学什么 都有哪些课程

热门文章

  1. 让AI个性化而且功耗更低 IBM研发新型神经网络芯片
  2. OpenAI最新研究:如何通过无监督学习提升「自然语言理解能力」?
  3. Jeff Dean晒Google Brain团队2017成绩单!
  4. MIT 的新型开源系统 Taco 将数据分析速度提升 100 倍 !(附论文)
  5. 这几家公司有个梦想:开发AI操作系统,让外行也成为人工智能大师
  6. 如果编程语言是飞机 | 每日趣闻
  7. “我辞职了,决定全职去开发我的操作系统!”
  8. 腾讯程序员最爱 C++,每年写 3 万行代码,70% 的技术 Leader 仍在持续编码
  9. 项目管理利器taiga快速安装
  10. bos开发时,测试卡在登录界面解决