使用C#读写结构化的二进制文件

最近工作上遇到一个问题,就是有将近200万个CSV文件,每个CSV文件包含了成千上万条实验数据,CSV以一个不连续的整数值作为文件名,比如:1.CSV、2.CSV、3.CSV、5.CSV等等。另外又有200万个XML文件,每个XML文件的文件名与CSV的文件名一一对应,在这些XML文件中,定义了所对应的CSV实验数据文件的实验描述信息(比如实验名称、实验类型等等),也就是说,每个XML包含的是它所对应的CSV文件的元数据。现在的一个需求是,当软件中列出其中一部分(比如几千个或者几万个)CSV文件时,需要在每个文件名边上显示对应的实验名称。

咋一看这样的需求,感觉比较简单,当显示某个CSV文件时,直接找到对应的XML文件,解析XML得到名称就结束了。然而,问题是:

  1. 这就需要软件本身自带这200万个XML文件,文件数量太大,如果压缩成ZIP,ZIP的尺寸也相对较大,在程序请求实验名称时还需要解压,性能极差

  2. 解析XML本身需要损耗一定的性能,如果要显示成百上千个CSV对应的实验名称,那么需要对每个XML进行解析,性能也很不理想

在此,我介绍一种方法,通过预处理的方式,将所需信息提取成结构化的数据结构(Structured Data Structure),然后通过索引进行快速定位。

问题分析

虽然XML文件数量比较大,每个XML文件提供的信息也比较多,但是我们所需要的信息仅仅就是XML文件中的实验名称,因此,一个思路就是,首先对所有XML文件进行预处理,然后提取实验名称,并将其保存到另一个文件中。当需要根据CSV文件名获取实验名称时,就查询这个实验名称数据文件,然后显示对应的实验名称。这里的问题是,使用哪种格式来产生实验名称数据文件呢?我们又有几个选择:

  1. 使用JSON,存储“CSV文件名<—>实验名称”的键值对,这样性能也不会很好,因为这样的键值对有200万个,解析JSON文件本身的CPU和IO负载会很高

  2. 使用桌面数据库,比如SQLite,这样做需要应用程序内建一个SQLite的引擎,它本身存在CPU架构的问题(x86,x64),而且中间封了一层数据库访问操作,性能也不见得特别高

  3. 自定义存储结构,这种做法比较灵活,但是需要自己实现,有一定的难度,出问题的几率也相对较大

综合分析,我们还是打算选择第三个方案,自己定义数据的存储结构。

假设CSV文件名是连续的,比如是从1.CSV、2.CSV一直到2000000.CSV,那么我们可以将CSV的文件名数值作为索引值,通过查表法找到对应的实验名称字符串即可。比如,在内存中有以下字符串数组:

640?wx_fmt=png

假设CSV文件名为1535.CSV,那么我们只需要assayNames[1534]即可获得第1535个CSV(也就是1535.CSV)所对应的实验名称。这样做的效率是非常高的,它直接利用了数组的索引。然而,现实并不是那么美好:

  1. 我们不可能把200万条数据全部放在一个数组内存中,这样做消耗内存会非常高

  2. 原始CSV文件的文件名标号并不是连续的

解决问题一的方式比较直白:我们需要将数据放在磁盘中,然后按需访问;对于问题二,我们需要引入数据库实现中的一个概念:索引。

解决问题

假设每条实验名称数据被当成一条长度固定的记录存放在二进制文件中,但由于文件名中数值标识并不连续,因此,无法简单地通过文件名来推断数据记录的位置(也就是数组的下标值),比如:

640?wx_fmt=png

对于1.csv、2.csv尚有规律可寻,实验名称数据记录在二进制文件中的位置,就是文件名数值减1,从4.csv开始,后面的位置值就与文件名没什么关系了。此时,我们需要有一个映射,来定义文件名中的数值与数据记录位置之间的关系。为此,我引入了另一个二进制文件,其中定义了200万条记录,每条记录仅占4个字节,每条记录(每4个字节)保存的是以该记录的偏移值作为文件名数值的CSV文件,所对应的实验名称数据记录在上述二进制文件中的记录位置。比如:

640?wx_fmt=png

那么,假设CSV文件的文件名为4.csv,于是,可以首先找到索引文件中偏移值为4(也就是index=3)的记录位置值(也就是2),然后,在二进制文件中定位到索引值为2的记录,就是4.csv所对应的实验名称数据。

代码实现

我使用System.Runtime.InteropServices命名空间下的Marshal类和GCHandle类,配合System.IO命名空间下的BinaryReader、BinaryWriter类来实现结构化二进制文件的读取和写入。封装代码如下:


public static class BinaryFileHelper
{
    public static T ReadStruct<T>(BinaryReader binaryReader, int idx = 0)
        where T : struct
    {
        var buff = new byte[Marshal.SizeOf<T>()];
        if (binaryReader.BaseStream.CanSeek)
        {
            binaryReader.BaseStream.Seek(idx * buff.Length, SeekOrigin.Begin);
            binaryReader.BaseStream.Read(buff, 0, buff.Length);
        }
        var gcHandle = GCHandle.Alloc(buff, GCHandleType.Pinned);
        try
        {
            var result = Marshal.PtrToStructure<T>(gcHandle.AddrOfPinnedObject());
            return result;
        }
        finally
        {
            gcHandle.Free();
        }
    }
    public static void WriteStruct<T>(BinaryWriter binaryWriter, T item)
        where T : struct
    {
        var buff = new byte[Marshal.SizeOf<T>()];
        var gcHandle = GCHandle.Alloc(buff, GCHandleType.Pinned);
        try
        {
            Marshal.StructureToPtr<T>(item, gcHandle.AddrOfPinnedObject(), false);
            binaryWriter.Write(buff, 0, buff.Length);
        }
        finally
        {
            gcHandle.Free();
        }
    }
}

接下来,再写一个测试程序来测试结构化二进制文件的读取性能:


[StructLayout(LayoutKind.Explicit)]
public struct AssayNameStructuredIndex
{
    [FieldOffset(0)]
    [MarshalAs(UnmanagedType.U4, SizeConst = 4)]
    public int Index;
}
[StructLayout(LayoutKind.Explicit)]
public struct AssayNameStructuredRecord
{
    [FieldOffset(0)]
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
    public string Name;
}
static void Main(string[] args)
{
    var stopwatch = new Stopwatch();
    using (var recordFileStream = new FileStream("assayNames.bin", FileMode.Open, FileAccess.Read))
    using (var indexFileStream = new FileStream("assayNames.idx", FileMode.Open, FileAccess.Read))
    using (var recordReader = new BinaryReader(recordFileStream))
    using (var indexReader = new BinaryReader(indexFileStream))
    {
        while (true)
        {
            Console.Write("请输入CSV文件名(直接回车退出程序):");
            var line = Console.ReadLine();
            if (string.IsNullOrEmpty(line)) break;
            if (!int.TryParse(Path.GetFileNameWithoutExtension(line), out var identifier)) continue;
            stopwatch.Restart();
            var indexValue = BinaryFileHelper.ReadStruct<AssayNameStructuredIndex>(indexReader, identifier);
            if (indexValue.Index == -1)
            {
                Console.WriteLine($"数据文件中未包含{line}的记录。");
                Console.WriteLine();
                continue;
            }
            var assayNameValue = BinaryFileHelper.ReadStruct<AssayNameStructuredRecord>(recordReader, indexValue.Index);
            stopwatch.Stop();
            Console.WriteLine($"耗时:{stopwatch.ElapsedMilliseconds}毫秒,实验名称:{assayNameValue.Name}。");
            Console.WriteLine();
        }
    }
}

执行结果如下:

640?wx_fmt=png

可以看到,无论CSV文件名中的数值是大还是小,从近200万条数据中读取实验名称信息的速度都是非常快的,基本上也就是零点几个毫秒,达到了预期的目标。

总结

所谓之结构化的数据,就是表示每条数据所占用的存储空间都是一致的,也就是每条记录所占用的字节数是相等的,这样才能非常容易地通过记录的索引值以及每条记录的大小来计算位置偏移量,从而快速读取数据。这是一种空间换时间的方案,一个明显的问题是,需要根据实际数据来合理选择每条记录所占用的存储空间:如果太大,那么200多万条记录累积起来,会占用大量存储空间,造成空间浪费;如果太小,又会导致某些数据无法正确存储,造成信息丢失。因此,本文介绍的方案还是需要根据实际情况进行斟酌,选择合理的记录存储结构。

原文地址: http://sunnycoding.cn/2018/07/04/accessing-structural-binary-file-using-csharp/


.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com

640?wx_fmt=jpeg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/320217.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

牛客IOI周赛16-普及组

比赛链接 文章目录求导题目描述题解&#xff1a;猜数题意&#xff1a;题解&#xff1a;方法一 贪心方法二 暴力答题卡题意&#xff1a;题解&#xff1a;代码&#xff1a;求导 链接&#xff1a; 时间限制&#xff1a;C/C 1秒&#xff0c;其他语言2秒 空间限制&#xff1a;C/C …

Pipelines - .NET中的新IO API指引(二)

原文&#xff1a;Pipelines - a guided tour of the new IO API in .NET, part 2作者&#xff1a;marcgravell在上一章&#xff0c;我们讨论了以往的StreamAPI中存在的一些问题&#xff0c;并且介绍了Pipe,PipeWriter,PipeReader 等API&#xff0c;研究如何写出一个Pipe 并且从…

[XSY] 线图(树形DP、分类讨论)

线图 如图&#xff0c;每个L(L(T))上的点对应T上的一条三点链 在连接L(L(T))上两点&#xff0c;当且仅当两点代表的三点链在T上有共边&#xff0c;且边权为 共边边权*2非共边1边权非共边2边权 在L(L(T))上从点u走到点v&#xff0c;等价于u代表的三点链在T上删掉自己的一条边&…

牛客网【每日一题】4月29日题目精讲 Symmetric Matrix

链接&#xff1a; 时间限制&#xff1a;C/C 1秒&#xff0c;其他语言2秒 空间限制&#xff1a;C/C 524288K&#xff0c;其他语言1048576K 64bit IO Format: %lld题目描述 输入描述: The input consists of several test cases and is terminated by end-of-file. Each test c…

邻接矩阵和邻接表的使用

邻接矩阵和邻接表的使用 邻接矩阵 为了遍历一个图&#xff0c;我们使用了邻接矩阵&#xff0c;及用ai,ja_{i,j}ai,j​表示由a到b的边权 注&#xff1a;若这两个点不相连或ijijij&#xff0c;那么这个值就会设定为一个非正常的值&#xff0c;以便遍历时特判不走这条边 使用&am…

一个迄今为止最快的并发键值存储库FASTER诞生

FASTER在过去十年中&#xff0c;云中的数据密集型应用程序和服务有了巨大的增长。数据在各种边设施&#xff08;例如&#xff0c;设备&#xff0c;浏览器和服务器&#xff09;上创建&#xff0c;并由云应用程序处理用来获得数据价值或做出决策。应用程序和服务可以处理收集的数…

牛客网【每日一题】4月30日题目精讲 换个角度思考

链接&#xff1a; 文章目录题目描述题解&#xff1a;主席树做法&#xff1a;代码&#xff1a;树状数组&#xff1a;更扯淡的方法&#xff01;&#xff01;&#xff01;时间限制&#xff1a;C/C 1秒&#xff0c;其他语言2秒 空间限制&#xff1a;C/C 262144K&#xff0c;其他语言…

三元环计数四元环计数

三元环计数 问题 给出一张n个点m条边的无向图&#xff0c;问图中有多少个三元组{ u , v , w } &#xff0c;满足图中存在 { (u,v) , (v,w) , (w,u) } 三条边。 求解 Step1 定向 将所有点按 度数 从小到大排序&#xff0c;如果度数相同按 点编号 从小到大排序&#xff0c;u…

牛客网【每日一题】5月1日题目 [SCOI2012]滑雪与时间胶囊

链接&#xff1a; 时间限制&#xff1a;C/C 2秒&#xff0c;其他语言4秒 空间限制&#xff1a;C/C 262144K&#xff0c;其他语言524288K 64bit IO Format: %lld题目描述 a180285非常喜欢滑雪。他来到一座雪山&#xff0c;这里分布着M条供滑行的轨道和N个轨道之间的交点&#x…

漫谈单体架构与微服务架构(上):单体架构

最近微服务架构特别火爆&#xff0c;就跟人工智能、区块链一样&#xff0c;软件架构设计如果不提微服务&#xff0c;感觉就像是与世界先进的架构风格和开发技术脱了节似的&#xff0c;各方各面都无法彰显高大上的气质。本来再打算使用一套系列文章来讨论微服务的方方面面&#…

ASP.NET Core应用程序的参数配置及使用

应用程序的开发不仅仅是写代码这点事情。假设你正在开发一个能够支持多次部署的微服务&#xff0c;此时你就需要有一个合理的应用程序配置方案&#xff0c;以便在开发和生产环境中能够方便地选用不同的配置参数&#xff0c;并且能够在部署到容器服务&#xff08;比如ACS或者Kub…

“Shopee杯” e起来编程暨武汉大学2020年大学生程序设计大赛决赛(重现赛)

比赛链接 文章目录A题 A Simple Problem about election题目描述题解&#xff1a;代码&#xff1a;D题 Deploy the medical team题意&#xff1a;题解&#xff1a;代码&#xff1a;F题 Figure out the sequence题意&#xff1a;题解&#xff1a;代码A题 A Simple Problem about…

RabbitMQ一个简单可靠的方案(.Net Core实现)

前言最近需要使用到消息队列相关技术&#xff0c;于是重新接触RabbitMQ。其中遇到了不少可靠性方面的问题&#xff0c;归纳了一下&#xff0c;大概有以下几种&#xff1a;1. 临时异常&#xff0c;如数据库网络闪断、http请求临时失效等&#xff1b;2. 时序异常&#xff0c;如A任…

牛牛染颜色

链接&#xff1a; 文章目录题目描述题意&#xff1a;题解&#xff1a;核心代码&#xff1a;时间限制&#xff1a;C/C 1秒&#xff0c;其他语言2秒 空间限制&#xff1a;C/C 131072K&#xff0c;其他语言262144K 64bit IO Format: %lld题目描述 牛牛最近得到了一颗树&#xff0…

.netcore consul实现服务注册与发现-单节点部署

一、Consul的基础介绍Consul是HashiCorp公司推出的开源工具&#xff0c;用于实现分布式系统的服务发现与配置。与其他分布式服务注册与发现的方案&#xff0c;比如 Airbnb的SmartStack等相比&#xff0c;Consul的方案更“一站式”&#xff0c;内置了服务注册与发现框 架、分布一…

分布式事务解决方案以及 .Net Core 下的实现(上)

数据一致性是构建业务系统需要考虑的重要问题 &#xff0c; 以往我们是依靠数据库来保证数据的一致性。但是在微服务架构以及分布式环境下实现数据一致性是一个很有挑战的的问题。最近在研究分布式事物&#xff0c;分布式的解决方案有很多解决方案&#xff0c;也让我在研究的同…

微软MVP张善友告诉你,微服务选型要注意这些地方

周六的下午&#xff0c;广州周大福金融中心的写字楼静悄悄的&#xff0c;53楼的实盈多功能会议室却异常火爆&#xff0c;热闹非凡。来自广州各大科技公司的技术小伙伴们齐聚一堂&#xff0c;他们都在期待着一个人&#xff0c;那就是——微软MVP张善友和他带来的 .NET Core 微服…

像鱼

链接&#xff1a; 时间限制&#xff1a;C/C 1秒&#xff0c;其他语言2秒 空间限制&#xff1a;C/C 262144K&#xff0c;其他语言524288K 64bit IO Format: %lld题目描述 给你一个边长为 n 的用硬币摆成的实心三角形&#xff0c;请问把他倒过来最少需要多少步&#xff1f; 例子…

[ZJOI2005]午餐(贪心+dp)

首先若只有一个窗口&#xff0c;利用贪心&#xff0c;按吃饭时间从大到小排序即可 正确性证明&#xff1a; 定义 eat[i] 第i个人的吃饭时间&#xff0c;time[i] 第i个人的打饭时间 延长时间T[i]max(eat[i]- ∑ji1ntimej\sum\limits_{ji1}^ntime_jji1∑n​timej​ ,0) 最后…

【图论】【模板】静态仙人掌(luogu 5236)

【模板】静态仙人掌 题目大意 给你一个无向仙人掌图&#xff08;保证每条边至多出现在一个简单回路中的无向图&#xff09;&#xff0c;问你两个点之间的最短路距离 输入样例#1 9 10 2 1 2 1 1 4 1 3 4 1 2 3 1 3 7 1 7 8 2 7 9 2 1 5 3 1 6 4 5 6 1 1 9 5 7输出样例#1 5 …