zoukankan      html  css  js  c++  java
  • 谁能在同一文件序列化多个对象并随机读写(反序列化)?BinaryFormatter、SoapFormatter、XmlSerializer还是BinaryReader

    谁能在同一文件序列化多个对象并随机读写(反序列化)?BinaryFormatter、SoapFormatter、XmlSerializer还是BinaryReader

    随机反序列化器

    +BIT祝威+悄悄在此留下版了个权的信息说:

    最近在做一个小型的文件数据库SharpFileDB,遇到这样一个问题:我需要找一个能够在同一文件中序列化多个对象,并且能随机进行反序列化的工具。随机反序列化的意思是,假设我在文件里依次序列化存储了a、b、c三种不同类型的对象,那么我可以通过Stream.Seek(,);或者Stream.Position来仅仅反序列化b。当然,这可能需要一些其它的数据结构辅助我找到Stream.Seek(,);或者Stream.Position所需的参数。

    我找到了BinaryFormatter、SoapFormatter、XmlSerializer和BinaryReader这几个类型,都是.NET Framework内置的。但是它们并非都能胜任单文件数据库的序列化工具。

    举个例子

    +BIT祝威+悄悄在此留下版了个权的信息说:

    假设我有如下两个类型,本文将一直使用这两个类型作为数据结构。

     1         [Serializable]
     2         public class Cat
     3         {
     4             public override string ToString()
     5             {
     6                 return string.Format("{0}: {1}", this.Id, this.Name);
     7             }
     8 
     9             public string Name { get; set; }
    10 
    11             public int Id { get; set; }
    12         }
    13 
    14         [Serializable]
    15         public class Fish 
    16         {
    17             public override string ToString()
    18             {
    19                 return string.Format("{0}: {1}", this.Id, this.Weight);
    20             }
    21 
    22             public float Weight { get; set; }
    23 
    24 
    25             public int Id { get; set; }
    26         }

    顺序读写

    如果是按顺序进行反序列化,应该是这样的:

     1                 SomeFormatter formatter = new SomeFormatter();//某种序列化器
     2 
     3                 Cat cat = new Cat() { Id = 1, Name = "汤姆" };
     4                 Cat cat2 = new Cat() { Id = 2, Name = "汤姆媳妇" };
     5                 Fish fish = new Fish() { Id = 3, Weight = 1.5f };
     6 
     7                 using (FileStream fs = new FileStream("singleFileDB.bin", FileMode.Create, FileAccess.ReadWrite))
     8                 {
     9                     formatter.Serialize(fs, cat);
    10                     formatter.Serialize(fs, cat2);
    11                     formatter.Serialize(fs, fish);
    12                 }
    13 
    14                 object obj = null;
    15 
    16                 using (FileStream fs = new FileStream("singleFileDB.bin", FileMode.Open, FileAccess.Read))
    17                 {
    18                     obj = formatter.Deserialize(fs);// 1: 汤姆
    19 
    20                     obj = formatter.Deserialize(fs);// 2: 汤姆媳妇
    21 
    22                     obj = formatter.Deserialize(fs);// 3: 1.5
    23                 }

    随机读写

    +BIT祝威+悄悄在此留下版了个权的信息说:

    所谓随机读写,就是把上面的代码稍微改一下。

     1                 SomeFormatter formatter = new SomeFormatter ();
     2     
     3                 Cat cat = new Cat() { Id = 1, Name = "汤姆" };
     4                 Cat cat2 = new Cat() { Id = 2, Name = "汤姆媳妇" };
     5                 Fish fish = new Fish() { Id = 3, Weight = 1.5f };
     6 
     7                 using (FileStream fs = new FileStream("singleFileDB.bin", FileMode.Create, FileAccess.ReadWrite))
     8                 {
     9                     formatter.Serialize(fs, cat);
    10                     formatter.Serialize(fs, cat2);
    11                     formatter.Serialize(fs, fish);
    12                 }
    13 
    14                 object obj = null;
    15 
    16                 using (FileStream fs = new FileStream("singleFileDB.bin", FileMode.Open, FileAccess.Read))
    17                 {
    18                     obj = formatter.Deserialize(fs); // 1: 汤姆
    19 
    20                     long position = fs.Position;
    21 
    22                     obj = formatter.Deserialize(fs); // 2: 汤姆媳妇
    23 
    24                     fs.Position = position;//位置指针再次指向{2: 汤姆媳妇}的起始位置。(实现随机反序列化)
    25                     obj = formatter.Deserialize(fs); // 2: 汤姆媳妇
    26 
    27                     obj = formatter.Deserialize(fs);// 3: 1.5
    28                 }

    在反序列化时,我们先得到一个{1: 汤姆}对象,此时文件流指针指向了下一个对象的起始位置,我们把这个位置记录下来。然后再次反序列化,得到了{2: 汤姆媳妇}。现在把文件流的位置指针重新指向刚刚记录的位置,再次反序列化,仍旧得到了{2: 汤姆媳妇}。

    能够实现这样的功能的序列化器就是我想要的。

    BinaryFormatter

    +BIT祝威+悄悄在此留下版了个权的信息说:

    用 System.Runtime.Serialization.Formatters.Binary.BinaryFormatter ,能够完全胜任。而且他是用二进制格式序列化的,这样更保密、占用空间更少。不再多说。

    SoapFormatter

    +BIT祝威+悄悄在此留下版了个权的信息说:

     System.Runtime.Serialization.Formatters.Soap.SoapFormatter 与 System.Runtime.Serialization.Formatters.Binary.BinaryFormatter 是近亲,他们都实现了 IRemotingFormatter 和 IFormatter 两个接口。但是 SoapFormatter 的反序列化方式与 BinaryFormatter 不同,导致它不能胜任。

    具体来说, SoapFormatter 反序列化一个对象X后,可能会让 Stream.Position 超过下一个对象的起始位置,甚至一直读到文件流的最后,无论这个对象X是在文件的开头还是中间还是末尾。而单文件数据库的文件是可能很大的,让 SoapFormatter这么一下子读到末尾,非常浪费,而且位置指针难以控制,无法用于随机反序列化。

     1                 SoapFormatterformatter = new SoapFormatter ();
     2     
     3                 Cat cat = new Cat() { Id = 1, Name = "汤姆" };
     4                 Cat cat2 = new Cat() { Id = 2, Name = "汤姆媳妇" };
     5                 Fish fish = new Fish() { Id = 3, Weight = 1.5f };
     6 
     7                 using (FileStream fs = new FileStream("singleFileDB.soap", FileMode.Create, FileAccess.ReadWrite))
     8                 {
     9                     formatter.Serialize(fs, cat);
    10                     formatter.Serialize(fs, cat2);
    11                     formatter.Serialize(fs, fish);
    12                 }
    13 
    14                 object obj = null;
    15 
    16                 using (FileStream fs = new FileStream("singleFileDB.soap", FileMode.Open, FileAccess.Read))
    17                 {
    18                     Console.WriteLine(fs.Position == fs.Length);// false
    19 
    20                     obj = formatter.Deserialize(fs); // 1: 汤姆
    21                     Console.WriteLine(fs.Position == fs.Length);// true
    22 
    23                     obj = formatter.Deserialize(fs); // 2: 汤姆媳妇
    24                     Console.WriteLine(fs.Position == fs.Length);// true
    25 
    26                     obj = formatter.Deserialize(fs);// 3: 1.5
    27                     Console.WriteLine(fs.Position == fs.Length);// true
    28                 }

    XmlSerializer

    +BIT祝威+悄悄在此留下版了个权的信息说:

    原本我对 System.Xml.Serialization.XmlSerializer 寄予厚望,不过后来发现这家伙最难用。在创建 XmlSerializer 时必须指定能序列化的对象的类型。

    1 XmlSerializer formatter = new XmlSerializer(typeof(Cat));

    这个formatter只能序列化/反序列化 Cat 类型。需要序列化其它类型,就得创建一个对应的 XmlSerializer 。

    最关键的, XmlSerializer根本不能在同一文件里保存多个对象。所以就彻底没戏了。

    BinaryReader

    +BIT祝威+悄悄在此留下版了个权的信息说:

    我看到LiteDB里用的是 System.IO.BinaryReader 。它能手动控制读取任意位置的一个个字节,是进行精细化控制的能手。不过这也有不好的一面,就是凡事必须亲力亲为,代码量会增长很多,读写字节+拼凑语义信息这种程序稍不留神就会出bug,必须用大量的测试进行验证。

    这方面, BinaryFormatter 就更好一点。它能自动反序列化任何对象,不需要你一个字节一个字节地去抠。你只需给对 Stream.Position 即可。而 System.IO.BinaryReader最终也是需要你给定这个位置指针的。 

    总结

    +BIT祝威+悄悄在此留下版了个权的信息说:

    为了随机读写单文件数据库,能用的.NET Framework内置序列化工具目前只找到了BinaryFormatter和BinaryReader两个。由于使用BinaryReader需要写的代码更多更复杂,我暂定使用BinaryFormatter。

    为了更详细说明用BinaryFormatter实现单文件数据库序列化/反序列化的思想,我做了如下一个Demo。

      1             const string strHowSingleFileDBWorks = "HowSingleFileDBWorks.db";
      2 
      3             // 首先,创建数据库文件。
      4             using (FileStream fs = new FileStream(strHowSingleFileDBWorks, FileMode.Create, FileAccess.Write))
      5             { }
      6 
      7             // 然后,在App中创建了一些对象。
      8             Cat cat = new Cat() { Id = 1, Name = "汤姆" };
      9             Cat cat2 = new Cat() { Id = 2, Name = "汤姆的媳妇" };
     10             Fish fish = new Fish() { Id = 3, Weight = 1.5f };
     11 
     12 
     13             // 然后,用某种序列化方式将其写入数据库。
     14             IFormatter formatter = new BinaryFormatter();
     15 
     16             // 写入cat
     17             long catLength = 0;
     18             using (MemoryStream ms = new MemoryStream())
     19             {
     20                 byte[] bytes;
     21                 formatter.Serialize(ms, cat);
     22                 ms.Position = 0;
     23                 bytes = new byte[ms.Length];
     24                 catLength = ms.Length;// 在实际数据库中,catLength由文件字节管理器进行读写
     25                 ms.Read(bytes, 0, bytes.Length);
     26                 using (FileStream fs = new FileStream(strHowSingleFileDBWorks, FileMode.Open, FileAccess.Write))
     27                 {
     28                     fs.Position = 0;// 在实际数据库中,需要指定对象要存储到的位置
     29                     fs.Write(bytes, 0, bytes.Length);//注意,若bytes.Length超过int.MaxValue,这里就需要特殊处理了。
     30                 }
     31             }
     32 
     33             // 写入cat2
     34             long cat2Length = 0;
     35             using (MemoryStream ms = new MemoryStream())
     36             {
     37                 byte[] bytes;
     38                 formatter.Serialize(ms, cat2);
     39                 ms.Position = 0;
     40                 bytes = new byte[ms.Length];
     41                 cat2Length = ms.Length;// 在实际数据库中,cat2Length由文件字节管理器进行读写
     42                 ms.Read(bytes, 0, bytes.Length);
     43                 using (FileStream fs = new FileStream(strHowSingleFileDBWorks, FileMode.Open, FileAccess.Write))
     44                 {
     45                     fs.Position = catLength;// 在实际数据库中,需要指定对象要存储到的位置
     46                     fs.Write(bytes, 0, bytes.Length);//注意,若bytes.Length超过int.MaxValue,这里就需要特殊处理了。
     47                 }
     48             }
     49 
     50             // 写入fish
     51             long fishLength = 0;
     52             using (MemoryStream ms = new MemoryStream())
     53             {
     54                 byte[] bytes;
     55                 formatter.Serialize(ms, fish);
     56                 ms.Position = 0;
     57                 bytes = new byte[ms.Length];
     58                 fishLength = ms.Length;// 在实际数据库中,fishLength由文件字节管理器进行读写
     59                 ms.Read(bytes, 0, bytes.Length);
     60                 using (FileStream fs = new FileStream(strHowSingleFileDBWorks, FileMode.Open, FileAccess.Write))
     61                 {
     62                     fs.Position = catLength + cat2Length;// 在实际数据库中,需要指定对象要存储到的位置
     63                     fs.Write(bytes, 0, bytes.Length);//注意,若bytes.Length超过int.MaxValue,这里就需要特殊处理了。
     64                 }
     65             }
     66 
     67             //查询cat2
     68             using (FileStream fs = new FileStream(strHowSingleFileDBWorks, FileMode.Open, FileAccess.Read))
     69             {
     70                 fs.Position = catLength;// 在实际数据库中,需要指定对象存储到的位置
     71                 object obj = formatter.Deserialize(fs);
     72                 Console.WriteLine(obj);// {2: 汤姆的媳妇}
     73             }
     74 
     75             //删除cat2
     76             // 在实际数据库中,这由文件字节管理器进行控制,只需标记cat2所在的空间为没有占用即可。实际操作是修改几个skip list指针。
     77 
     78             //新增cat3
     79             Cat cat3 = new Cat() { Id = 4, Name = "" };
     80             long cat3Length = 0;
     81             using (MemoryStream ms = new MemoryStream())
     82             {
     83                 byte[] bytes;
     84                 formatter.Serialize(ms, cat3);
     85                 ms.Position = 0;
     86                 bytes = new byte[ms.Length];
     87                 cat3Length = ms.Length;// 在实际数据库中,cat3Length由文件字节管理器进行读写
     88                 ms.Read(bytes, 0, bytes.Length);
     89                 using (FileStream fs = new FileStream(strHowSingleFileDBWorks, FileMode.Open, FileAccess.Write))
     90                 {
     91                     fs.Position = catLength;// 在实际数据库中,需要指定对象要存储到的位置,这里由文件字节管理器为其找到可插入的空闲空间。
     92                     fs.Write(bytes, 0, bytes.Length);//注意,若bytes.Length超过int.MaxValue,这里就需要特殊处理了。
     93                 }
     94             }
     95 
     96             //查询cat cat3 fish
     97             using (FileStream fs = new FileStream(strHowSingleFileDBWorks, FileMode.Open, FileAccess.Read))
     98             {
     99                 object obj = null;
    100                 // cat
    101                 fs.Position = 0;// 在实际数据库中,需要指定对象存储到的位置
    102                 obj = formatter.Deserialize(fs);
    103                 Console.WriteLine(obj);// {1: 汤姆}
    104                 
    105                 // cat3
    106                 fs.Position = catLength;// 在实际数据库中,需要指定对象存储到的位置
    107 
    108                 obj = formatter.Deserialize(fs);
    109                 Console.WriteLine(obj);// {4: 喵}
    110                 
    111                 // fish
    112                 fs.Position = catLength + cat2Length;// 在实际数据库中,需要指定对象存储到的位置
    113 
    114                 obj = formatter.Deserialize(fs);
    115                 Console.WriteLine(obj);// {3: 1.5}
    116             }
    单文件数据库是如何工作的

    今后通过对此Demo进行不断扩展,就可以实现一个单文件数据库了。

    由于FileStream.Length是long类型的,所以理论上的单文件数据库的长度最大为long.MaxValue(9223372036854775807=0x7FFFFFFFFFFFFFFF)个字节,即8589934591GB = 8388607TB = 8191PB = 7EB

  • 相关阅读:
    Java 8实战之读书笔记五:超越Java 8
    Quartz的简单使用
    Quartz实现数据库动态配置定时任务
    Java解析Groovy和Shell的代码
    Spring学习笔记(3)——快速入门
    linux的防火墙端口配置
    气泡提示 纯CSS
    解决LINUX下SQLPLUS时上下左右键乱码问题
    redhat Enterprise Linux 6 VNC安装
    使用mount命令挂载CDROM
  • 原文地址:https://www.cnblogs.com/bitzhuwei/p/how-to-serialize-multiple-objects-in-one-file-and-random-deserialize-any-object.html
Copyright © 2011-2022 走看看