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.
POST++ 的安装
POST++ 的安装十分简单。目前在以下系统已经过测试:Digital Unix, Linux, Solaris, Windows NT 4.0, Windows 95. 我希望对于大部分所有其他新 Unix 方言(AIX, HP-UX 10, SCO…)也没有问题。不幸的是我没有时用过这些系统。在 Windows 下我使用 Microsoft Visual C++ 5.0 和 Borland 5.02 compilers 编译。Visual C++ 的 Makefiel 是 makefile,Broland C++ 的 Makefile 是 makefile。
为使用 POST++ 唯一你需要的东西就是函数库(在 Unix 下是 libstorage.a 在 Windows 下是and storage.lib)。这个库可以通过运行 make 命令生成。有个特别的 MAKE.BAT 用于 Microsoft Visual C++,它使用 makefile.mvc 作为输入调用 NMAKE (如果你正在使用 Borland 请编辑这个文件或者通过 make.exe -f makefile.bcc 命令调用)。
在 Unix 下的安装可以通过拷贝 POST++ 库和同文件到一些标准系统目录下来完成。你必须为 makefile 里 INSTALL_LIB_PATH 和 INSTALL_INC_PATH 变量设置恰当的值并运行 make install 命令。INSTALL_LIB_PATH 的缺省值为 /usr/local/lib INSTALL_INC_PATH 的是 /usr/local/include。你可以通过明确指定路径到 POST++ 目录来编译和链接而避免拷贝文件到系统目录中。
POST++ 类库
POST++ 包括一些持久类的定义,她们可以用于你的应用也可以作为开发 POST++ 类的好例子。你可以看见那些类的实现中甚至没有 POST 特定的代码。这些类包括 array(数组), matrix(矩阵), string(字符串), L2-list(L2-列表), hash table(哈希表), AVL-tree(AVL-树), R-tree(R-数), text object(文本对象)。 R-tree 提供了提供空间对象(包含空间对等物的对象)的快速访问。文本对象包含了 Boyer and Moore 算法的修正,扩展到由 OR/AND 关系组合的多样式搜索。这些类的定义可以在以下文件中发现:
描述接口实现Arrays of scalars and references, matrixes and stringsarray.harray.cxxL2-list and AVL-treeavltree.havltree.cxxHash table with collision chainshashtab.hhashab.cxxR-tree with quadratic method of nodes splittingrtree.hrtree.cxxT-tree (combination of AVL tree and array)ttree.httree.cxxText object with modified Boyer and Moore search algorithmtextobj.htextobj.cxx
在论文 "A study of index structures for main memory database management systems" 中 T.J. Lehman and M.J Carey 提议使用 T-trees 作为主要内存数据库的一个存储的有效的数据结构。T-trees 基于 AVL 树由 Adelson-Velsky and Landis 提议。在这个分段中,我们提供 T-trees 一个概览作为在 POST++ 中的实现。
像 AVL 树一样,T-tree的左子树和右子树高度差不大于1。和 AVL 树不一样,T-tree的每一个节点排列次序保存多个关键值而不是单一关键值。一个节点里最左边和最右边关键值定义包含在这个节点内关键值的范围。这样,一个节点左子树只包含比最左关键值小的关键值,而右子树包含比该节点最右关键值大的关键值。节点内落在最小和最大关键值之间的关键值被称作被该节点约束( bounded )。注意等于最大关键字或最小关键字的关键字可以根据索引是否全局唯一和查找条件认为是被约束或不被约束(e.g. “大于”("greater-than") 相对于 “大于等于”("greater-than or equal-to"))。
一个既有左子树也有右子树的节点被归为内部节点(internal node),仅有一个子树的节点被归为不完全叶(semi-leaf),一个没有子树的节点被归为叶(leaf)。为了保持高占用率,每个内部节点具有一个必须包含的关键值的最小数目(一般为 k-2,如果 k 是可以排列在一个节点里的关键字的最大数目)。然而,对于叶子和不完全叶来说没有占用率条件。
在T-tree中查找相当于直接查找。对于每一个节点,检查看一下关键值是否在该节点最左和最右关键值之间;如果是这种情况,就是说节点里包这个关键值的话就返回该值(否则关键值不包含在树中)。否则,如果关键值比最左关键值小,那么查找左子树,反之搜索右子树。重复这个过程直到发现这个关键值或者查找到null节点。