谁能在同一文件序列化多个对象并随机读写(反序列化)?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. ORACLE中常见SET指令

    1         SET TIMING ON 说明:显示SQL语句的运行时间.默认值为OFF. 在SQLPLUS中使用,时间精确到0.01秒.也就是10毫秒. 在PL/SQL DEVELOPER 中 ...

  2. BitHacks

    备份文件时看到的.我以前居然下过这东西. 2016-12-4 12:05:52更新 纯文本格式真棒.假如使用word写的我能拷过来格式还不乱?? Markdown真好. Bit Hacks By Se ...

  3. OpenGL帧缓存对象(FBO:Frame Buffer Object)(转载)

    原文地址http://www.songho.ca/opengl/gl_fbo.html 但有改动. OpenGL Frame BufferObject(FBO) Overview: 在OpenGL渲染 ...

  4. 如何在springmvc的请求过程中获得地址栏的请求

    由于spring的dispatchservlet会通过当前的handlermapping来将当前地址栏的请求映射为实际的项目目录结构,所以使用普通的request.getRequestURL()是无法 ...

  5. POJ No.2386【B007】

    [B007]Lake Counting[难度B]—————————————————————————————————————————— [Description] Due to recent rains ...

  6. 使用VisualVM检测

    下载 https://visualvm.github.io/ 检测远程服务器 转自:http://blog.csdn.net/yangkangtq/article/details/52277794 授 ...

  7. Python for Infomatics 第13章 网页服务二(译)

    注:文章原文为Dr. Charles Severance 的 <Python for Informatics>.文中代码用3.4版改写,并在本机测试通过. 13.4 JavaScript ...

  8. Easyui Ajax验证Form表单。。。

        今天做项目用到easyui Ajax验证表单.本想自定义一个easyui的验证,查资料发现easyui 自带了一个通用的验证!见以下下截图. 后台返回值 true验证通过,返回false验证失 ...

  9. 图论 - 寻找fly真迹

    一天fly正坐在课堂上发呆,突然,他注意到了桌面上的一个字符串S1S2S3S4...Sn,这个字符串只由字符"a","b"和"c"构成.刚好 ...

  10. C# 使用access,报错:标准表达式中数据类型不匹配

    最初以为是数字类型造成的,结果最后才发现是日期格式错误,这是我的参数 db.AddInParameter(dbCommand, "savedate", DbType.DateTim ...