如何压缩序列化列表<>在C#中有效

时间:2014-04-12 22:25:01

标签: c# list serialization compression

为了简化长程序,我使用了一个包含List(又名tilemap)的Map类。在当前场景中,我的地图必须具有2048x2048像素的尺寸,分为16x16(像素)单元格,包括8个图层。这总共(128x128个单元格)乘以8 = 131072个平铺元素。

我最大的问题是当我对Map对象进行序列化时,我试图用gzip压缩它。但是输出文件是400 KB(或者没有gzip的640 KB),我觉得它很快。我希望它小于40 KB,特别是我在这里只使用了整个tilemap中的Tile引用。该文件几乎与我在整个场景中使用大型2048x2048一样大,而使用磁贴的目的是使其尺寸非常小。

有什么我完全误解的吗?是否有一种非常有效的方法来压缩List<>?

在您的下方,我们会找到代码段。我试图在这些例子中保持超级简单,它在我的真实程序中相当复杂。

这是Map类:

[Serializable()]
public class Map
{
    public List<Tile> tilemap = new List<Tile>();

    public Map()
    {
        Tile single_tile = new Tile();

        // Fill entire tilemap with one single tile object
        for(int i = 0; i < 131072; i++)
        {
            tilemap.Add( single_tile );
        }

    }

    public Map(SerializationInfo info, StreamingContext ctxt)
    {
        tilemap = (List<Tile>)info.GetValue("tilemap", typeof(List<Tile>));
    }

    public void  GetObjectData(SerializationInfo info, StreamingContext ctxt)
    {
        info.AddValue("tilemap", tilemap);
    }

}

这是Tile课程。

[Serializable()]
public class Tile
{
    public int id;
    public string name;
    public int type;
    public int passage;
    public bool autotiled;

    public Tile()
    {

    }

    public Tile(int _id)
    {
        id = _id;
        name = "Grass";
        type = 1;
        passage = 2;
        autotiled = false;
    }

    public Tile(SerializationInfo info, StreamingContext ctxt)
    {
        id = (int)info.GetValue("id", typeof(int));
        name = (string)info.GetValue("name", typeof(string));
        type = (int)info.GetValue("type", typeof(int));
        passage = (int)info.GetValue("passage", typeof(int));
        autotiled = (bool)info.GetValue("autotiled", typeof(bool));
    }

    public void  GetObjectData(SerializationInfo info, StreamingContext ctxt)
    {
        info.AddValue("id", id);
        info.AddValue("name", name);
        info.AddValue("type", type);
        info.AddValue("passage", passage);
        info.AddValue("autotiled", autotiled);
    }

}

最后是序列化+压缩过程

Map map = new Map();
string filename = "Game_Data\\" + "Map_1.txt";

Stream stream = File.Open(filename, FileMode.Create);

GZipStream compressor = new GZipStream(stream, CompressionMode.Compress );

BinaryFormatter bFormatter = new BinaryFormatter();
bFormatter.Serialize(compressor, obj);
compressor.Close();
stream.Close();

3 个答案:

答案 0 :(得分:1)

每个元素4个字节的131072个元素(一个引用)生成512kb的原始数据,因此您可以得到您应该获得的内容。其余数据应该是序列化的开销。

我认为改进的唯一方法是不保存每个图块,但只保存重要图像/非默认图块,稀疏矩阵的工作方式相同。根据您的地图,甚至可能存在您无需实际保存的整个图层。

答案 1 :(得分:0)

假设相同的图块可以在同一个地图中重复出现,您可以将信息拆分为一个唯一图块列表和一个描述图块在地图中位置的列表。

IEnumerable<Tile> uniqueTiles = tilemap.Distinct();
// Serialize these tiles, i.e each unique tile only once.

IEnumerable<int> map = tilemap.Select(m => m.id);
// Serialize the IDs.

在反序列化切片时,首先使用ID作为键将切片加载到字典中。

var tileDict = new Dictionary<int, Tile>();

使用ID列表和此词典,您现在可以快速重建原始tilemap。

List<int> ids = ... // Deserialized IDs.
var tilemap = new List<Tile>(ids.Count);
for (int i = 0; i < ids.Count; i++) {
    tilemap.Add(tileDict[ids[i]];
}

答案 2 :(得分:0)

如果您需要紧凑的表示,请不要使用BinaryFormatterBinaryFormatter实现的格式是许多事情的重大折衷。

我理解这是图像数据。因此将其保存为2048x2048 PNG图像并使用ScriptPNG缩小它。

如果它不是图像,请实现自定义二进制序列化格式。