我正在尝试使用MessagePack
保存多个结构列表,因为我读到它的性能优于BinaryFormatter
序列化。
[我想做的是接收实时时间序列数据,并定期将其定期保存(追加)到磁盘上,例如,如果列表中的元素数为100。我的问题是:
1)在这种情况下,序列化结构列表并将其异步保存到磁盘是否更好?
2)如何使用MessagePack将其简单地保存到磁盘?
public struct struct_realTime
{
public int indexNum { get; set; }
public string currentTime { get; set; }
public string currentType { get; set; }
}
class Program
{
static void Main(string[] args)
{
List<struct_realTime> list_temp = new List<struct_realTime>(100000);
for (int num=0; num < 100000; num++)
{
list_temp.Add(new struct_realTime
{
indexNum = 1,
currentTime = "time",
currentType = "type",
});
}
string filename = "file.bin";
using (var fileStream = new FileStream(filename, FileMode.Append, FileAccess.Write))
{
byte[] bytes = MessagePackSerializer.Serialize(list_temp);
Console.WriteLine(MessagePackSerializer.ToJson(bytes));
}
}
}
当我运行此代码时,它将创建file.bin
并打印出100000个结构,但文件为0字节。
[当我使用BinaryFormatter
时,我这样做:
using (var fileStream = new FileStream("file.bin", FileMode.Append))
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(fileStream, list_temp);
}
如何解决该问题?我想得到一些帮助。
您要做的是附加使用MessagePackSerializer
序列化的对象到包含已经序列化的相似对象序列的文件中,就像使用MessagePackSerializer
或BinaryFormatter
一样。稍后,您大概希望能够将整个序列反序列化为列表或数组。
您的代码有三个问题,两个简单,一个基本。
简单的问题如下:
您实际上没有写BinaryFormatter
。可以将其固定如下:
Json.NET
您尚未用fileStream
标记foreach (var item in list_temp)
{
using (var fileStream = new FileStream(filename, FileMode.Append, FileAccess.Write))
{
MessagePackSerializer.Serialize(fileStream, item);
}
}
。可以将其固定如下:
struct_realTime
完成此操作后,您现在可以将多个[MessagePackObject]
attributes序列化为一个文件...,但是之后将无法读取它们!这是因为[MessagePackObject]
将在反序列化根对象类型时读取整个文件,从而跳过附加到该文件的所有其他数据。因此,如下所示的代码将失败,因为仅从文件中读取了一个对象:
[MessagePackObject]
public struct struct_realTime
{
[Key(0)]
public int indexNum { get; set; }
[Key(1)]
public string currentTime { get; set; }
[Key(2)]
public string currentType { get; set; }
}
演示小提琴#1 struct_realTime
。
并且如下代码将失败,因为流中的根对象是单个对象,而不是对象数组:
MessagePackSerializer
演示小提琴#2 var readList = new List<struct_realTime>();
using (var fileStream = File.OpenRead(filename))
{
while (fileStream.Position < fileStream.Length)
{
var item = MessagePackSerializer.Deserialize<struct_realTime>(fileStream);
readList.Append(item);
}
}
Assert.IsTrue(readList.Count == list_temp.Count);
。
由于here似乎缺乏反序列化流中多个根对象的能力,您有什么选择?首先,您可以反序列化List<struct_realTime> readList;
using (var fileStream = File.OpenRead(filename))
{
readList = MessagePackSerializer.Deserialize<List<struct_realTime>>(fileStream);
}
Assert.IsTrue(readList.Count == list_temp.Count);
,将其追加,然后将整个对象反序列化回文件。大概出于性能原因,您不想这样做。
第二,您可以直接使用here手动解析并重写适当的MessagePackSerializer
到文件,然后使用List<struct_realTime>
本身进行序列化并将对象附加到文件。
以下扩展方法完成了这一工作,方法是在文件的开头查找要写入新数组的数量,然后在末尾查找以追加对象:
MessagePack specification
可以如下使用:
array 32
format header
注意:
MessagePack协议具有3种不同的数组格式:
array 32
存储一个数组,该数组的长度最多为15个元素。MessagePackSerializer
存储一个数组,该数组的长度最多为(2 ^ 16)-1个元素。public static class MessagePackExtensions
{
const byte Array32 = 0xdd;
const int Array32HeaderLength = 5;
static void FormatArray32Header(byte [] buffer, uint value)
{
buffer[0] = Array32;
buffer[1] = unchecked((byte)(value >> 24));
buffer[2] = unchecked((byte)(value >> 16));
buffer[3] = unchecked((byte)(value >> 8));
buffer[4] = unchecked((byte)value);
}
static uint ParseArray32Header(byte [] buffer, int readCount)
{
if (readCount < 5 || buffer[0] != Array32)
throw new ArgumentException("Stream was not positioned on an Array32 header.");
int i = 1;
//https://stackoverflow.com/questions/8241060/how-to-get-little-endian-data-from-big-endian-in-c-sharp-using-bitconverter-toin
//https://stackoverflow.com/a/8241127 by https://stackoverflow.com/users/23354/marc-gravell
var value = unchecked((uint)((buffer[i++] << 24) | (buffer[i++] << 16) | (buffer[i++] << 8) | buffer[i++]));
return value;
}
public static void AppendToFile<T>(Stream stream, T item)
{
if (stream == null)
throw new ArgumentNullException(nameof(stream));
if (!stream.CanSeek)
throw new ArgumentException("!stream.CanSeek");
stream.Position = 0;
var buffer = new byte[Array32HeaderLength];
var read = stream.Read(buffer, 0, Array32HeaderLength);
stream.Position = 0;
if (read == 0)
{
FormatArray32Header(buffer, 1);
stream.Write(buffer, 0, Array32HeaderLength);
}
else
{
var count = ParseArray32Header(buffer, read);
FormatArray32Header(buffer, count + 1);
stream.Write(buffer, 0, Array32HeaderLength);
}
stream.Position = stream.Length;
MessagePackSerializer.Serialize(stream, item);
}
}
存储一个数组,该数组的长度最多为(2 ^ 32)-1个元素。扩展方法要求根数组为// Append each item sequentially
foreach (var item in list_temp)
{
using (var fileStream = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
MessagePackExtensions.AppendToFile(fileStream, item);
}
}
// Then deserialize the entire sequence as a List<T>>
using (var fileStream = File.OpenRead(filename))
{
var array = MessagePackSerializer.Deserialize<List<struct_realTime>>(fileStream);
Console.WriteLine(MessagePackSerializer.ToJson(array));
Assert.IsTrue(array.Count == list_temp.Count);
Assert.IsTrue(list_temp.SequenceEqual(array));
}
,以便在新大小变得大于fixarray
或array 16
的容量时,无需重新格式化整个数组。但是,array 32
将始终以最紧凑的格式写入,因此不能保证将其附加到先前由array 32
序列化的集合中。
演示小提琴#3 fixarray
。