如何从字符串生成流?

1004

我需要为一个从文本文件中获取流的方法编写单元测试。我想要做类似于这样的事情:

Stream s = GenerateStreamFromString("a,b \n c,d");

1
有关内存节省的解决方案,请参见https://dev59.com/ToPba4cB1Zd3GeqPzO8Q#55170901中的“StringReaderStream”。 - xmedeko
14个回答

1238
public static Stream GenerateStreamFromString(string s)
{
    var stream = new MemoryStream();
    var writer = new StreamWriter(stream);
    writer.Write(s);
    writer.Flush();
    stream.Position = 0;
    return stream;
}

别忘了使用Using:

using (var stream = GenerateStreamFromString("a,b \n c,d"))
{
    // ... Do stuff to stream
}
关于未被释放的StreamWriterStreamWriter只是基础流的封装器,并不使用需要被释放的资源。 Dispose方法将关闭StreamWriter正在写入的底层Stream。在这种情况下,我们希望关闭的是MemoryStream
在.NET 4.5中,现在有一个重载的StreamWriter可以在编写器被释放后保持基础流处于打开状态,但这段代码也能实现同样的效果,在其他版本的.NET上也适用。
参见Is there any way to close a StreamWriter without closing its BaseStream?

180
需要指出的一个重要概念是,流由字节组成,而字符串由字符组成。理解将字符转换为一个或多个字节(或在这种情况下转换为流)始终使用(或假定)特定编码是至关重要的。虽然这个答案在某些情况下是正确的,但它使用默认编码,在一般情况下可能不适用。显式地向StreamWriter构造函数传递编码将使作者需要考虑编码的影响更加明显。 - drwatsoncode
9
您说“在使用流时不要忘记使用Using”,但是在您的GenerateStreamFromString方法中,您没有在StreamWriter中使用Using。这是有原因的吗? - Ben
16
是的,如果你处理掉 StreamWriter 对象,底层流也会被关闭。我们不希望这样。StreamWriter 可以被释放的唯一原因是为了清理流,所以可以安全地忽略它。 - Cameron MacFarland
3
需要注意的是,整个字符串被复制到内存中,这对于大字符串可能很重要,因为现在内存中有一个额外的副本。 - Przemysław Michalski
1
@ahong 不是很准确。StreamWriter 大概已经在内部执行你所说的操作了。这样做的好处是封装和简化代码,但代价是把一些东西(比如编码)抽象掉了。这取决于你想要实现什么目标。 - Cameron MacFarland
显示剩余9条评论

1016

另一种解决方案:

public static MemoryStream GenerateStreamFromString(string value)
{
    return new MemoryStream(Encoding.UTF8.GetBytes(value ?? ""));
}

50
以防有人将此用于XML字符串反序列化,我不得不将UTF8切换为Unicode,以便在不使用标志的情况下正常工作。好文章! - Gaspa79
3
我喜欢这个带有Rhyous的修改和额外糖分的版本,可以作为扩展方法使用,比被接受的答案更好。它更灵活,代码行数更少,涉及的对象也更少(无需显式使用StreamWriter)。 - KeithS
2
新的MemoryStream(Encoding.UTF8.GetBytes("\ufeff" + (value ?? ""))),如果你需要在流的开头包含BOM。 - robert4
8
这是非常紧凑的语法,但它会导致字节数组(byte[])的大量分配,因此在高性能代码中要小心。 - michael.aird
2
这个解决方案仍然有机会使流变成只读的。 new MemoryStream(value,false)。如果您必须使用流作者来写入,则无法使流变为只读。 - codekandis
显示剩余3条评论

121

将此代码添加到静态字符串实用类中:

public static Stream ToStream(this string str)
{
    MemoryStream stream = new MemoryStream();
    StreamWriter writer = new StreamWriter(stream);
    writer.Write(str);
    writer.Flush();
    stream.Position = 0;
    return stream;
}

这个添加了一个扩展函数,所以你可以简单地这样使用:

using (var stringStream = "My string".ToStream())
{
    // use stringStream
}

9
当垃圾回收器清理 StreamWriter 时,我发现返回的流会被关闭(导致半随机异常)。解决方法是使用一个允许我指定 leaveOpen 的不同构造函数。 - Bevan
1
StreamWriter 不应该被释放吗? - Métoule
使用语句将在变量离开作用域后处理流写入器。 - Josh G

65
public Stream GenerateStreamFromString(string s)
{
    return new MemoryStream(Encoding.UTF8.GetBytes(s));
}

46

现代化和略微修改的 ToStream 扩展方法的版本:

public static Stream ToStream(this string value) => ToStream(value, Encoding.UTF8);

public static Stream ToStream(this string value, Encoding encoding) 
                          => new MemoryStream(encoding.GetBytes(value ?? string.Empty));

根据@Palec在@Shaun Bowe答案下的评论建议进行修改。


或者按照@satnhak的建议,使用一行代码:

public static Stream ToStream(this string value, Encoding encoding = null) 
    => new MemoryStream((encoding ?? Encoding.UTF8).GetBytes(value ?? string.Empty));

2
public static Stream ToStream(this string value, Encoding encoding = null) => new MemoryStream((encoding ?? Encoding.UTF8).GetBytes(value ?? string.Empty)); - satnhak

30

我使用了类似以下的答案混合:

public static Stream ToStream(this string str, Encoding enc = null)
{
    enc = enc ?? Encoding.UTF8;
    return new MemoryStream(enc.GetBytes(str ?? ""));
}

然后我像这样使用它:

String someStr="This is a Test";
Encoding enc = getEncodingFromSomeWhere();
using (Stream stream = someStr.ToStream(enc))
{
    // Do something with the stream....
}

Thomas,为什么要踩我?enc = enc ?? Encoding.UTF8 允许我明确地请求使用特定编码的流,或者默认使用UTF8。因为在.NET中(至少我使用的是.NET 4.0),你不能给函数签名中除了字符串以外的引用类型赋一个默认值,所以这一行是必要的。这样说清楚了吗? - Robocide
提醒:将此内容放入单独的类中(非泛型静态类?)会更有帮助,也可以减少负面评价。 - Ali
这个能进一步缩减吗?public static Stream ToStream(this string str, Encoding enc = Encoding.UTF8) { return new MemoryStream(enc.GetBytes(str ?? "")); } - Matthew Lock

25

使用MemoryStream类,先调用Encoding.GetBytes将您的字符串转换为字节数组。

之后,如果您需要在流上使用TextReader,您可以直接提供一个StringReader,跳过MemoryStreamEncoding步骤。


17
我们使用以下列出的扩展方法。我认为你应该让开发者决定编码,这样就不会有太多的魔法 involved(参与其中)了。
public static class StringExtensions {

    public static Stream ToStream(this string s) {
        return s.ToStream(Encoding.UTF8);
    }

    public static Stream ToStream(this string s, Encoding encoding) {
        return new MemoryStream(encoding.GetBytes(s ?? ""));
    }
}

2
我更倾向于使用第一种方法return ToStream(s,Encoding.UTF8)进行实现。 在当前的实现中(return s.ToStream(Encoding.UTF8)),开发人员被迫更加努力地思考以理解代码,并且似乎未处理s == null的情况,可能会抛出NullReferenceException异常。 - Palec

14

如果你需要更改编码,我建议使用@ShaunBowe的解决方案。但是这里的每个答案都至少将整个字符串复制到内存中一次。使用ToCharArray + BlockCopy组合的答案则会复制两次。

如果这很重要,这里有一个简单的Stream包装器可以用于原始的UTF-16字符串。如果与StreamReader一起使用,则选择Encoding.Unicode

public class StringStream : Stream
{
    private readonly string str;

    public override bool CanRead => true;
    public override bool CanSeek => true;
    public override bool CanWrite => false;
    public override long Length => str.Length * 2;
    public override long Position { get; set; } // TODO: bounds check

    public StringStream(string s) => str = s ?? throw new ArgumentNullException(nameof(s));

    public override long Seek(long offset, SeekOrigin origin)
    {
        switch (origin)
        {
            case SeekOrigin.Begin:
                Position = offset;
                break;
            case SeekOrigin.Current:
                Position += offset;
                break;
            case SeekOrigin.End:
                Position = Length - offset;
                break;
        }

        return Position;
    }

    private byte this[int i] => (i & 1) == 0 ? (byte)(str[i / 2] & 0xFF) : (byte)(str[i / 2] >> 8);

    public override int Read(byte[] buffer, int offset, int count)
    {
        // TODO: bounds check
        var len = Math.Min(count, Length - Position);
        for (int i = 0; i < len; i++)
            buffer[offset++] = this[(int)(Position++)];
        return (int)len;
    }

    public override int ReadByte() => Position >= Length ? -1 : this[(int)Position++];
    public override void Flush() { }
    public override void SetLength(long value) => throw new NotSupportedException();
    public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
    public override string ToString() => str; // ;)     
}

这里有一个更完整的解决方案,具备必要的边界检查(派生自MemoryStream,因此还具有ToArrayWriteTo方法)。


9

这是你所需要的:

private Stream GenerateStreamFromString(String p)
{
    Byte[] bytes = UTF8Encoding.GetBytes(p);
    MemoryStream strm = new MemoryStream();
    strm.Write(bytes, 0, bytes.Length);
    return strm;
}

1
写入后需要重置位置。最好使用构造函数,就像joelnet的答案中所示。 - Jim Balter

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