【问题标题】:c# double to character array or alternativec# double 到字符数组或替代
【发布时间】:2012-06-06 12:19:05
【问题描述】:

我有一个程序,它存储了一堆包含许多 double 类型成员的结构实例。我经常将这些转储到一个文件中,我正在使用字符串生成器,例如:

 StringBuilder builder = new StringBuilder(256);

 builder.AppendFormat("{0};{1};{2};", x.A.ToString(), x.B.ToString(), x.C.ToString()); 

其中 'x' 是我的类型的一个实例,而 A、B、C 是双精度类型 X 的成员。我在每一个上调用 ToString() 以避免装箱。但是,这些对 ToString 的调用仍会在我的应用程序上下文中分配大量内存,我想减少这种情况。我在想的是有一个字符数组,并将每个成员直接写入其中,然后从该字符数组创建一个字符串并将其转储到文件中。几个问题:

1) 我的想法听起来合理吗?有没有人知道已经实现了类似的东西?

2) 是否已经内置了一些东西可以将双精度转换为字符数组(我猜这会达到一些参数化的精度?)。理想情况下想要传入我的数组和一些索引并让它开始写入那里。

我尝试这样做的原因是在我的应用程序运行时减少内存中的大峰值,因为我运行许多实例并且经常发现自己受到内存的限制。

干杯 一个

【问题讨论】:

  • StringBuilder 对于像这样的简单连接来说太过分了。
  • 你为什么不简单地使用:builder.AppendFormat("{0};{1};{2};", x.A, x.B, x.C) 来代替?
  • @asawyer 其实还有很多会员,这只是举例
  • @user555265 在 int 上调用 ToString() 应该将其装箱,因为它很可能会覆盖 object.ToString() 的实现。见stackoverflow.com/questions/3499651/boxing-a-thing-of-the-past
  • @MartinLiversage 我知道,但不是 Double.ToString() 导致 builder.AppendFormat("{0};{1};{2};", x.A, x.B, x.C ),它是 StringBuilder.AppendFormat(string, object[]) (msdn.microsoft.com/en-us/library/cazfhf32)。另外,不要忘记我们在这里传递了一个数组。除了装箱的双精度数之外,该数组也分配在堆上。

标签: c# memory tostring


【解决方案1】:

文件必须是某种文本格式吗?

如果没有,到目前为止,最有效的方法是使用 BinaryWriter(和 BinaryReader 读取它们)。

更多信息请参见http://msdn.microsoft.com/en-us/library/system.io.binarywriter.aspx。

【讨论】:

  • 不幸的是理想的文本:-( .. 但如果我没有找到任何其他解决方案,我可以探索这个,所以谢谢!
【解决方案2】:

如果可以直接写入文本文件,则可以使用 steamwriter 编写强类型结构。我没有测试内存使用情况,但我认为它们应该是有效的

        using (var tw = new System.IO.StreamWriter("filename", true)) //(true to append to file)
        {
            tw.Write(x.A);
            tw.Write(';');
        }

如果需要字符串生成器,也可以使用以下方法调用强类型重载:

        builder.Append(x.A) //strongly typed as long as the field is a system type
            .Append(';')
            .Append(x.B)
            .Append(';'); 

当然,这两种方法在实现某种循环或委托时看起来会更好,但这与装箱逻辑无关。

EDIT 自定义双重写作张贴在其他答案:c# double to character array or alternative

【讨论】:

  • tw.Write(x.A) 会调用 ToString() 我相信:msdn.microsoft.com/en-us/library/ek5h49e6.aspx
  • 没错,stringbuilder 重载也是如此。假设那些强类型将具有自定义行为以提高性能,我并没有看那么远。我摆弄了一下,并通过一些自定义试用添加了另一个答案。
【解决方案3】:

您应该直接写入文件流以减少内存使用率。

using(var writer = new StreamWriter(...))
{
   writer.Write(x.A);
   writer.Write(";");
   writer.Write(x.B);
   writer.Write(";");
   writer.Write(x.C);
}

【讨论】:

  • 但这只会调用 ToString 虽然我认为:msdn.microsoft.com/en-us/library/ek5h49e6.aspx
  • ToString() 无论如何都会被调用,但这种方式避免了字符串生成器引入的额外数组和字符串。
  • 是的,这可能是一个公平的观点。在我的特殊情况下,它确实是(从使用 CLRProfiler 到配置文件分配)分配大量内存的 ToString() 操作,所以这些是我真正想要优化的
  • 是的..我基本上写了一个CSV,稍后在excel中打开
  • 我调查过了,没有字符串分配是没有办法写双重内容的。您可以编写自己的代码将 double 转换为 char 数组,但我确定这是开销!
【解决方案4】:

您确定是对Double.ToString 的多次调用导致您的内存问题吗?每个字符串都应该在下一代 0 收集时被收集,而 .NET 垃圾收集器在这方面非常有效。

如果您创建的字符串超过 85K,它们将在大对象堆上创建,这可能会增加您的应用程序所需的总内存,即使大字符串只是暂时存在(大对象堆碎片)。

您可以使用性能监视器来详细了解您的应用程序如何使用托管堆。您使用了 CLRProfiler,它是一个更高级的工具,所以您可能不会学到任何新东西。

StringBuilder 是在内存中构建字符串的正确类,但如果您只是在内存中构建字符串以便稍后将其写入文件,您应该使用StreamWriter 直接写入文件。

StringBuilder 将不得不扩展用于存储字符串的缓冲区,您可以通过提前设置 StringBuilder 的容量来避免这种额外开销(您已经在示例代码中这样做了)。

无论您调用何种重载将Double 格式化为StringBuilder,调用最终都会导致Double.ToString 被调用。 StringBuilder.AppendFormat 直接格式化到缓冲区而不分配额外的格式化字符串,因此在内存使用方面StringBuilder.AppendFormat 与StringBuilder.Append 一样好,并且两个重载都将分配格式化为Double 的字符串作为格式化过程的一部分.但是,StringBuilder.AppendFormat 将装箱 Double,因为它接受 params Object[] 数组。使用接受Double 的StringBuilder.Append 重载不会遇到此问题。

如果您确定知道Double.ToString 是您的内存问题的根源,我相信您最好的选择是编写自己的浮点格式化代码,该代码可以将浮点数直接写入StringBuilder。这项任务并不简单,但您可以从开源 C 库中获得灵感。

【讨论】:

  • 是的 .. 在 CLR Profiler 中,我可以看到 Double::ToString String() 是罪魁祸首。性能..谢谢!
【解决方案5】:

出于对如何进行的纯粹好奇,我忍不住尝试创建一个可以直接编写双打的场景。下面是结果。我没有对它或任何东西进行基准测试,但它在我运行的(有限)测试中确实按预期工作。

        double[] test = { 8.99999999, -4, 34.567, -234.2354, 2.34, 500.8 };
        using (var sw = new FileStream(@"c:\temp\test.txt", FileMode.Create))
        {
            using (var bw = new BinaryWriter(sw))
            {
                const byte semicol = 59, minus = 45, dec = 46, b0 = 48;

                Action<double> write = d =>
                {
                    if (d == 0)
                        bw.Write(b0);
                    else
                    {
                        if (d < 0)
                        {
                            bw.Write(minus);
                            d = -d;
                        }

                        double m = Math.Pow(10d, Math.Truncate(Math.Log10(d)));
                        while(true)
                        {
                            var r = ((decimal)(d / m) % 10); //decimal because of floating point errors
                            if (r == 0) break;
                            if (m == 0.1)
                                bw.Write(dec); //decimal point
                            bw.Write((byte)(48 + r));         
                            m /= 10d;
                        }
                    }

                    bw.Write(semicol);
                };

                foreach (var d in test)
                    write(d);
            }
        }

【讨论】:

  • 我认为 while 循环中的 (r==0) 会导致您退出,例如如果您在写最后一个 9 之前在中间 0 上写出 8.909 ..
  • 糟糕,这是因为解析为 (int) (补充说,在测试遇到的浮点问题时,在小数转换之前),如果仍然有小数,模应该总是返回至少一个分数要处理。我已经编辑了帖子并从 var r = (int)((decimal)(d / m) % 10) 中删除了 (int) 转换;
猜你喜欢
  • 1970-01-01
  • 2021-12-31
  • 2017-09-16
  • 2019-02-26
  • 2020-03-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多