C# 文件流读写以及进度回调

待兔 等级 494 0 0

前不久遇到一个问题,是公司早期的基础库遇到的,其实很低级,但是还是记录下来。出错点是一个 IO 流的写入bug,我们项目会有一种专有的数据格式,这个格式的奇葩点在于如果设置 IO 读缓冲区为 2014 字节的时候,整个文件刚好能读完,也就是说其 length 刚好是 1024 的倍数。后来在一次升级中增加了更多的文件格式,并且新的文件格式使用了新的自定义写入流,具有加密和压缩的作用,这样一来,文件的长度就不一定是 1024 的倍数了。

后来通过查看这个基础类的源代码发现因为是 .NET 2.0 时代的东西,也没有 Stream.Copy 的方法,于是当时的程序员手动写了个 Stream.Copy 的方法,我稍作改动为了更直观将输出流改为输出到文件,代码大概如下:

var fs_in = System.IO.File.OpenRead(@"C:\3.0.6.apk");
var fs_out = System.IO.File.OpenWrite(@"C:\3.0.6.apk.copy");
byte[] buffer = new byte[1024];
while (fs_in.Read(buffer,0,buffer.Length)>0)
{
    fs_out.Write(buffer, 0, buffer.Length);
}
Console.WriteLine("复制完成"); 

所以一眼就能看出这个方法简直有天大的 bug ,假设文件长度不为 1024 的倍数,永远会在文件尾部多补充上一段的冗余数据。

diff.PNG

这里整整多出了 878 字节的数据,导致整个文件都不对了,明显是基础知识都没学好。

增加一个变量保存实际读取到的字节数,改为如下:

var fs_in = System.IO.File.OpenRead(@"C:\迅雷下载\3.0.6.apk");
var fs_out = System.IO.File.OpenWrite(@"C:\迅雷下载\3.0.6.apk.copy");
byte[] buffer = new byte[1024];
int readBytes = 0;
while ((readBytes= fs_in.Read(buffer, 0, buffer.Length)) >0)
{
    fs_out.Write(buffer, 0, readBytes);
}
Console.WriteLine("复制完成"); 

对于处理大型文件,一般都有进度指示,比如处理压缩了百分多少之类的,这里我们也可以加上,比如专门写一个方法用于文件读取并返回 byte[] 和百分比。

static void ReadFile(string filename,int bufferLength, Action<byte[],int> callback)
{
    if (!System.IO.File.Exists(filename)) return;
    if (callback == null) return;
    System.IO.FileInfo finfo = new System.IO.FileInfo(filename);
    long fileLength = finfo.Length;
    long totalReadBytes = 0;
    var fs_in = System.IO.File.OpenRead(filename);
    byte[] buffer = new byte[bufferLength];
    int readBytes = 0;
    while ((readBytes = fs_in.Read(buffer, 0, buffer.Length)) > 0)
    {
        byte[] data = new byte[readBytes];
        Array.Copy(buffer, data, readBytes);
        totalReadBytes += readBytes;
        int percent = (int)((totalReadBytes / (double)fileLength) * 100);
        callback(data, percent);
    }
} 

调用就很简单了:

static void Main(string[] args)
{
    string filename = @"C:\3.0.6.apk";
    var fs_in = System.IO.File.OpenRead(filename);
    long ttc = 0;
    ReadFile(filename, 1024, (byte[] data, int percent) => 
    {
        ttc += data.Length;
        Console.SetCursorPosition(0, 0);
        Console.Write(percent+"%");
    });
    Console.WriteLine("len:"+ttc);
    Console.ReadKey();
} 

这是基于文件读取的,稍微改一下就可以改成输入流输出流的,这里就不贴出来了。文件读写非常耗时,特别是大文件,IO 和 网络请求都是 “重操作”,所以建议大家 IO 都放在单独的线程去执行。C# 中可以使用 Task、Thread、如果同时有多个线程需要执行就用 ThreadPool 或 Task,Java 或 Android 中用 Thread 或线程池,以及非常流行的 RxJava 等等 ...

收藏
评论区

相关推荐

Retrofit 支持suspend函数源码分析
Retrofit 2.6.0 之后支持接口suspend函数配合协程使用,举个例子: ApiService java interface LoginApiService : BaseService { @GET("/wxarticle/chapters/json") suspend fun getChapters(): BaseResp
C# 文件流读写以及进度回调
前不久遇到一个问题,是公司早期的基础库遇到的,其实很低级,但是还是记录下来。出错点是一个 IO 流的写入bug,我们项目会有一种专有的数据格式,这个格式的奇葩点在于如果设置 IO 读缓冲区为 2014 字节的时候,整个文件刚好能读完,也就是说其 length 刚好是 1024 的倍数。后来在一次升级中增加了更多的文件格式,并且新的文件格式使用了新的自定义写入
js 的 forEach,map,filter,some,every,find(es6),reduce详解
forEach() 定义和用法 forEach()方法用于调用数组的每个元素,并将元素传递给回调函数 注意: forEach()对于空数组是不会执行回调函数的。 语法 array.forEach(function(currentValue,index,arr),thisValue) 参数 function(currentValue,index,a
浅谈promise和js执行机制(一)
作为一个入门级前端,今天是一个非常值得纪念的日子,因为这是我第一次在论坛上发表帖子,作为起步。虽然我觉得自己水平还是十分的有限,对一些细节的理解还不是很透彻,但是还是要迈出这一步,不管是给别的新手作为学习参考,还是自己以后回顾,总觉得需要把自己的成长记录下来,希望自己以后还是要多坚持,如果有不对的地方还是希望大家及时提出来,共同进步 今天有时间翻到了
重新认识Activity—Activity的生命周期
Activity的生命周期概念 当用户浏览,退出和返回应用时,Activity实例会在其生命周期中转换为不同的状态。在生命周期回调方法中,可以声明用户离开并重新进入Activity时Activity的行为方式。每个回调允许执行适合于给定状态更改的特定工作。生命周期回调的良好实现可以帮助确保您的应用程序避免: 如果用户在使用您的App时接到电话或切换
golang 分析调试高阶技巧
layout: post title: “golang 调试高阶技巧” date: 2020603 1:44:09 0800 categories: golang GC 垃圾回收 golang 高阶调试 Golang tools nm compile
React 组件通信之发布订阅模式
React 通信 react的数据流是单向的, react 通信有以下几种方式: 父向子通信: 传入props 子向父通信:父组件向子组件传一个函数,然后通过这个函数的回调,拿到子组件传过来的值 父向孙通信:利用context传值。React.createContext() 兄弟间通信: ​ 1、找一个相同的父组件,既可以用pr
CDN分发回源流程简单介绍
目录前言正文 前言CDN的全称是“Content Delivery Network”,中文叫内容分发网络。CDN是一个经过策略性部署的整体系统,包括分布式存储、负载均衡、网络请求的重定向和内容管理四个重要部分,其中,内容管理和全局的网络流量管理(Traffic Management)是CDN的核心所在。通过对用户就近性和服务器负载的判断,CDN确保内容以
天池比赛数据挖掘心电图模型调参
Task4 建模与调参 4.1 学习目标 学习机器学习模型的建模过程与调参流程 完成相应学习打卡任务 4.2 内容介绍 逻辑回归模型: 理解逻辑回归模型; 逻辑回归模型的应用; 逻辑回归的优缺点; 树模型: 理解树模型; 树模型的应用; 树模型的优缺点; 集成模型 基于bagging思想的集成
Promise从入门到拿Offer之手写Promise
1、Promise构造函数的实现Promise构造函数用来声明示例对象,需要传入一个执行器函数。其中包括resolve函数和reject函数,以及几个重要的属性:状态属性、结果属性和回调函数队列。构造函数的基本框架 resolve函数用于异步处理成功后调用的函数。其中包括验证对象状态修改次数,修改promise实例对象状态,异步调用成功的回调函数
Vue 的 三种 watcher
userwatcher在页面中使用的watcher,即用户定义的watcher,用于观察一个属性的更新,支持数组定义多个,对象定义单个的形式,在initWatcher中进行watcher的初始化之后,在渲染函数进行数据的读取,触发依赖收集时会将userwatcher的依赖收集进去,data属性set更新时会被触发userwatcher所定义的回调函数(将新旧
一起学重绘和回流
前言Hello,大家好,我是Symbol卢,最近也比较忙,原本打算继续更新 的文章,收到了大家的反馈帮助到了一些同学(一同学习的朋友),也是真的很开心😄 但是刚好遇见这次的征文由于某些原因就先写了这篇文章(偷偷的说:“用大白话轻松搞定正则 的下 也快写完了”),因为我的成长道路上也有很多的前辈给我很大的帮助,所以我会继续的传承下去;重绘和回流也是面试当中经常
面试避坑手册之 Java字节流和字符流总结IO流!
从接收输入值说起在日常的开发应用中,有时候需要直接接收外部设备如键盘等的输入值,而对于这种数据的接收方式,我们一般有三种方法:字节流读取,字符流读取,Scanner 工具类读取。 字节流读取直接看一个例子:cpublic class Demo01SystemIn public static void main(String[] args) throw
JAVA回调机制(CallBack)之小红是怎样买到房子的??
JAVA回调机制CallBack 序言最近学习java,接触到了回调机制(CallBack)。初识时感觉比较混乱,而且在网上搜索到的相关的讲解,要么一言带过,要么说的比较单纯的像是给CallBack做了一个定义。当然了,我在理解了回调之后,再去看网上的各种讲解,确实没什么问题。但是,对于初学的我来说,缺了一个循序渐进的过程。此处,将我对回调机制的个人理解,按
阿里P8面试官都说太详细了,你值得拥有
阿里P8级架构师第九篇:千亿流量高并发高可用分布式系统之数据治理篇 阿里P8级架构师第十篇:千亿流量高并发高可用分布式系统之人工智能加成篇 数据融合模块1. 构建画像模块2. 召回策略模块3. 排序模型模块ctr预估4. 微服务模块5. AB Test模块6. Spark调优模块7. 推荐系统落地实践 阿里P8级架构师第十一篇:千亿流量高并发高