将 StringBuilder 写入流中

62
什么是将StringBuilder写入System.IO.Stream的最佳方法?
我目前正在执行以下操作:
StringBuilder message = new StringBuilder("All your base");
message.Append(" are belong to us");

System.IO.MemoryStream stream = new System.IO.MemoryStream();
System.Text.ASCIIEncoding encoding = new ASCIIEncoding();
stream.Write(encoder.GetBytes(message.ToString()), 0, message.Length);
6个回答

95

如果你要写入流,请不要使用StringBuilder,而是使用StreamWriter直接写入:

using (var memoryStream = new MemoryStream())
using (var writer = new StreamWriter(memoryStream ))
{
    // Various for loops etc as necessary that will ultimately do this:
    writer.Write(...);
}

11
如果他在将字符串写入之前需要将其用于其他用途,那么这就不可行。 - Skurmedel
11
完全正确,但当然他没有说那是必要条件,所以我没有做出任何假设。 - Neil Barnwell
1
在从流中读取之前,还要记得Flush and Seek - styfle

18

那是最好的方法。否则就放弃StringBuilder,使用以下类似的内容:

using (MemoryStream ms = new MemoryStream())
{
    using (StreamWriter sw = new StreamWriter(ms, Encoding.Unicode))
    {
        sw.WriteLine("dirty world.");
    }
    //do somthing with ms
}

7
也许这会有用。
var sb= new StringBuilder("All your money");
sb.Append(" are belong to us, dude.");
var myString = sb.ToString();
var myByteArray = System.Text.Encoding.UTF8.GetBytes(myString);
var ms = new MemoryStream(myByteArray);
// Do what you need with MemoryStream

7
需要翻译的内容:Copy first to a string, then copy again to a byte array, then finally copy to the stream? Really? Surely this can be done succinctly without making any intermediate copy. (What if the string builder is 2GB?)您是否真的需要先复制到一个字符串,然后再复制到字节数组,最后再复制到流中?这样做必须要进行中间步骤的复制吗?如果字符串构建器达到了2GB,怎么办?相信可以通过更简洁的方法来实现。 - Ian Goldby

6

编辑:如@ramon-smits所指出,如果您可以访问StringBuilder.GetChunks(),那么您也将可以访问StreamWriter.WriteAsync(StringBuilder)。 因此,您可以改为执行以下操作:

StringBuilder stringBuilder = new StringBuilder();
// Write data to StringBuilder...
Stream stream = GetStream(); // Get output stream from somewhere.
using (var streamWriter = new StreamWriter(stream, Encoding.UTF8, leaveOpen: true))
{
    await streamWriter.WriteAsync(stringBuilder);
}

更简单。


最近我需要做这件事情,但在这个问题中找到的答案都不尽人意。

你可以将一个 StringBuilder 写入流中而无需将整个字符串实例化:

StringBuilder stringBuilder = new StringBuilder();
// Write data to StringBuilder...
Stream stream = GetStream(); // Get output stream from somewhere.
using (var streamWriter = new StreamWriter(stream, Encoding.UTF8, leaveOpen: true))
{
    foreach (ReadOnlyMemory<char> chunk in stringBuilder.GetChunks())
    {
        await streamWriter.WriteAsync(chunk);
    }
}
<注意>这个API(StringBuilder.GetChunks())仅在.NET Core 3.0及以上版本中可用。 <如果此操作频繁发生,您可以通过使用StringBuilder对象池进一步减少GC压力。>

1
这是从 StringBuilder 提取数据的一种很酷的方法。一个警告是,这不是线程安全的,因此您应该在此代码块周围放置锁定。https://learn.microsoft.com/en-us/dotnet/api/system.text.stringbuilder.getchunks - AnthonyVO
1
我认为这不是必需的,你可以直接使用streamWriter.WriteAsync(stringBuilder) - Ramon Smits

5
根据您的使用情况,使用StringWriter也许更合适:
StringBuilder sb = null;

// StringWriter - a TextWriter backed by a StringBuilder
using (var writer = new StringWriter())
{
    writer.WriteLine("Blah");
    . . .
    sb = writer.GetStringBuilder(); // Get the backing StringBuilder out
}

// Do whatever you want with the StringBuilder

1
相当确定:https://msdn.microsoft.com/zh-cn/library/system.io.stringwriter.getstringbuilder(v=vs.110).aspx - Chris Moschini

1

如果您想使用类似StringBuilder的东西,因为它更容易传递和处理,则可以使用我创建的以下StringBuilder替代。

它最重要的不同之处在于,它允许访问内部数据,而无需首先将其组装成String或ByteArray。这意味着您不必将内存要求加倍,并冒着尝试分配符合整个对象的连续内存块的风险。

注意:我相信有比在内部使用List<string>()更好的选项,但这很简单并且已经足够满足我的需求。

public class StringBuilderEx
{
    List<string> data = new List<string>();
    public void Append(string input)
    {
        data.Add(input);
    }
    public void AppendLine(string input)
    {
        data.Add(input + "\n");
    }
    public void AppendLine()
    {
        data.Add("\n");
    }

    /// <summary>
    /// Copies all data to a String.
    /// Warning: Will fail with an OutOfMemoryException if the data is too
    /// large to fit into a single contiguous string.
    /// </summary>
    public override string ToString()
    {
        return String.Join("", data);
    }

    /// <summary>
    /// Process Each section of the data in place.   This avoids the
    /// memory pressure of exporting everything to another contiguous
    /// block of memory before processing.
    /// </summary>
    public void ForEach(Action<string> processData)
    {
        foreach (string item in data)
            processData(item);
    }
}

现在,您可以使用以下代码将整个内容转储到文件中。
var stringData = new StringBuilderEx();
stringData.Append("Add lots of data");

using (StreamWriter file = new System.IO.StreamWriter(localFilename))
{
    stringData.ForEach((data) =>
    {
        file.Write(data);
    });
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接