永久对象存储之c++(二)
打开存储器
POST++ 使用内存内存映射机制访问文件中的数据。在 POST++ 通过两个不同的方法提供数据一致性。首先而且更加先进的是基于事务机制使用的镜像页面在出错后来提供存储恢复和事务回滚。在写镜像页面前创建运算被使用。这个运算以如下方式执行:所有文件映射页面被设置为只读保护。任何对这些页面的写访问将引起访问违反异常。这个异常被一个特别的句柄捕获,它改变页面保护为可读写并放这个页面的拷贝在事务记录文件中(记录文件名为原文件名和后追“。log”的组合)。所有接下来这个页面的写操作将不再引起页面错误。存储器方法 commit() 刷新所有的改变页面到磁盘上并截断记录文件。storage::commit() 方法被 storage::close() 隐含调用。如果错误在 storage::commit() 操作前发生,所有的改变将通过拷贝事务记录中改变的页面到存储数据文件被复原。同样所有的改变可以通过显式调用
storage::rollback() 方法来复原。通过指定 storage::open() 方法的 storage::use_transaction_log 属性来选择文件访问事务所基于的模式。
另外一个提供数据一致性的手段基于写拷贝机制。在这种情况下源文件没有受到影响。任何试图对文件镜像页面的改变,导致产生一个该页面的拷贝,它从系统交换区种分配并具有读写许可。文件直到显式调用 storage::flush() 方法时才更新。这个方法写数据到临时文件(带后缀“。tmp”)然后重命名为原来的。因此这个操作形成文件的一个原子更新(当然假设在操作系统能保证 rename() 操作的原子数)。
注意:如果你没有使用事务处理,storage::close() 方法不会刷数据到文件中。所以如果你在此前调用 storage::flush() 方法所有的自上次 flush 之后的改变将会丢失。
Windows 95 细节:在 Windows 95 中重命名到已有的文件是不行的,所以源文件首先被保存为带后缀“。sav”的文件名。然后后缀为“。tmp”的临时文件被重命名为原来的名字以及最后的旧的拷贝被删除。所以如果错误发生在 flush() 操作中并且之后你找不到存储文件,请不要惊慌,只需找到以后缀“。sav”结束的文件并且重命名为原来的就可以了。
提示:如果你计划在程序执行期间保存数据我强烈建议你使用事务处理。也可以采用写拷贝的途径但是这样需要多得多的消耗。同样如果存储非常大事务处理也通常更好,因为生成临时的文件拷贝需要很多的磁盘空面和时间。
这里有几个属性供存储器 open()
方法使用:
support_virtual_functions 如果存储器中的对象带有虚函数则必须设置这个属性。如果没有设置这个属性,POST++ 假定所有的永久对象在存储中只包含有引用(对存储器中其他对象的)。所以只有在数据文件映像的基地址发生改变时才需要调整引用(这个地址被存放在数据文件的第一个字中并且 POST++ 通常试图映像文件到相同的地址上来避免不必要的引用调整)。但是如果对象类包含虚函数,指向虚表的指针被放在对象内。如果你重新编译你的应用,这个标的地址可能改变。POST++ 库比较执行对象的时间戳和这个应用产生的数据库的时间戳进行比较。如果这个时间戳不等的话,则会校正虚表的指针。为了得到应用时间戳 POST++ 必须可以定位执行文件对象。不幸的是没有找到执行文件名的简便的方法。在 Unix 下POST++ 看命令行解释器设置的环境变量“_”的值。但如果进程不是从命令行执行的(比如通过system())或者工作目录被 chdir() 改变这个方法将不起作用。最简单的方法是使用文件comptime.cxx,它必须在每次重编译你的应用时被编译并和存储库一同被链接。在 Windows 中没有这个问题,执行映像的名称可以通过 Win32 API 得到。在存储器打开时 POST++ 比较这个时间戳和数据文件的时间辍,如果他们不等并且指定了 support_virtual_functions 属性那么校正所有对象(通过调用缺省构造函数)。 read_only 通过设置这个属性程序员说明他只需要数据文件读权限。POST++ 将创建数据文件的只读视图并且任何改变存储器中的对象或者分配新对象的尝试将会导致保护违例错。这里有一个例外:如果不能够映像数据文件到相同的地址或者应用程序发生改变时并且指定了 support_virtual_functions ,那么对此区域的保护被临时改变为写拷贝并且装载的对象被转换。 use_transaction_log 设置这个属性强制对所有数据文件更新使用事务。影子页面被用来执行事务。事务在第一次修改存储后被打开。通过 storage::commit() 或者 storage::rollback() 操作显式的关闭。方法 storage::commit() 保存所有的改变页面到磁盘上并且截断事务记录,方法storage::rollback()忽略此次事务中的所有改变。 no_file_mapping 缺省情况下 POST++ 将映像数据文件到进程虚拟内存中。这种情况下打开数据库的时间将大大减少,因为文件页面将在需要时调入。但是如果数据库大小不是特别大或者数据库中所有数据需要立即访问,那么把文件读入内存优于使用虚拟内存映像因为这种情况下没有额外的页面溢出错误。标志 no_file_mapping 阻止 POST++ 映像文件并根据分配的内存段读文件。 fault_tolerant 这个标志被应用程序用于在系统或应用出错情况下想保护数据库的一致性。如果使用了事务 use_transaction_log 这个标志不必指定,因为一致性可以由事务机制来提供。如果没有指定 use_transaction_log 标志并且设置了 fault_tolerant 标志, POST++ 将不改变源文件而保持它的一致性。这依靠读文件到内存中(如果没有设置 no_file_mapping 标志)或者使用写拷贝页面保护。在后一种情况下试图改变映像到文件的页面将导致在系统交换文件中生成页面拷贝。flush() 方法将保存内存内数据库的映像到临时文件中然后使用原子操作重命名到源文件。如果没有指定 fault_tolerant 标志,POST++ 在数据库页面上原有位置进行修改,提供最大的应用性能(因为没有拷贝修改页面和保存数据库映像到临时文件的额外开销)。在修改页面没有立刻刷新到磁盘的条件下,部分改变可能因为系统错误而丢失(最坏的事是部分修改的页面保存了而另外一些没有保存 - 这样数据库的一致性可能被搅乱了)。 do_garbage_collection 当设置了这个属性时 POST++ 将在打开储存器时执行垃圾收集。垃圾收集操作和指针对齐联系在一起。使用垃圾收集往往比手工内存释放来的安全(考虑到挂起的引用问题),但是显式内存释放开销较少。POST++ 中的垃圾收集相比显式内存分配有一个更大的优势:内存收集器对小对象使用的页面进行优化。如果页中没有 已分配的小对象那么垃圾收集器将在空闲页中包含这一页。这不会在显式释放时完成,因为小对象的空闲单元被串成链不能简单从这个链中移开(在垃圾收集器中所有的链被重新构造)。即使你使用显式内存释放,我仍建议你每隔一定时间做垃圾收集来检查引用的一致性和没有内存泄漏(garbage_collection 方法返回释放对象的数目,如果你确信你已经释放了所有的不能到达的对象,那么这个值将会是0)。考虑到垃圾收集器修改存储中所有的对象(设置掩码位),重连链中 空闲对象),在事务模式下运行GC可能是消耗时间和磁盘空间的操作,因为所有文件中的页将被拷贝到事务记录文件中)。
你可以通过 file::max_file_size 变量指定存储文件的最大尺寸。如果数据文件的大小比 file::max_file_size 并且模式不是 read_only,那么虚拟空间 size_of_file - file::max_file_size 以外的字节将被保留在文件映像空间的后面。当存储大小扩展时(因为分配新对象),这些页面将被提交(在 Windows NT)并被使用。如果文件大小大于 file::max_file_size 或者使用了 read_only 模式,那么映像区域的大小和文件大小一致。在后一种情况下不可能进行存储扩展。在 Windows 中我使用 GlobalMemoryStatus() 方法来得到关于系统真实可分配的虚拟内存的信息并减少 file::max_file_size 为该值。不幸我发现在 Unix 中没有轻便的调用可用来达到相同的目的(getrlimit 不返回用户进程可使用的虚拟内存的确切信息)。
对象存储的接口在文件 storage.h 定义并且实现部分可在 storage.cxx 中看到。依赖于操作系统的映像内存文件的部分被封装在 file 类中,其定义在 file.h 实现在 file.cxx.
今天达内IT培训对有关c++永久对象存储的知识就先介绍到这里,如果想了解更多关于c++永久对象存储的知识,请关于我们的更新:《永久对象存储之c++(三)》,也可以访问我们的网站:http://www.yc-edu.org/。
北京php培训
地址:苏州桥东北角西海国际北侧(北京科技经营管理学院院内北楼一层)
电话:010-51515100
网址:http://www.yc-edu.org/
浙公网安备 33010602011771号