废话就不说了。
这里记录三种使用pbn的方式:
1、手动方式
2、动态方式
3、proto文件方式
代码示例是编译不过的,主要是学习交流之用。
1、手动方式
核心类:
ProtoContractAttribute
ProtoMemberAttribute
ProtoBuf.Serializer
需要注意的地方:
继承的表示法,需要在基类上使用 ProtoSub(typeof(SubClass), SubClassTag(int))
类似XML那种标记子类的方式。 XMLInclude。
所以这种使用方式有诸多不便。
使用方式如代码:
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using ProtoBuf;
using System;
using System.IO;
using System.Xml.Serialization;
namespace StaticUsageDemo
{
[ProtoContract]
[Serializable]
public class TestDataTable
{
[ProtoMember(1)]
public TestData[] Data = new TestData[0];
}
[ProtoContract]
[Serializable]
public class TestData
{
[ProtoMember(6, IsRequired = true)]
public float value { get; set; }
[ProtoMember(1, IsRequired = true)]
public int UserID { get; set; }
[ProtoMember(2, IsRequired = false)]
public string UserName { get; set; }
[ProtoMember(3, IsRequired = false)]
public string UserName2 { get; set; }
[ProtoMember(4, IsRequired = false)]
public string UserName3 { get; set; }
[ProtoMember(5, IsRequired = false)]
public string UserName4 { get; set; }
}
public static class StaticUsageDemo
{
[MenuItem("Metadata/Create")]
public static void CreateFile()
{
UGE.Utility.HPTimer.Init();
var dataTable = new TestDataTable();
int count = 100000;
using(UGE.Utility.TimeTracker.Record("Create Data"))
{
List<TestData> list = new List<TestData>(count);
for ( int i =0; i < count; ++i )
{
list.Add(new TestData
{
UserID = 0,
UserName = "AAA",
UserName2 = "AAA",
UserName3 = "AAA",
UserName4 = "AAA",
value = 1,
});
}
dataTable.Data = list.ToArray();
}
//TestDataTable dataXML2;
//{
// Stream fileXML = File.Create("Tools/Output/TestData.xml");
// try
// {
// XmlSerializer xmler = new XmlSerializer(dataTable.GetType());
// xmler.Serialize(fileXML, dataTable);
// // read xml version data;
// fileXML.Seek(0, SeekOrigin.Begin);
// dataXML2 = xmler.Deserialize(fileXML) as TestDataTable;
// Debug.Log("dataXML:" + dataXML2.Data[0].UserName);
// }
// finally
// {
// fileXML.Close();
// }
//}
// use data;
{
Stream file = File.Open("Tools/Output/TestData.dat", FileMode.OpenOrCreate);
try
{
Debug.Log("tableCount:" + dataTable.Data.Length);
using ( UGE.Utility.TimeTracker.Record("Serialize Data") )
{
Serializer.Serialize<TestDataTable>(file, dataTable);
}
file.Seek(0, SeekOrigin.Begin);
TestDataTable table;
using ( UGE.Utility.TimeTracker.Record("Deserialize Data") )
{
table = Serializer.Deserialize<TestDataTable>(file);
}
Debug.Log("tableCount:" + table.Data.Length);
Debug.Log(string.Format("method 2:{0}", table.Data[1].UserName));
}
finally
{
file.Close();
}
}
Debug.Log(UGE.Utility.TimeTracker.Dump());
return;
}
}
}
2、动态方式
核心类:
RuntimeTypeModel
MetaType
动态构建方法:
1、注册一个MetaType
RuntimeTypeModel.Default.Add( System.Type)
2、往MetaType里添加Field
Type type = mt.Type;
FieldInfo[] fields = type.GetFields((BindingFlags)( BindingFlags.Instance | BindingFlags.Public ));
foreach ( var field in fields )
{
mt.Add(mt.GetNextFieldNumber(), field.Name);
}
3、构建TypeTree
MetaType.AddSubType ( SubClassTag:int, SubClassType:System.Type)
需要注意的地方:
1、继承,我测试发现,子类的 tag得是全局唯一
所以我为了记录classTag ,专门弄了一个xml来维护这个事情--当然是通过反射代码 。
2、一旦执行了 Serialize / Deserialize 方法后, MetaType 就 Frozen了, 这之后再添加 Field 或 SubType 就会报错。
3、proto文件方式
1、获取生成工具
编译 protobuf-net 和 ProtoGen 这两个csproj,可以得到一个 protogen.exe ,还有一堆 xlst。这些是用来解析 proto文件,并生成目标代码的。
2、编写一个 proto文件
比如 test.proto
message TestData {
required int32 ID=1;
required string UserName=2;
required string UserName2=3;
required string UserName3=4;
required string UserName4=5;
required string UserName5=6;
required float value=7;
}
message TestDataTable
{
repeated TestData data=1;
}
3、使用一个proto文件
protogen -i:test.proto -o:test.cs -ns:UGE.Metadata -p:import=UGE
这句话的意思是, 输入test.proto文件, 给我生成 test.cs 文件, 代码在 namespace UGE.Metadata里, 顺便引用下 using UGE.
例子如下:
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
// Generated from: test.proto
namespace test
{
[global::System.Serializable, global::ProtoBuf.ProtoContract(Name=@"TestData")]
public partial class TestData : global::ProtoBuf.IExtensible
{
public TestData() {}
private int _ID;
[global::ProtoBuf.ProtoMember(1, IsRequired = true, Name=@"ID", DataFormat = global::ProtoBuf.DataFormat.TwosComplement)]
public int ID
{
get { return _ID; }
set { _ID = value; }
}
private string _UserName;
[global::ProtoBuf.ProtoMember(2, IsRequired = true, Name=@"UserName", DataFormat = global::ProtoBuf.DataFormat.Default)]
public string UserName
{
get { return _UserName; }
set { _UserName = value; }
}
private string _UserName2;
[global::ProtoBuf.ProtoMember(3, IsRequired = true, Name=@"UserName2", DataFormat = global::ProtoBuf.DataFormat.Default)]
public string UserName2
{
get { return _UserName2; }
set { _UserName2 = value; }
}
private string _UserName3;
[global::ProtoBuf.ProtoMember(4, IsRequired = true, Name=@"UserName3", DataFormat = global::ProtoBuf.DataFormat.Default)]
public string UserName3
{
get { return _UserName3; }
set { _UserName3 = value; }
}
private string _UserName4;
[global::ProtoBuf.ProtoMember(5, IsRequired = true, Name=@"UserName4", DataFormat = global::ProtoBuf.DataFormat.Default)]
public string UserName4
{
get { return _UserName4; }
set { _UserName4 = value; }
}
private string _UserName5;
[global::ProtoBuf.ProtoMember(6, IsRequired = true, Name=@"UserName5", DataFormat = global::ProtoBuf.DataFormat.Default)]
public string UserName5
{
get { return _UserName5; }
set { _UserName5 = value; }
}
private float _value;
[global::ProtoBuf.ProtoMember(7, IsRequired = true, Name=@"value", DataFormat = global::ProtoBuf.DataFormat.FixedSize)]
public float value
{
get { return _value; }
set { _value = value; }
}
private global::ProtoBuf.IExtension extensionObject;
global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing)
{ return global::ProtoBuf.Extensible.GetExtensionObject(ref extensionObject, createIfMissing); }
}
[global::System.Serializable, global::ProtoBuf.ProtoContract(Name=@"TestDataTable")]
public partial class TestDataTable : global::ProtoBuf.IExtensible
{
public TestDataTable() {}
private readonly global::System.Collections.Generic.List<TestData> _data = new global::System.Collections.Generic.List<TestData>();
[global::ProtoBuf.ProtoMember(1, Name=@"data", DataFormat = global::ProtoBuf.DataFormat.Default)]
public global::System.Collections.Generic.List<TestData> data
{
get { return _data; }
}
private global::ProtoBuf.IExtension extensionObject;
global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing)
{ return global::ProtoBuf.Extensible.GetExtensionObject(ref extensionObject, createIfMissing); }
}
}
没有测试在这种情况下怎么继承。估计这是正确使用pb的方式,应该有很多文档说明。
我在这里记录的很简单,主要是指明了关键的工作流。
在实际使用的时候,需要根据情况自行安排使用方式,所以再往多写就啰嗦了。
顺带一提我选择的是第二种方式。
各方式速度比较:
100,000 十万条数据, 5个string 1个int32 , 1个float 。数据没有多样化。
ms Write Read
方式1 430+ 2000+
方式2 1000+ 1300+
方式3 我没测过,应该和1类似
和json之类的压缩率比较没意义,pb使用varint,本来就很擅长压缩。
算是大致有个印象吧