谁能在同一文件序列化多个对象并随机读写(反序列化)?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祝威+悄悄在此留下版了个权的信息说:

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

         [Serializable]
public class Cat
{
public override string ToString()
{
return string.Format("{0}: {1}", this.Id, this.Name);
} public string Name { get; set; } public int Id { get; set; }
} [Serializable]
public class Fish
{
public override string ToString()
{
return string.Format("{0}: {1}", this.Id, this.Weight);
} public float Weight { get; set; } public int Id { get; set; }
}

顺序读写

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

                 SomeFormatter formatter = new SomeFormatter();//某种序列化器

                 Cat cat = new Cat() { Id = , Name = "汤姆" };
Cat cat2 = new Cat() { Id = , Name = "汤姆媳妇" };
Fish fish = new Fish() { Id = , Weight = 1.5f }; using (FileStream fs = new FileStream("singleFileDB.bin", FileMode.Create, FileAccess.ReadWrite))
{
formatter.Serialize(fs, cat);
formatter.Serialize(fs, cat2);
formatter.Serialize(fs, fish);
} object obj = null; using (FileStream fs = new FileStream("singleFileDB.bin", FileMode.Open, FileAccess.Read))
{
obj = formatter.Deserialize(fs);// 1: 汤姆 obj = formatter.Deserialize(fs);// 2: 汤姆媳妇 obj = formatter.Deserialize(fs);// 3: 1.5
}

随机读写

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

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

                 SomeFormatter formatter = new SomeFormatter ();

                 Cat cat = new Cat() { Id = , Name = "汤姆" };
Cat cat2 = new Cat() { Id = , Name = "汤姆媳妇" };
Fish fish = new Fish() { Id = , Weight = 1.5f }; using (FileStream fs = new FileStream("singleFileDB.bin", FileMode.Create, FileAccess.ReadWrite))
{
formatter.Serialize(fs, cat);
formatter.Serialize(fs, cat2);
formatter.Serialize(fs, fish);
} object obj = null; using (FileStream fs = new FileStream("singleFileDB.bin", FileMode.Open, FileAccess.Read))
{
obj = formatter.Deserialize(fs); // 1: 汤姆 long position = fs.Position; obj = formatter.Deserialize(fs); // 2: 汤姆媳妇 fs.Position = position;//位置指针再次指向{2: 汤姆媳妇}的起始位置。(实现随机反序列化)
obj = formatter.Deserialize(fs); // 2: 汤姆媳妇 obj = formatter.Deserialize(fs);// 3: 1.5
}

在反序列化时,我们先得到一个{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这么一下子读到末尾,非常浪费,而且位置指针难以控制,无法用于随机反序列化。

                 SoapFormatterformatter = new SoapFormatter ();

                 Cat cat = new Cat() { Id = , Name = "汤姆" };
Cat cat2 = new Cat() { Id = , Name = "汤姆媳妇" };
Fish fish = new Fish() { Id = , Weight = 1.5f }; using (FileStream fs = new FileStream("singleFileDB.soap", FileMode.Create, FileAccess.ReadWrite))
{
formatter.Serialize(fs, cat);
formatter.Serialize(fs, cat2);
formatter.Serialize(fs, fish);
} object obj = null; using (FileStream fs = new FileStream("singleFileDB.soap", FileMode.Open, FileAccess.Read))
{
Console.WriteLine(fs.Position == fs.Length);// false obj = formatter.Deserialize(fs); // 1: 汤姆
Console.WriteLine(fs.Position == fs.Length);// true obj = formatter.Deserialize(fs); // 2: 汤姆媳妇
Console.WriteLine(fs.Position == fs.Length);// true obj = formatter.Deserialize(fs);// 3: 1.5
Console.WriteLine(fs.Position == fs.Length);// true
}

XmlSerializer

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

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

 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。

             const string strHowSingleFileDBWorks = "HowSingleFileDBWorks.db";

             // 首先,创建数据库文件。
using (FileStream fs = new FileStream(strHowSingleFileDBWorks, FileMode.Create, FileAccess.Write))
{ } // 然后,在App中创建了一些对象。
Cat cat = new Cat() { Id = , Name = "汤姆" };
Cat cat2 = new Cat() { Id = , Name = "汤姆的媳妇" };
Fish fish = new Fish() { Id = , Weight = 1.5f }; // 然后,用某种序列化方式将其写入数据库。
IFormatter formatter = new BinaryFormatter(); // 写入cat
long catLength = ;
using (MemoryStream ms = new MemoryStream())
{
byte[] bytes;
formatter.Serialize(ms, cat);
ms.Position = ;
bytes = new byte[ms.Length];
catLength = ms.Length;// 在实际数据库中,catLength由文件字节管理器进行读写
ms.Read(bytes, , bytes.Length);
using (FileStream fs = new FileStream(strHowSingleFileDBWorks, FileMode.Open, FileAccess.Write))
{
fs.Position = ;// 在实际数据库中,需要指定对象要存储到的位置
fs.Write(bytes, , bytes.Length);//注意,若bytes.Length超过int.MaxValue,这里就需要特殊处理了。
}
} // 写入cat2
long cat2Length = ;
using (MemoryStream ms = new MemoryStream())
{
byte[] bytes;
formatter.Serialize(ms, cat2);
ms.Position = ;
bytes = new byte[ms.Length];
cat2Length = ms.Length;// 在实际数据库中,cat2Length由文件字节管理器进行读写
ms.Read(bytes, , bytes.Length);
using (FileStream fs = new FileStream(strHowSingleFileDBWorks, FileMode.Open, FileAccess.Write))
{
fs.Position = catLength;// 在实际数据库中,需要指定对象要存储到的位置
fs.Write(bytes, , bytes.Length);//注意,若bytes.Length超过int.MaxValue,这里就需要特殊处理了。
}
} // 写入fish
long fishLength = ;
using (MemoryStream ms = new MemoryStream())
{
byte[] bytes;
formatter.Serialize(ms, fish);
ms.Position = ;
bytes = new byte[ms.Length];
fishLength = ms.Length;// 在实际数据库中,fishLength由文件字节管理器进行读写
ms.Read(bytes, , bytes.Length);
using (FileStream fs = new FileStream(strHowSingleFileDBWorks, FileMode.Open, FileAccess.Write))
{
fs.Position = catLength + cat2Length;// 在实际数据库中,需要指定对象要存储到的位置
fs.Write(bytes, , bytes.Length);//注意,若bytes.Length超过int.MaxValue,这里就需要特殊处理了。
}
} //查询cat2
using (FileStream fs = new FileStream(strHowSingleFileDBWorks, FileMode.Open, FileAccess.Read))
{
fs.Position = catLength;// 在实际数据库中,需要指定对象存储到的位置
object obj = formatter.Deserialize(fs);
Console.WriteLine(obj);// {2: 汤姆的媳妇}
} //删除cat2
// 在实际数据库中,这由文件字节管理器进行控制,只需标记cat2所在的空间为没有占用即可。实际操作是修改几个skip list指针。 //新增cat3
Cat cat3 = new Cat() { Id = , Name = "喵" };
long cat3Length = ;
using (MemoryStream ms = new MemoryStream())
{
byte[] bytes;
formatter.Serialize(ms, cat3);
ms.Position = ;
bytes = new byte[ms.Length];
cat3Length = ms.Length;// 在实际数据库中,cat3Length由文件字节管理器进行读写
ms.Read(bytes, , bytes.Length);
using (FileStream fs = new FileStream(strHowSingleFileDBWorks, FileMode.Open, FileAccess.Write))
{
fs.Position = catLength;// 在实际数据库中,需要指定对象要存储到的位置,这里由文件字节管理器为其找到可插入的空闲空间。
fs.Write(bytes, , bytes.Length);//注意,若bytes.Length超过int.MaxValue,这里就需要特殊处理了。
}
} //查询cat cat3 fish
using (FileStream fs = new FileStream(strHowSingleFileDBWorks, FileMode.Open, FileAccess.Read))
{
object obj = null;
// cat
fs.Position = ;// 在实际数据库中,需要指定对象存储到的位置
obj = formatter.Deserialize(fs);
Console.WriteLine(obj);// {1: 汤姆} // cat3
fs.Position = catLength;// 在实际数据库中,需要指定对象存储到的位置 obj = formatter.Deserialize(fs);
Console.WriteLine(obj);// {4: 喵} // fish
fs.Position = catLength + cat2Length;// 在实际数据库中,需要指定对象存储到的位置 obj = formatter.Deserialize(fs);
Console.WriteLine(obj);// {3: 1.5}
}

单文件数据库是如何工作的

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

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

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

  1. Java IO流之普通文件流和随机读写流区别

    普通文件流和随机读写流区别 普通文件流:http://blog.csdn.net/baidu_37107022/article/details/71056011 FileInputStream和Fil ...

  2. JAVA之旅(三十)——打印流PrintWriter,合并流,切割文件并且合并,对象的序列化Serializable,管道流,RandomAccessFile,IO其他类,字符编码

    JAVA之旅(三十)--打印流PrintWriter,合并流,切割文件并且合并,对象的序列化Serializable,管道流,RandomAccessFile,IO其他类,字符编码 三十篇了,又是一个 ...

  3. C#文件序列化

    前言 最近,为了实现Unity游戏数据的加密,我都把注意力放到了C#的加密方式身上,最简单的莫过于C#的序列化了,废话不多说,直接开始 准备工作 在使用文件序列化前我们得先引用命名空间 using S ...

  4. 计算机程序的思维逻辑 (60) - 随机读写文件及其应用 - 实现一个简单的KV数据库

    57节介绍了字节流, 58节介绍了字符流,它们都是以流的方式读写文件,流的方式有几个限制: 要么读,要么写,不能同时读和写 不能随机读写,只能从头读到尾,且不能重复读,虽然通过缓冲可以实现部分重读,但 ...

  5. 序列化与反序列化 - BinaryFormatter二进制(.dat)、SoapFormatter(.soap)、XmlSerializer(.xml)

    序列化的作用是什么?为什么要序列化? 1.在进程下次启动时读取上次保存的对象的信息. 2.在不同的应用程序域或进程之间传递数据. 3.在分布式应用程序中的各应用程序之间传输对象. 所为序列化,就是将对 ...

  6. Java编程的逻辑 (60) - 随机读写文件及其应用 - 实现一个简单的KV数据库

    本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...

  7. ObjectOutputStream:对象的序列化流 ObjectInputStream:对象的反序列化流

    package com.itheima.demo04.ObjectStream; import java.io.FileOutputStream; import java.io.IOException ...

  8. 用非常硬核的JAVA序列化手段实现对象流的持久化保存

    目录 背景 对象流的概念 对象流实例 引入一张组织结构图 定义组织架构图的类 类的完整结构 用对象流保存组织架构的对象信息 核心代码 用对象流读取文件并输出 核心代码 总结 背景 在OOP(面向对象编 ...

  9. 「C语言」文件的概念与简单数据流的读写函数

    写完「C语言」单链表/双向链表的建立/遍历/插入/删除 后,如何将内存中的链表信息及时的保存到文件中,又能够及时的从文件中读取出来进行处理,便需要用到”文件“的相关知识点进行文件的输入.输出. 其实, ...

随机推荐

  1. Python-基础练习题2

    编写登陆接口 输入用户名密码 认证成功后显示欢迎信息 输错三次后锁定 #!/usr/bin/env python # _*_ coding:utf8 _*_ import getpass Userna ...

  2. Beginning Scala study note(4) Functional Programming in Scala

    1. Functional programming treats computation as the evaluation of mathematical and avoids state and ...

  3. EXT5 时间框控制(开始时间不能大于结束时间)

    1.网上看的大部分代码都是利用vtype : 'dateRange'  EXT的这个属性,但是可能由于环境问题还是怎么样,我就是实现不了想要的效果. 然后研究了一下,在时间框的listeners 监听 ...

  4. Arc GIS engine10.2与VS2012的安装及匹配步骤

      本文章已收录于:   .embody { padding: 10px 10px 10px; margin: 0 -20px; border-bottom: solid 1px #ededed } ...

  5. vs2012 发布web应用程序

    Visual Studio 2012 Visual Studio Express 2012 for Web 与 的Visual Studio 2010  Visual Studio Web发布更新 与 ...

  6. jQuery之ajax错误调试分析

    jQuery中把ajax封装得非常好.但是日常开发中,我偶尔还是会遇到ajax报错.这里简单分析一下ajax报错 一般的jQuery用法如下,ajax通过post方式提交"汤姆和老鼠&quo ...

  7. hdu3068马拉车

    其实马拉车还真是最好理解的算法(感觉初中的时候好像讲过类似的,但是当时就没有认真听) 没想到一个简单的优化能变成O(n),感觉碉堡 不说了,马拉车裸题,我在写的时候只保留了id,没保留mx,希望能形成 ...

  8. SPOJ FASTFLOW网络流水题

    Dinic=bfs+dfs  = = 用bfs算出到原点的最短路径(每条残存都算1) 然后每次都跑两端只差1的路径跑dfs,并且一直跑到不能跑 一个优化:如果一个点流出的量已经到流入量了就可以返回上一 ...

  9. PHP+JQUEY+AJAX实现分页【转】

    HTML CSS #list{width:680px; height:530px; margin:2px auto; position:relative} #list ul li{float:left ...

  10. 利用浏览器LocalStorage缓存图片,视频文件

    文章路径:https://hacks.mozilla.org/2012/02/saving-images-and-files-in-localstorage/