热门标签 | HotTags
当前位置:  开发笔记 > 程序员 > 正文

NET常见类系列探究——序列化和反序列化的应用

l为什么要序列化?为了达到软件“人性化”的目的,很多开发制作软件的程序商们非常习惯将某些预订好的设置(诸如“皮肤”等)设定成最佳状态,保存到一个信息文件中;或者当用户改变这一状态时

 l       为什么要序列化?

为了达到软件“人性化”的目的,很多开发制作软件的程序商们非常习惯将某些预订好的设置(诸如“皮肤”等)设定成最佳状态,保存到一个信息文件中;或者当用户改变这一状态时软件自身也会将用户设置的状态记载下来,这样用户下次开启软件就不必费神费力地重新去设置适合自己的软件布局状态了。

       像这种“设置记载”的做法最早(当然,现在仍然保留着)是使用类似于文本文件的形式(ini文件)进行存储,大概格式如下:

 

[区域块名称]

;注释

变量(字段)名=值

 

当一个软件开启的时候,它首先加载该文件(如果该文件存在,没有人为改动出现异常的情况下),其步骤分别是:1)读取相关设置的区域块名称(标识符,表明统一的某个模块的设置)2)读取其名下所有的变量(字段,又称属性)和值 3)根据标识符和变量要求,改变软件界面,呈现给用户看。

 

比如说一个股票软件的主界面(呈现用户自选股)的页面,就应该包含诸如“背景色”、“涨的颜色”、“跌的颜色”等属性。那么按照中国大陆的习惯,可能设置文件中应该存在这样的信息:

 

[SelfChoose]

;选股票的SQL

Sql=”select * from Customers where cid=?”

;背景色

BackGroundColor=black

;涨的颜色

RisingUp=red

;跌的颜色

DropDown=green

 

这样做当然是一目了然的,但是由于是文本文件,所以客户可以轻易打开直接进行编辑修改。如果有些小孩或者是恶意的黑客们加入了些具备破坏性的代码,其结果可想而知。而且读取这样的文件虽然可以借助微软的DLL类库(WritePrivateProfileStringGetPrivateProfileString),但是读取的时候仍旧是离散的,每次读入一个值,还要根据“区域块名”和某个隶属的“变量名”决定究竟是设置改变哪个块的哪个部分,如果一个软件有很多设置的话代码量是非常可怕的。

 

后来经过改进,微软推出了注册表和XML来替代它,这两种方式都有自己的对应类和接口等操作函数(方法),不过XML的安全性还是不高(仍旧基于文本格式,可以打开直接修改),至于注册表一旦写坏,用户就有“冒着重装系统的危险”。

现在的问题在于:如何让客户的设置按照预期的目标进行存储,并且要符合OOP的思想,而且又要做到安全性高(最好是用记事本无法打开,或者打开根本看不懂)?

 

要做到这点,我们不得不考虑这样的设计:

1)基于OOP思想的设置:它本身必须是一个类,因为只有这样才可能符合OOP的逻辑(不一定有方法和事件,但私有变量和公开属性必不可少);最好做到这个文件读入不是分批、离散字符串式,而是通过某种途径可以还原成一个类进行操作。

2)安全性:要么加密,要么以二进制方式直接存储对象。

 

幸运的是,微软已经帮助我们实现了这种模式;这就是下面要讨论的“OOP序列化”问题。

 

l       如何序列化?

将某个软件模块以及其属性抽象出来,写成一个普通类(一般无方法事件,存在公开属性和私有变量),比如上面“股票设置”例子可以写成这样一个类:

Code
[Serializable]
public class SelfChoose
{
    
private const string sql=”select * from Customers where cid={0}”;
    
public string SQL
    {
        
return string.Format(sql,Id);
    }

    
private int id=0;
    
public int Id
    {
        
//省略Get、Setf属性
    }

    
//省略BackGround、RisingUp和DropDown的属性
}

当需要读取的时候,在界面的Load的事件中可以这样写:

 

Code
using System.Runtime.Serialization.Formatters.Binary;

private Form_Load(sender as object, e as EventArgs)
{
    
using (FileStream fs=new FileStream(序列化文件存放位置,FileMode.Open))
    {
        BinaryStream bs
=new BinaryStream();
        SelfChoose sc
=bs.Deserialize(bs) as SelfChoose;
    窗体的名称.BackGround
=sc. BackGround
……
    }
}

当用户关闭窗体的时候,在关闭的事件中保存用户的设置:

Code
private Form_Closing(sender as object, e as EventArgs)
{
    
using (FileStream fs=new FileStream(序列化文件存放位置,FileMode.OpenOrCreate))
    {
SelfChoose sc
=new SelfChoose();
sc.BackGroundColor
=窗体颜色. BackGroundColor
//其余属性设置……
BinaryStream bs=new BinaryStream();
bs.Serialize(fs,sc);
    }
}
 

这里需要注意几个问题:

1)要(反)序列化的时候,必须引入“using System.Runtime.Serialization.Formatters.Binary;”

命名空间。

2BinaryStream是一个二进制文件的读写类,其中包含着两个方法:

       Ivoid Serialize (Stream s, object obj):要被写入(序列化)的类以及写入的流(写入的位置)。

       IIobject Deserialize (Stream s):要被读出类(反序列化)的源(注意还原成原来的类的时候需要强制类型转换)。

 

l       (反)序列化的另类妙用

大家不知道是否注意这样一个问题——为什么BinaryStream的输入、输出参数竟然是Stream(而不是FileStream)?难道说(反)序列化不仅仅是用来做设置记录一类的?

我们说Stream是文件读写流的一个抽象类,许多IO类(FileStreamMemoryStream等)都继承了该类。现在就来使用MomoryStream类构造一个简单的“深复制”方法

所谓“深复制”,就是指将类自身中的所有数据——无论什么类型,完全克隆拷贝一份返回给用户(某些类实现了“Clone”方法,但是都是“潜复制”,即把自身直接返回出去——return this)这样做的问题显然会带来一个问题——那就是“牵一发动全身”(任何地方改变了这个副本类的某个属性,则原来版本也“同步”变更了)。而且,如果克隆的类中带有引用类型(诸如自定义类或数组),则问题更加复杂化。传统的做法往往是一一拷贝这些引用类型的副本,但是这样做显然费时费力。

现在既然(反)序列化可以把类存储在文件中,并且还原成原始保存的状态,那么为何不使用这种手段进行“深复制”呢?不过,要提醒诸位的是,(反)序列化实现深复制不必每次把类存储在特定文件中,只要在内存中进行就可以了,所以要使用到MemoryStream。典型代码如下:

 

Code
public class XXXX:ICloneable
{
    
//引用类型、值类型和自定义类型……,Get、Set方法
    public override object Clone()
    {
        MemoryStream ms
=new MemoryStream();
        BinaryStream bs
= BinaryStream();
        bs.Serialize(ms,
this);
        bs.Position
=0;
        
return bs.Deserialize(ms):
    }
}

以上XXX类实现了ICloneable接口方法,使用了内存式的“(反)序列化克隆”手段达到了目的。此处注意两点:

1)不能使用using代码块,因为using代码块会隐含调用Dispose()方法,而MemoryStream没有这个方法(没有实现IDisposable接口)。

2)序列化之后,必须设置Position=0,否则发生异常(因为MemoryStream在序列化之后指针已经在末尾了,此时反序列化需要从头读取的;FileStream不存在这样的问题)。


推荐阅读
  • 本文详细分析了JSP(JavaServer Pages)技术的主要优点和缺点,帮助开发者更好地理解其适用场景及潜在挑战。JSP作为一种服务器端技术,广泛应用于Web开发中。 ... [详细]
  • CentOS 7 磁盘与文件系统管理指南
    本文详细介绍了磁盘的基本结构、接口类型、分区管理以及文件系统格式化等内容,并提供了实际操作步骤,帮助读者更好地理解和掌握 CentOS 7 中的磁盘与文件系统管理。 ... [详细]
  • 三星W799在2011年的表现堪称经典,以其独特的双屏设计和强大的功能引领了双模手机的潮流。本文详细介绍其配置、功能及锁屏设置。 ... [详细]
  • 在API测试中,我们常常需要通过大量不同的数据集(包括正常和异常情况)来验证同一个接口。如果为每种场景单独编写测试用例,不仅繁琐而且效率低下。采用数据驱动的方式可以有效简化这一过程。本文将详细介绍如何利用CSV文件进行数据驱动的API测试。 ... [详细]
  • 本文详细介绍了如何解决Uploadify插件在Internet Explorer(IE)9和10版本中遇到的点击失效及JQuery运行时错误问题。通过修改相关JavaScript代码,确保上传功能在不同浏览器环境中的一致性和稳定性。 ... [详细]
  • 本文将介绍如何使用 Go 语言编写和运行一个简单的“Hello, World!”程序。内容涵盖开发环境配置、代码结构解析及执行步骤。 ... [详细]
  • Linux 系统启动故障排除指南:MBR 和 GRUB 问题
    本文详细介绍了 Linux 系统启动过程中常见的 MBR 扇区和 GRUB 引导程序故障及其解决方案,涵盖从备份、模拟故障到恢复的具体步骤。 ... [详细]
  • 本文探讨了Hive中内部表和外部表的区别及其在HDFS上的路径映射,详细解释了两者的创建、加载及删除操作,并提供了查看表详细信息的方法。通过对比这两种表类型,帮助读者理解如何更好地管理和保护数据。 ... [详细]
  • 1:有如下一段程序:packagea.b.c;publicclassTest{privatestaticinti0;publicintgetNext(){return ... [详细]
  • 本文详细介绍了如何在Linux系统上安装和配置Smokeping,以实现对网络链路质量的实时监控。通过详细的步骤和必要的依赖包安装,确保用户能够顺利完成部署并优化其网络性能监控。 ... [详细]
  • 深入理解Tornado模板系统
    本文详细介绍了Tornado框架中模板系统的使用方法。Tornado自带的轻量级、高效且灵活的模板语言位于tornado.template模块,支持嵌入Python代码片段,帮助开发者快速构建动态网页。 ... [详细]
  • PHP 5.2.5 安装与配置指南
    本文详细介绍了 PHP 5.2.5 的安装和配置步骤,帮助开发者解决常见的环境配置问题,特别是上传图片时遇到的错误。通过本教程,您可以顺利搭建并优化 PHP 运行环境。 ... [详细]
  • 本文介绍了在使用Visual Studio 2015进行项目开发时,遇到类向导弹出“异常来自 HRESULT:0x8CE0000B”错误的解决方案。通过具体步骤和实践经验,帮助开发者快速排查并解决问题。 ... [详细]
  • 本文介绍了Java并发库中的阻塞队列(BlockingQueue)及其典型应用场景。通过具体实例,展示了如何利用LinkedBlockingQueue实现线程间高效、安全的数据传递,并结合线程池和原子类优化性能。 ... [详细]
  • 1.如何在运行状态查看源代码?查看函数的源代码,我们通常会使用IDE来完成。比如在PyCharm中,你可以Ctrl+鼠标点击进入函数的源代码。那如果没有IDE呢?当我们想使用一个函 ... [详细]
author-avatar
_妃妃教主
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有