Написать StringBuilder в поток

Как лучше всего записать 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);

person Andy Joiner    schedule 10.02.2010    source источник


Ответы (6)


Не используйте 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(...);
}
person Neil Barnwell    schedule 10.02.2010
comment
Это невозможно, если ему нужно использовать строку для чего-то еще, прежде чем писать ее. - person Skurmedel; 10.02.2010
comment
Абсолютно верно, но, конечно, он не сказал, что это требование, поэтому я не делал никаких предположений. - person Neil Barnwell; 10.02.2010
comment
Также не забудьте выполнить Flush and Seek перед чтением из потока. - person styfle; 30.05.2018

Это лучший метод. В противном случае потеряйте StringBuilder и используйте что-то вроде следующего:

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

Возможно, это будет полезно.

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
person DmitryBoyko    schedule 09.05.2016
comment
Скопировать сначала в строку, затем снова скопировать в массив байтов, а затем, наконец, скопировать в поток? Действительно? Конечно, это можно сделать кратко, не делая промежуточных копий. (Что, если размер построителя строк составляет 2 ГБ?) - person Ian Goldby; 15.02.2017

В зависимости от вашего варианта использования также может иметь смысл просто начать с 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
person Chris Moschini    schedule 22.05.2013
comment
Совершенно верно: msdn. microsoft.com/en-us/library/ - person Chris Moschini; 04.03.2015

EDIT: как указывает @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 в Stream без материализации всей строки:

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.

person Jabbersii    schedule 22.10.2020
comment
Это отличный способ извлечь данные из StringBuilder. Одно предупреждение состоит в том, что это не является потокобезопасным, поэтому вы должны заблокировать этот блок кода. docs.microsoft.com/en-us/dotnet/ API/ - person AnthonyVO; 30.12.2020
comment
Я не думаю, что это нужно, вы можете просто сделать streamWriter.WriteAsync(stringBuilder) - person Ramon Smits; 09.04.2021

Если вы хотите использовать что-то вроде 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);
    });
}
person AnthonyVO    schedule 30.09.2017