将流转换为字符串并再次转换为流

263

我想将对象序列化为字符串,然后再反序列化。

我们使用protobuf-net将对象转换为流并成功地将其反转回来。

但是,将流转换为字符串再反转回去的操作却不成功。经过StreamToStringStringToStream两个方法后,新的Stream对象无法被protobuf-net反序列化;它会引发一个Arithmetic Operation resulted in an Overflow异常。如果我们对原始流进行反序列化,那么就可以正常工作。

我们使用的方法:

public static string StreamToString(Stream stream)
{
    stream.Position = 0;
    using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
    {
        return reader.ReadToEnd();
    }
}

public static Stream StringToStream(string src)
{
    byte[] byteArray = Encoding.UTF8.GetBytes(src);
    return new MemoryStream(byteArray);
}

我们使用这两个来示范代码:

MemoryStream stream = new MemoryStream();
Serializer.Serialize<SuperExample>(stream, test);
stream.Position = 0;
string strout = StreamToString(stream);
MemoryStream result = (MemoryStream)StringToStream(strout);
var other = Serializer.Deserialize<SuperExample>(result);

1
应该使用MemoryStream而不是Stream吗? - Ehsan
9个回答

354

我刚刚测试了一下,它运行得很好。

string test = "Testing 1-2-3";

// convert string to stream
byte[] byteArray = Encoding.ASCII.GetBytes(test);
MemoryStream stream = new MemoryStream(byteArray);

// convert stream to string
StreamReader reader = new StreamReader(stream);
string text = reader.ReadToEnd();

如果 stream 已经被写入过,你可能需要在读取文本之前先将其寻找到开头:stream.Seek(0, SeekOrigin.Begin);


8
不要忘记在 StreamReader reader = new StreamReader(stream); 周围加上 using 块。 - PRMan
这个可以工作是因为它在字符串上进行了测试。对于包含任意字节的流,它不起作用:请参见Marc Gravell下面的回答,关于Convert.ToBase64。 - FTWinston

69

这种做法非常常见但是根本是错的。Protobuf数据不是字符串数据,它肯定不是ASCII编码。你正在 反过来使用 编码。文本编码转换:

  • 将任意字符串转换为格式化字节
  • 将格式化字节转换回原始字符串

你没有 格式化的字节,而是 任意的字节。你需要使用类似于base-n(通常是base-64)编码的东西。这个编码实现了以下转换:

  • 将任意字节转换为格式化的字符串
  • 将格式化的字符串转换回原始字节

请看 Convert.ToBase64StringConvert.FromBase64String


1
你能否使用BinaryFormatter,类似于这个奇怪的例子吗? - drzaus
@drzaus 嗯...也许不是这样的: > "任何未配对的代理字符在二进制序列化中都会丢失" - drzaus

16

将UTF8 MemoryStream转换为字符串:

var res = Encoding.UTF8.GetString(stream.GetBuffer(), 0 , (int)stream.Length)

3
请使用ToArray()。缓冲区可能比所使用的数据大小要大。ToArray()返回正确大小的数据副本。var array = stream.ToArray(); var str = Encoding.UTF8.GetString(array, 0, array.Length);。另请参阅https://msdn.microsoft.com/en-us/library/system.io.memorystream.getbuffer.aspx?ranMID=24542&ranEAID=TnL5HPStwNw&ranSiteID=TnL5HPStwNw-fWxoHnp968IcSsgF87gs3g&tduid=(af5d64506773ff2a87e3cde51de009eb)(256380)(2459594)(TnL5HPStwNw-fWxoHnp968IcSsgF87gs3g)() - Mortennobel
4
ToArray()方法会在内存中分配一个新的数组,并从缓冲区复制数据,如果你处理大量数据,则可能会产生严重影响。 - Levi Botelho
1
请注意使用stream.Length而不是stream.GetBuffer().Length。Levi正确指出了不使用ToArray()的原因。 - Wolfgang Grinfeld

8
 StreamReader reader = new StreamReader(strm, System.Text.Encoding.UTF8);
        var final1 = reader.ReadToEnd();

大多数问题的主要原因是编码类型...在此默认情况下使用System.Text.Encoding.UTF8可以解决问题...

享受吧...


1
由于StreamReader实现了IDisposable,建议使用using - user3819197

6

当你进行测试时,请使用如下的UTF8编码流进行尝试。

var stream = new MemoryStream();
var streamWriter = new StreamWriter(stream, System.Text.Encoding.UTF8);
Serializer.Serialize<SuperExample>(streamWriter, test);

6

试一下这个。

string output1 = Encoding.ASCII.GetString(byteArray, 0, byteArray.Length)

2

我编写了一个有用的方法,可以调用任何需要StreamWriter并将其写入字符串的操作。该方法如下所示:

static void SendStreamToString(Action<StreamWriter> action, out string destination)
{
    using (var stream = new MemoryStream())
    using (var writer = new StreamWriter(stream, Encoding.Unicode))
    {
        action(writer);
        writer.Flush();
        stream.Position = 0;
        destination = Encoding.Unicode.GetString(stream.GetBuffer(), 0, (int)stream.Length);
    }
}

你可以像这样使用它:;
string myString;

SendStreamToString(writer =>
{
    var ints = new List<int> {1, 2, 3};
    writer.WriteLine("My ints");
    foreach (var integer in ints)
    {
        writer.WriteLine(integer);
    }
}, out myString);

我知道使用 StringBuilder 可以更容易地完成此操作,但重点是您可以调用任何需要 StreamWriter 的方法。

1
我想将对象序列化为字符串,然后再反序列化回来。
与其他答案不同,但对于大多数对象类型来说,最直接的方法是使用XmlSerializer:
        Subject subject = new Subject();
        XmlSerializer serializer = new XmlSerializer(typeof(Subject));
        using (Stream stream = new MemoryStream())
        {
            serializer.Serialize(stream, subject);
            // do something with stream
            Subject subject2 = (Subject)serializer.Deserialize(stream);
            // do something with subject2
        }

所有支持的类型的公共属性都将被序列化。甚至支持一些集合结构,并将隧道通往子对象属性。您可以使用属性上的attributes控制序列化的方式。
这不适用于所有对象类型,某些数据类型不支持序列化,但总体而言它非常强大,您不必担心编码。

0
在需要序列化/反序列化POCO的使用案例中,Newtonsoft的JSON库非常好用。我将其用于将POCO持久化在SQL Server中,并将它们作为JSON字符串存储在nvarchar字段中。需要注意的是,由于它并不是真正的序列化/反序列化,因此它将无法保留私有/受保护的成员和类层次结构。

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