【问题标题】:Split array into size limited CSV strings将数组拆分为大小有限的 CSV 字符串
【发布时间】:2012-02-17 12:18:38
【问题描述】:

我正在寻找一种将大型 int[] 转换为 csv 字符串的 string[] 的有效方法,其中每个 csv 限制为最多 4000 个字符。数组中的值可以是 1 到 int.MaxValue 之间的任何值。

这是我的最终代码:

public static string[] GetCSVsFromArray(int[] array, int csvLimit)
{
    List<string> parts = new List<string>();
    StringBuilder sb = new StringBuilder();
    foreach(int id in array)
    {
        string intId = id.ToString();
        if (sb.Length + intId.Length < csvLimit)
            sb.Append(intId).Append(",");
        else
        {
            if (sb.Length > 0)
                sb.Length--;
            parts.Add(sb.ToString());
            sb.Length = 0;
        }
    }
    if(sb.Length>0)
       parts.Add(sb.ToString());
    return parts.ToArray();
}

有没有更有效的方法来做到这一点?

这就是我现在使用的(我能够将返回参数更改为 List 类型以在最后保存 ToArray() 调用):

public static List<string> GetCSVsFromArray(int[] array, int csvLimit)
{
    List<string> parts = new List<string>();
    StringBuilder sb = new StringBuilder();
    foreach(int id in array)
    {
        string intId = id.ToString();
        if (sb.Length + intId.Length < csvLimit)
            sb.Append(intId).Append(",");
        else
        {
            if (sb.Length > 0)
                sb.Length--;
            parts.Add(sb.ToString());
            sb.Length = 0;
        }
    }
    if(sb.Length>0)
       parts.Add(sb.ToString());
    return parts;
}

性能结果:

10,000,000 个项目 csv 限制为 4000 个字符

  • 原文:2,887.488ms
  • GetIntegerDigitCount: 3105.355ms
  • 最终:2883.587ms

虽然在我的开发人员机器上删除 ToArray() 调用只节省了 4 毫秒,但这似乎在速度慢得多的机器上产生了显着差异(在 DELL D620 上节省了 200 多毫秒)

【问题讨论】:

  • 你在做多余的parts.ToArray(),它为引擎盖下的所有项目做内存复制
  • 你在新建一行的时候把intId的值扔掉了?
  • 为什么我觉得你是rolling your own CSV parser?请不要那样做。不要写这样的代码。
  • csv 是我无法更改的存储过程的输入参数 (nvarchar(4000))。

标签: c# .net-3.5 refactoring


【解决方案1】:

在为每个数字创建一个新字符串以计算位数时,您正在分配大量堆内存。使用following method计算数字中的位数(见下面的方法)。

所以不是

string intId = id.ToString();
if (sb.Length + intId.Length < csvLimit)

只需使用:

if (sb.Length + this.GetIntegerDigitCount(id) < csvLimit)

结果:

  • 处理 1000 万个号码时速度提高 2 倍
  • 旧:4316ms,新:1983ms,差异:2333ms。更快 217.6%

编辑:更多关于大型 csv 限制的结果

项目:10000000; csvLimit:4000;旧版:2091 毫秒,新版:1868 毫秒,差异:223 毫秒 更快 = 111.937901498929%


我用来测量时间的代码:

 double elapsedOld = 0;
 double elapsedNew = 0;
 int count = 10000000;
 int csvLimit = 4000;
 var items = Enumerable.Range(0, count).ToArray();
 var watch = Stopwatch.StartNew();
 this.GetCsVsFromArray(items, csvLimit);
 watch.Stop();
 elapsedOld = watch.ElapsedMilliseconds;

 watch = Stopwatch.StartNew();
 this.GetCsVsFromArrayTuned(items, csvLimit);
 watch.Stop();
 elapsedNew = watch.ElapsedMilliseconds;
 var stat = String.Format(
     "Items:{0}; csvLimit:{1}; Old:{2}ms, New:{3}ms, Diff:{4}ms faster = {5}%",                
     count,
     csvLimit,
     elapsedOld,
     elapsedNew,
     elapsedOld - elapsedNew,
     elapsedOld * 100 / elapsedNew);

GetIntegerDigitCount

public int GetIntegerDigitCount(int valueInt)
{
    double value = valueInt;
    int sign = 0;
    if (value < 0)
    {
        value = -value;
        sign = 1;
    }

    if (value <= 9)
    {
        return sign + 1;
    }

    if (value <= 99)
    {
        return sign + 2;
    }

    if (value <= 999)
    {
        return sign + 3;
    }

    if (value <= 9999)
    {
        return sign + 4;
    }

    if (value <= 99999)
    {
        return sign + 5;
    }

    if (value <= 999999)
    {
        return sign + 6;
    }

    if (value <= 9999999)
    {
        return sign + 7;
    }

    if (value <= 99999999)
    {
        return sign + 8;
    }

    if (value <= 999999999)
    {
        return sign + 9;
    }

    return sign + 10;
}

【讨论】:

  • ToString 不仅仅是为了获取长度,sb.Append(Int32) 在内部对 int 做了一个 ToString。您的代码看起来更快,因为您的字符串限制为 4,这意味着您不对任何大于 9999 的数字执行 ToString。将 csvLimit 设置为 4000 并检查结果。
  • 我已经尝试过 csvlimit=4000,请参阅我的答案的编辑部分,基本上是以下统计信息:Items:10000000; csvLimit:4000; Old:2091ms, New:1868ms, Diff:223ms faster = 111.937901498929%
  • 真的你是对的,在大的 CSV 限制值差异非常小。并感谢关于 StringBuilder 的内部 ToString() 调用的一个好点!
  • @JohnHunder :您是否以某种方式改进了算法?知道什么是最终解决方案以及性能有何差异真的很有趣。谢谢
  • 虽然我发现 getintegerdigitcount 版本稍慢(将 int 复制到 double 似乎是其根源),但原则上这是一个好主意。此外,您对删除 ToArray() 调用的评论是我能找到的最大的性能提升。
【解决方案2】:

这里的 Linq 可以加快速度。稍作修改后,您的代码将如下所示:

    public static string[] GetCSVsFromArray(int[] array, int csvLimit)
    {
        List<string> parts = new List<string>();
        StringBuilder sb = new StringBuilder();
        foreach (string intId in array.Select(id => id.ToString()))
        {
            if (sb.Length + intId.Length < csvLimit)
                sb.Append(intId).Append(",");
            else
            {
                if (sb.Length > 0)
                    sb.Length--; parts.Add(sb.ToString()); sb.Length = 0;
            }
        }
        return parts.ToArray();
    }

【讨论】:

  • 你有没有检查过这个每次我尝试的性能总是比我原来的慢一点?
  • 我没有。您是否记录了与其性能相关的任何数据?
  • 0-10000000,限制 4000 - 原始 1770ms,你的 1843ms; 0-10000000,限制8000 - 原来的1808ms,你的1855ms; 10000000-20000000,限制8000 - 原始3971ms,你的4197ms
  • 谢谢。我会研究它以优化它。
  • 通过替换foreach语句检查foreach (string intId in Enumerable.Select(array, id =&gt; id.ToString()))
【解决方案3】:
using System.Linq;    

public static string[] GetCSVsFromArray(int[] array, int limit)
{
    int i = 0;
    return array.Select(a => a.ToString())
                .GroupBy(a => { i += a.Length; return (i - a.Length) / limit; })
                .Select(a => string.Join(",",a))
                .ToArray();
}

【讨论】:

  • 您是否尝试过启动此代码?一些性能指标Items: 1000000. Old: 404ms, New:1844ms, Diff: -1440ms,非常慢。从性能角度来看,缩短代码 sn-p 并不总是更快
  • 请告诉我输入数组的大小
  • 查看我的第一条评论 - 项目:1000000
  • 您可以尝试将 string.join 替换为 Aggregate 吗?
猜你喜欢
  • 2022-07-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-01
  • 1970-01-01
  • 2012-02-22
相关资源
最近更新 更多