3

我有一个程序,它存储了一堆包含许多 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)是否已经内置了一些东西来将双精度转换为字符数组(我想这将达到一些参数化的精度?)。理想情况下想要传入我的数组和一些索引并让它开始写入那里。

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

干杯

4

5 回答 5

3

文件是否需要某种文本格式?

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

有关详细信息,请参阅http://msdn.microsoft.com/en-us/library/system.io.binarywriter.aspx

于 2012-06-06T12:29:39.080 回答
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(';'); 

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

编辑在其他答案中发布的自定义双写:c# double to character array or alternative

于 2012-06-06T12:56:22.823 回答
1

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

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

您可以使用性能监视器来了解有关应用程序如何使用托管堆的更多信息。您使用了 CLRProfiler,它是一种更高级的工具,因此您可能不会学到任何新东西。

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

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

无论您调用什么重载将 a 格式化Double为 aStringBuilder调用最终都会导致Double.ToString被调用。StringBuilder.AppendFormat直接格式化到缓冲区中,而不分配额外的格式化字符串,因此就内存使用而言,StringBuilder.AppendFormatStringBuilder.Append和两个重载都将分配一个格式化字符串Double作为格式化过程的一部分。但是,StringBuilder.AppendFormat会装箱,Double因为它接受一个params Object[]数组。使用StringBuilder.Append接受 a 的重载Double不会遇到这个问题。

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

于 2012-06-06T14:12:38.367 回答
1

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

using(var writer = new StreamWriter(...))
{
   writer.Write(x.A);
   writer.Write(";");
   writer.Write(x.B);
   writer.Write(";");
   writer.Write(x.C);
}
于 2012-06-06T12:56:54.747 回答
1

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

        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);
            }
        }
于 2012-06-06T20:02:06.297 回答