一种快速生成CSV的方法

事情是这个样子的

在QQ群在聊把如何100万数据导出成CSV文件?会不会很慢?

俺回了一句"现在的机器性能好,没啥问题"。

然后大家开始谈论机器的配置了。哎,俺的机器配置有点差。

然后俺就进行了一个测试。

测试数据

数据定义

public struct Rec

{

public int v1;

public int v2;

}

private Rec[] Data;

这里为啥不用class呢,因为这个场景struct 更简单。

测试数据生成

Data = new Rec[200*10000];

Random r = new Random(DateTime.Now.Millisecond);

for(int i= 0;i < Data.Length;i++)

{

Data[i].v1 = r.Next();

Data[i].v2 = r.Next();

}

测试数据200万行,每行2个数字。

测试结果

测试程序为 .NET Framework 4.8 64 Release 版本

如果用.Net 8 版本进行测试,除了方法3之外,会快一些。方法3速度基本不变。

开始写代码

方法一

这是最最普通的生成CSV的方法,就是一个一个数据的写入到CSV中。没啥可说的。就是用来当做参照物的,耗时0.536秒

cs 复制代码
            string fn = @"1.csv";  
            FileStream fs = new FileStream(fn, FileMode.Create);
            DateTime t1 = DateTime.Now;
            byte[] buff;
            byte[] buff_rn = ASCIIEncoding.ASCII.GetBytes("\r\n");
            byte[] buff_split = ASCIIEncoding.ASCII.GetBytes(",");
            string s ="v1,v2";
            buff = ASCIIEncoding.ASCII.GetBytes(s);
            fs.Write(buff,0,buff.Length);
            foreach (Rec i in Data)
            {
                fs.Write(buff_rn, 0, buff_rn.Length); 

                s = i.v1.ToString();
                buff = ASCIIEncoding.ASCII.GetBytes(s);
                fs.Write(buff, 0, buff.Length);

                fs.Write(buff_split, 0, buff_split.Length);

                s = i.v2.ToString();
                buff = ASCIIEncoding.ASCII.GetBytes(s);
                fs.Write(buff, 0, buff.Length);  
            }
            
            fs.Flush();
            fs.Close();
            fs.Dispose();
            DateTime t2 = DateTime.Now;
            label1.Text=("输出"+Data.Length.ToString()+"行CSV文件,耗时" +((t2 - t1).TotalMilliseconds/1000).ToString("0.000")+"秒");
 

方法二

在方法1上加了一个8M的缓存。其实这种写法在其他语言(c++、delphi 等)还有效果,C#效果不大,因为C#的文件流本身有优惠。耗时0.503秒

cs 复制代码
            string fn = @"2.csv"; 
            int ms_size = 8 * 1024 * 1024;
            byte[] ms=new byte[ms_size + 1024];
            int ms_position = 0;
            FileStream fs = new FileStream(fn, FileMode.Create);
            DateTime t1 = DateTime.Now;
            byte[] buff;
            byte[] buff_rn = ASCIIEncoding.ASCII.GetBytes("\r\n");
            byte[] buff_split = ASCIIEncoding.ASCII.GetBytes(",");
            string s = "v1,v2";
            buff = ASCIIEncoding.ASCII.GetBytes(s);
            Array.Copy(buff, 0, ms, ms_position, buff.Length);
            ms_position+= buff.Length;
            foreach (Rec i in Data)
            {
                Array.Copy(buff_rn, 0, ms, ms_position, buff_rn.Length);
                ms_position += buff_rn.Length; 

                s = i.v1.ToString();
                buff = ASCIIEncoding.ASCII.GetBytes(s); 
                Array.Copy(buff, 0, ms, ms_position, buff.Length);
                ms_position += buff.Length;
                 
                Array.Copy(buff_split, 0, ms, ms_position, buff_split.Length);
                ms_position += buff_split.Length;

                s = i.v2.ToString();
                buff = ASCIIEncoding.ASCII.GetBytes(s);
                Array.Copy(buff, 0, ms, ms_position, buff.Length);
                ms_position += buff.Length;

                if (ms_position > ms_size)
                { 
                    fs.Write(ms,0, ms_position); 
                    ms_position = 0;
                }
            }
            if (ms_position > 0)
            {
                fs.Write(ms, 0, ms_position);
            }
            fs.Flush();
            fs.Close();
            fs.Dispose();
            DateTime t2 = DateTime.Now;
            label2.Text = ("输出" + Data.Length.ToString() + "行CSV文件,耗时" + ((t2 - t1).TotalMilliseconds / 1000).ToString("0.000") + "秒");

优化

我们先分析一下,生成CSV中,主要的计算是啥?在这个测试上主要计算就是,整型转字符串。执行了400万次(200万行,每行2个数字)整型转字符串。那么我们就先测试一下执行了400万次整型转字符串多少时间。

cs 复制代码
            string s = "";
            DateTime t1 = DateTime.Now;
            foreach (Rec i in Data)
            {
                s = i.v1.ToString();
                s = i.v2.ToString();
            }
            DateTime t2 = DateTime.Now;
            label4.Text = ( (Data.Length*2).ToString() + "次ToString,耗时" + ((t2 - t1).TotalMilliseconds / 1000).ToString("0.000") + "秒");

耗时是0.265秒

那么我们就像从 整型转字符串 开始优化。

方法三

使用了 IntToString 函数,速度优化到了 **耗时0.176秒。**优化到这里出现了一个有趣的数据:

只执行 整型转字符串 耗时是0.265秒 ,方法三的速度比只 整型转字符串还快。为什么呢?

因为在 方法三 中,其实没有使用字符串,取消中间商赚差价。

cs 复制代码
           string fn = @"3.csv";
            int ms_size = 8 * 1024 * 1024;
            byte[] ms = new byte[ms_size + 1024];
            int ms_position = 0;
            FileStream fs = new FileStream(fn, FileMode.Create);
            DateTime t1 = DateTime.Now;
            byte[] buff;
            byte[] buff_rn = ASCIIEncoding.ASCII.GetBytes("\r\n");
            byte[] buff_split = ASCIIEncoding.ASCII.GetBytes(",");
            string s = "v1,v2";
            buff = ASCIIEncoding.ASCII.GetBytes(s);
            Array.Copy(buff, 0, ms, ms_position, buff.Length);
            ms_position += buff.Length;
            foreach (Rec i in Data)
            {
                Array.Copy(buff_rn, 0, ms, ms_position, buff_rn.Length);
                ms_position += buff_rn.Length;

                IntToString(i.v1,ref ms, ref ms_position); 


                Array.Copy(buff_split, 0, ms, ms_position, buff_split.Length);
                ms_position += buff_split.Length;

                IntToString(i.v2, ref ms, ref ms_position);

                if (ms_position > ms_size)
                {
                    fs.Write(ms, 0, ms_position);
                    ms_position = 0;
                }
            }
            if (ms_position > 0)
            {
                fs.Write(ms, 0, ms_position);
            }
            fs.Flush();
            fs.Close();
            fs.Dispose();
            DateTime t2 = DateTime.Now;
            label3.Text = ("输出" + Data.Length.ToString() + "行CSV文件,耗时" + ((t2 - t1).TotalMilliseconds / 1000).ToString("0.000") + "秒");
 

这个代码的功能 其实就个 int.ToString。 看上去下面这个代码有点拉胯,像是那种故意搞笑的代码,是的确实拉胯,但是它确实快。

cs 复制代码
        public static void IntToString(int n,ref byte[] buff,ref int position)
        {
            int len = 0;
            int len_inc = 16;
            int p = position+ len_inc;
            do
            {
                buff[p] = (byte)((n % 10)+48);
                p += 1;
                len += 1;
            }
            while ((n /= 10) != 0);
            p -= 1;
            for (int i = 0; i < len;i++)
            {
                buff[position]= buff[p];
                position += 1;
                p -= 1;
            } 
        }

其他

Release 版本 和 Debug版本差别很大,例如 方法三 的Debug版 速度并不快,但是用Release 版本,方法3就明显快。

使用.Net 8 版本进行测试,结果如下

.NET Framework 4.8 .Net 8 对比
方法1 0.538 0.257 .Net 8 快很多
方法2 0.503 0.190 .Net 8 快很多
方法3 0.176 0.178 两者差不多
整型转字符串 0.265 0.075 .Net 8 快很多

上面的数据进行时测试,不是很准确。但是可以看出 :

.Net 8 在 整型转字符串上要快很多。方法三因为没有用 .ToString() 所以 速度在.NET Framework 4.8 和 .Net 8 下差不多。

相关推荐
FPGA3 分钟前
探讨4B/5B编码、8B/10B编码区别以及FPGA实现
数据结构
秋难降3 分钟前
线段树的深度解析(最长递增子序列类解题步骤)
数据结构·python·算法
楚韵天工25 分钟前
基于GIS的无人机模拟飞行控制系统设计与实现
深度学习·算法·深度优先·无人机·广度优先·迭代加深·图搜索算法
binqian37 分钟前
【异步】js中异步的实现方式 async await /Promise / Generator
开发语言·前端·javascript
林开落L1 小时前
库制作与原理(下)
linux·开发语言·centos·库制作与原理
fengfuyao9851 小时前
基于MATLAB的GUI实现人脸检测、眼睛检测以及LBP直方图显示
开发语言·计算机视觉·matlab
你也向往长安城吗1 小时前
推荐一个三维导航库:three-pathfinding-3d
javascript·算法
★YUI★2 小时前
学习游戏制作记录(玩家掉落系统,删除物品功能和独特物品)8.17
java·学习·游戏·unity·c#
百度智能云2 小时前
VectorDB+FastGPT一站式构建:智能知识库与企业级对话系统实战
算法
CHANG_THE_WORLD2 小时前
# C++ 中的 `string_view` 和 `span`:现代安全视图指南
开发语言·c++