数据管理_DM的实现

DataManager 的实现

DataManager 是数据库系统中的核心组件,负责管理底层数据的访问、修改和事务处理。它是 DM 层直接对外提供方法的类,用来对上层其他模块提供数据操作的API的,同时也实现了对 DataItem 对象的缓存管理。DataItem 存储的 key 是由页号和页内偏移组成的一个 8 字节无符号整数,页号和偏移各占 4 字节。

DataManager 的核心功能
  1. 数据缓存与管理DataManager 通过缓存 DataItem 对象,提供高效的数据访问。DataItemkey 是一个 8 字节无符号整数,其中页号和页内偏移各占 4 字节。这种设计允许快速定位和访问数据,减少了对底层存储的频繁访问。
  2. 数据访问与操作DataManager 提供了读取、插入和修改等数据操作方法。通过这些方法,数据库的上层模块可以方便地对数据进行操作。
  3. 事务管理DataManager 支持事务管理,确保数据操作的原子性。事务管理能够保证在事务提交或回滚时,数据的一致性和完整性。
  4. 日志记录与恢复DataManager 在数据修改操作前后执行日志记录,以确保数据的安全性和可靠性,并支持系统崩溃后的数据恢复。
  5. 页面索引管理DataManager 还实现了页面索引管理功能,通过页面索引,可以快速定位到合适的空闲空间,提高数据插入的效率和性能。
  6. 文件初始化与校验DataManager 在创建和打开数据库文件时,进行文件的初始化和校验,确保文件的正确性和完整性。

DataManager 的uid的生成与解析

DataItemDataManager 中的存储和管理是通过一个唯一标识符 Uid 来实现的。这个 Uid 是由页面编号 (pgno) 和页面内偏移量 (offset) 组成的一个 8 字节无符号整数,其中页号和偏移量各占 4 字节。高4字节的32位表示DataItem存储的Page的页号低4字节的32位中只有低16位有意义,这16位表示DataItem存储的Page中的页内偏移,而高16位无意义 , 这里以pgno = 2 和 offset = 0来演示生成和解析 Uid 的详细过程。

1、生成 Uid 通过将页面编号 (pgno) 和偏移量 (offset) 组合成一个 8 字节无符号整数来生成 Uid。这里使用了位移和按位或运算。

public class Types {public static long addressToUid(int pgno, short offset) {long u0 = (long) pgno;long u1 = (long) offset;return u0 << 32 | u1; // 左移32位表示页号,按位或运算将页号和偏移量合并成一个Uid}
}

按位或:有1为1;双0为0

2、从 Uid 中提取偏移量 (**offset**) 为了从 Uid 中提取出偏移量,需要对 Uid 进行按位与运算。偏移量是 Uid 的低 16 位,通过与 16 位全1(0xFFFF)进行按位与操作可以提取出偏移量。(双1为1;其余为0)

// 提取偏移量,偏移量占 Uid 的低16位
short offset = (short) (uid & ((1L << 16) - 1)); // 按位与操作提取出低16位的偏移量

3、从 Uid 中提取页面编号 (**pgno**) 提取页面编号则需要将 Uid 右移 32 位,以便将高 32 位对齐到低位,然后通过按位与操作提取出页面编号。

// 右移32位,将高32位对齐到低位
uid >>>= 32;
// 提取页面编号,页面编号占 Uid 的高32位
int pgno = (int) (uid & ((1L << 32) - 1)); // 按位与操作提取出页面编号

编码实现

DM结构定义
public class DataManagerImpl extends AbstractCache<DataItem> implements DataManager {TransactionManager tm; //TM事务管理器PageCache pc; //PC页面缓存Logger logger;  //数据库日志PageIndex pIndex;  //页面索引Page pageOne;  //第一页
创建新的DM(构造方法)
    public DataManagerImpl(PageCache pc, Logger logger, TransactionManager tm) {super(0);this.pc = pc;this.logger = logger;this.tm = tm;this.pIndex = new PageIndex();}
创建/打开DM
    /*** 创建页缓存(PageCache):通过调用PageCache.create(path, mem)方法,创建一个页缓存对象pc。path参数指定了数据存储的路径,mem参数指定了内存大小。* 创建日志记录器(Logger):通过调用Logger.create(path)方法,创建一个日志记录器对象lg。path参数同样指定了数据存储的路径。* 创建数据管理器实现类(DataManagerImpl)实例:使用创建的页缓存对象pc、日志记录器对象lg和事务管理器对象tm作为参数,创建一个数据管理器实现类DataManagerImpl的实例dm。* 初始化页一(PageOne):调用dm.initPageOne()方法,初始化数据管理器中的页一。页一是一个特殊的页,通常用于存储数据库的元数据或配置信息。* 返回数据管理器实例:将创建并初始化好的DataManagerImpl实例dm作为返回值,返回给调用者。这是数据库打开过程中的初始化阶段,其中创建了数据管理器所需的核心组件,并对关键的页进行了初始化操作。* @param path* @param mem* @param tm* @return*/public static DataManager create(String path, long mem, TransactionManager tm) {PageCache pc = PageCache.create(path, mem); //111111Logger lg = Logger.create(path);DataManagerImpl dm = new DataManagerImpl(pc, lg, tm);dm.initPageOne();return dm;}//111111。缓存public static PageCacheImpl create(String path, long memory) {//1、根据路径创建db文件File f = new File(path+PageCacheImpl.DB_SUFFIX);try {//2、判断文件是否能创建成功if(!f.createNewFile()) {Panic.panic(Error.FileExistsException);}} catch (Exception e) {Panic.panic(e);}//3、文件是否可读写if(!f.canRead() || !f.canWrite()) {Panic.panic(Error.FileCannotRWException);}//4、创建NIO管道和随机文件对象操作类创建的文件FileChannel fc = null;RandomAccessFile raf = null;try {raf = new RandomAccessFile(f, "rw");fc = raf.getChannel();} catch (FileNotFoundException e) {Panic.panic(e);}//5、创建PageCacheImpl对象return new PageCacheImpl(raf, fc, (int)memory/PAGE_SIZE);}public static DataManager open(String path, long mem, TransactionManager tm) {PageCache pc = PageCache.open(path, mem);//2222Logger lg = Logger.open(path);DataManagerImpl dm = new DataManagerImpl(pc, lg, tm);if(!dm.loadCheckPageOne()) {Recover.recover(tm, lg, pc);}dm.fillPageIndex();PageOne.setVcOpen(dm.pageOne);dm.pc.flushPage(dm.pageOne);return dm;}//222222222222public static PageCacheImpl open(String path, long memory) {File f = new File(path+PageCacheImpl.DB_SUFFIX);if(!f.exists()) { //判断文件是否存在Panic.panic(Error.FileNotExistsException);}if(!f.canRead() || !f.canWrite()) {Panic.panic(Error.FileCannotRWException);}FileChannel fc = null;RandomAccessFile raf = null;try {raf = new RandomAccessFile(f, "rw");fc = raf.getChannel();} catch (FileNotFoundException e) {Panic.panic(e);}return new PageCacheImpl(raf, fc, (int)memory/PAGE_SIZE);}

如上两种不同方式需要注意:

  • 从空文件创建需要对第一页进行初始化

  • 从已有文件创建(即打开),需要对第一页进行校验从而判断是否需要执行恢复流程,并重新对第一页生成随机字节

初始化PageIndex
    // 初始化pageIndexvoid fillPageIndex() {//获取当前的pageCache中的页面数量int pageNumber = pc.getPageNumber();//遍历从第二页开始的每一页for(int i = 2; i <= pageNumber; i ++) {Page pg = null;try {//获取第i页pg = pc.getPage(i);} catch (Exception e) {Panic.panic(e);}//获取第i页的空闲空间大小,将第i页的页面编号和空闲空间大小添加到 PageIndex 中pIndex.add(pg.getPageNumber(), PageX.getFreeSpace(pg));pg.release();}}


这个方法的主要目的是在数据库打开时,为数据管理器构建一个页索引,以便后续的数据操作可以快速定位和访问页

加载并检查PageOne
getForCache

也是继承自AbstractCache,只需要从 key 中解析出页号,从 pageCache 中获取到页面,再根据偏移,解析出 DataItem 即可

@Override
protected DataItem getForCache(long uid) throws Exception {// 从 uid 中提取出偏移量(offset),这是通过位操作实现的,偏移量是 uid 的低16位short offset = (short) (uid & ((1L << 16) - 1));// 将 uid 右移32位,以便接下来提取出页面编号(pgno)uid >>>= 32;// 从 uid 中提取出页面编号(pgno),页面编号是 uid 的高32位int pgno = (int) (uid & ((1L << 32) - 1));// 使用页面缓存(pc)的 getPage(int pgno) 方法根据页面编号获取一个 Page 对象Page pg = pc.getPage(pgno);// 使用 DataItem 接口的静态方法 parseDataItem(Page pg, short offset, DataManagerImpl dm)// 根据获取到的 Page 对象、偏移量和当前的 DataManagerImpl 对象(this)解析出一个 DataItem 对象,并返回这个对象return DataItem.parseDataItem(pg, offset, this);
}
releaseForCache

DataItem 缓存释放,需要将 DataItem 写回数据源,由于对文件的读写是以页为单位进行的,只需要将 DataItem 所在的页 release 即可:

@Override
protected void releaseForCache(DataItem di) {di.page().release();
}
DataManager的核心方法
  1. 读取数据read()
  • 根据 Uid 从缓存中获取 DataItem,并校验其有效性。DataItemUid 是由页号和页内偏移组成的一个 8 字节无符号整数。

@Override
public DataItem read(long uid) throws Exception {//从缓存页面中读取到DataItemImplDataItemImpl di = (DataItemImpl) super.get(uid); // 若缓存中不存在则调用 getForCache() 方法//校验di是否有效if (!di.isValid()) {// 无效释放缓存di.release();return null;}return di;
}

2.插入数据insert()

  • PageIndex 中选择一个合适的页面进行插入操作,记录插入日志,并返回插入位置的偏移。插入的位置和页面信息都是通过页号和偏移量进行管理的。

@Override
public long insert(long xid, byte[] data) throws Exception {// 将输入的数据包装成DataItem的原始格式byte[] raw = DataItem.wrapDataItemRaw(data);// 如果数据项的大小超过了页面的最大空闲空间,抛出异常if (raw.length > PageX.MAX_FREE_SPACE) {throw Error.DataTooLargeException;}// 初始化一个页面信息对象PageInfo pi = null;// 尝试5次找到一个可以容纳新数据项的页面for (int i = 0; i < 5; i++) {// 从页面索引中选择一个可以容纳新数据项的页面pi = pIndex.select(raw.length);// 如果找到了合适的页面,跳出循环if (pi != null) {break;} else {// 如果没有找到合适的页面,创建一个新的页面,并将其添加到页面索引中int newPgno = pc.newPage(PageX.initRaw());pIndex.add(newPgno, PageX.MAX_FREE_SPACE);}}// 如果还是没有找到合适的页面,抛出异常if (pi == null) {throw Error.DatabaseBusyException;}// 初始化一个页面对象Page pg = null;// 初始化空闲空间大小为0int freeSpace = 0;try {// 获取页面信息对象中的页面pg = pc.getPage(pi.pgno);// 生成插入日志byte[] log = Recover.insertLog(xid, pg, raw);// 将日志写入日志文件logger.log(log);// 在页面中插入新的数据项,并获取其在页面中的偏移量short offset = PageX.insert(pg, raw);// 释放页面pg.release();// 返回新插入的数据项的唯一标识符return Types.addressToUid(pi.pgno, offset);} finally {// 将页面重新添加到页面索引中if (pg != null) {pIndex.add(pi.pgno, PageX.getFreeSpace(pg));} else {pIndex.add(pi.pgno, freeSpace);}}
}/***  返回一个完整的 DataItem 结构数据*  dataItem 结构如下:*  [ValidFlag] [DataSize] [Data]*  ValidFlag 1字节,0为合法,1为非法*  DataSize  2字节,标识Data的长度* @param raw* @return*/
public static byte[] wrapDataItemRaw(byte[] raw) {byte[] valid = new byte[1]; //证明此时为非法数据byte[] size = Parser.short2Byte((short)raw.length); //计算数据字节大小return Bytes.concat(valid, size, raw); //拼接DataItem 结构数据
}/*** 根据给定的空间大小选择一个 PageInfo 对象。** @param spaceSize 需要的空间大小* @return 一个 PageInfo 对象,其空闲空间大于或等于给定的空间大小。如果没有找到合适的 PageInfo,返回 null。*/
public PageInfo select(int spaceSize) {
lock.lock(); // 获取锁,确保线程安全
try {int number = spaceSize / THRESHOLD; // 计算需要的空间大小对应的区间编号// 此处+1主要为了向上取整/*1、假需要存储的字节大小为5168,此时计算出来的区间号是25,但是25*204=5100显然是不满足条件的2、此时向上取整找到 26,而26*204=5304,是满足插入条件的*/if (number < INTERVALS_NO) number++; // 如果计算出的区间编号小于总的区间数,编号加一while (number <= INTERVALS_NO) { // 从计算出的区间编号开始,向上寻找合适的 PageInfoif (lists[number].size() == 0) { // 如果当前区间没有 PageInfo,继续查找下一个区间number++;continue;}return lists[number].remove(0); // 如果当前区间有 PageInfo,返回第一个 PageInfo,并从列表中移除}return null; // 如果没有找到合适的 PageInfo,返回 null
} finally {lock.unlock(); // 释放锁
}
}// 定义一个静态方法,用于创建插入日志
public static byte[] insertLog(long xid, Page pg, byte[] raw) {// 创建一个表示日志类型的字节数组,并设置其值为LOG_TYPE_INSERTbyte[] logTypeRaw = {LOG_TYPE_INSERT};// 将事务ID转换为字节数组byte[] xidRaw = Parser.long2Byte(xid);// 将页面编号转换为字节数组byte[] pgnoRaw = Parser.int2Byte(pg.getPageNumber());// 获取页面的第一个空闲空间的偏移量,并将其转换为字节数组byte[] offsetRaw = Parser.short2Byte(PageX.getFSO(pg));// 将所有字节数组连接在一起,形成一个完整的插入日志,并返回这个日志return Bytes.concat(logTypeRaw, xidRaw, pgnoRaw, offsetRaw, raw);
}// 将raw插入pg中,返回插入位置
public static short insert(Page pg, byte[] raw) {pg.setDirty(true); // 将pg的dirty标志设置为true,表示pg的数据已经被修改short offset = getFSO(pg.getData()); // 获取pg的空闲空间偏移量System.arraycopy(raw, 0, pg.getData(), offset, raw.length); // 将raw的数据复制到pg的数据中的offset位置setFSO(pg.getData(), (short) (offset + raw.length)); // 更新pg的空闲空间偏移量return offset; // 返回插入位置
}

3.关闭 DataManagerclose()

  • 正常关闭时,执行缓存和日志的关闭流程,并设置第一页的字节校验。
@Override
public void close() {super.close();logger.close();PageOne.setVcClose(pageOne);pageOne.release();pc.close();
}

DataManager的初始化
  1. 从空文件创建create()
  • 初始化 PageCacheLogger,并初始化第一页。

public static DataManager create(String path, long mem, TransactionManager tm) {PageCache pc = PageCache.create(path, mem);Logger lg = Logger.create(path);DataManagerImpl dm = new DataManagerImpl(pc, lg, tm);dm.initPageOne();return dm;
}

  1. 从已有文件打开open()
  • 加载并检查第一页,必要时执行恢复操作,并填充 PageIndex

public static DataManager open(String path, long mem, TransactionManager tm) {PageCache pc = PageCache.open(path, mem);Logger lg = Logger.open(path);DataManagerImpl dm = new DataManagerImpl(pc, lg, tm);if (!dm.loadCheckPageOne()) {Recover.recover(tm, lg, pc);}dm.fillPageIndex();PageOne.setVcOpen(dm.pageOne);dm.pc.flushPage(dm.pageOne);return dm;
}

总结

DataItemDataManager 是数据库系统中数据管理的关键组件。DataItem 提供了数据的存储和访问接口,支持数据修改和事务管理。而 DataManager 负责管理底层数据的访问、缓存、事务处理和日志记录,通过这些功能的实现,为上层模块提供了安全、高效的数据操作接口。两者的协作使得数据库系统能够以高效、可靠的方式管理和操作底层数据,确保数据的安全性和一致性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/436663.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Web认识 -- 第一课

文章目录 前言一、HTML是什么&#xff1f;二、了解Web1. 基本概念2.Web标准3. Web构成1.前端1. HTML2.CSS3. javaScript4.常见浏览器介绍 2.Web标签构成1.结构标准2.表现标准 -- css3. 行为标准 -- javaScript 总结 前言 这里是我们进入前端学习的开端,在本次更新之后我会陆续…

【智能算法应用】正余弦优化算法求解二维路径规划问题

摘要 正余弦优化算法&#xff08;Sine Cosine Algorithm, SCA&#xff09;是一种新颖的群体智能优化算法&#xff0c;能够有效地求解复杂的非线性问题。在本研究中&#xff0c;我们将SCA应用于二维路径规划问题&#xff0c;以找到从起点到终点的最优路径&#xff0c;同时避开障…

Python从入门到高手4.1节-掌握条件控制语句

目录 4.1.1 理解条件控制 4.1.2 if, elif, else 4.1.3 条件表达式 4.1.4 条件控制可以嵌套 4.1.5 if语句的三元运算 4.1.6 国庆节快乐 4.1.1 理解条件控制 在日常生活中&#xff0c;我们常喜欢说如果, "如果怎么样&#xff0c;那么就会怎么样"。"如果&qu…

【JVM】双亲委派模型

文章目录 双亲委派模型&#xff08;面试高频&#xff09;类加载器工作过程存在意义 双亲委派模型&#xff08;面试高频&#xff09; 之所以这个东西面试出场概率高高&#xff0c;最大的原因就是它起的名字很好听 描述了查找 .class 文件的策略。 类加载器 JVM 中进行类加载的…

使用容器启动的zk无法暴露3888问题解决

1. 问题描述 zk配置如下&#xff1a; 我通过容器启动了一个zk&#xff0c;通过-p 参数暴露了2181和3888端口&#xff0c;容器启动脚本如下&#xff1a; #!/bin/shdocker rm -f myzookeeper1docker run -p 12181:2181 -p 13888:3888 --name myzookeeper1 --restart always …

汽车零部件开发流程关键阶段

目录 1、定点阶段 1.1、定点前的准备工作 1.2、定点决策过程 1.3、定点后的工作交接 2、A样阶段&#xff1a;设计验证与基本功能实现 2.1、样件制作&#xff1a;从设计图纸到实物转化 2.2、功能测试&#xff1a;初步验证与性能评估 2.3、评估与优化&#xff1a;A样阶段…

【羊毛资源】华为云开发者云主机免费申请使用指南

本文内容均来自个人笔记并重新梳理&#xff0c;如有错误欢迎指正&#xff01; 如果对您有帮助&#xff0c;烦请点赞、关注、转发、订阅专栏&#xff01; 专栏订阅入口 | 精选文章 | Kubernetes | Docker | Linux | 羊毛资源 | 工具推荐 | 往期精彩文章 【Docker】&#xff08;全…

Vue-Lecture1-Notes

渐进式框架 Vue 被称为“渐进式框架”&#xff0c;是因为它允许开发者根据项目的需求逐步引入和使用其功能&#xff0c;而不需要一次性使用整个框架。简单来说&#xff0c;Vue 提供了从简单到复杂的功能层次&#xff0c;可以灵活选择使用。 按需使用&#xff1a;Vue 的核心功能…

一次实践:给自己的手机摄像头进行相机标定

文章目录 1. 问题引入2. 准备工作2.1 标定场2.2 相机拍摄 3. 基本原理3.1 成像原理3.2 畸变校正 4. 标定解算4.1 代码实现4.2 详细解析4.2.1 解算实现4.2.2 提取点位 4.3 解算结果 5. 问题补充 1. 问题引入 不得不说&#xff0c;现在的计算机视觉技术已经发展到足够成熟的阶段…

简单vue指令实现 el-table 可拖拽表格功能

安装 SortableJS sorttableJs 相关优点如下&#xff1a; 相关配置项 参考 &#x1f449; SortableJS中文官网 pnpm i sortablejs封装成指令 不多逼逼&#xff0c;直接上才艺 &#x1f92a;&#x1f92a;&#x1f92a; 先安装一个 nanoid 插件 用于生成随机id&#xff0c;注…

【STM32单片机_(HAL库)】4-3-4【定时器TIM】测量按键按下时间实现3

1.硬件 STM32单片机最小系统按键模块 2.软件 定时器HAL驱动层文件添加ic驱动文件添加GPIO常用函数定时器输入捕获实验配置步骤main.c程序 #include "sys.h" #include "delay.h" #include "led.h" #include "uart1.h" #include &qu…

游戏修改器Cheat Engine CE v7.5修改版下载安装详细方法

Cheat Engine是一个专注于游戏的修改器。它可以用来扫描游戏中的内存&#xff0c;并允许修改它们。它还附带了调试器、反汇编器、汇编器、变速器、作弊器生成、Direct3D操作工具、系统检查工具等。 具体安装方法如下&#xff1a; 地址&#xff1a;Cheat Engine 7.5.zip 解压文件…

C++之String类(下)

片头 嗨喽~ 我们又见面啦&#xff0c;在上一篇C之String类&#xff08;上&#xff09;中&#xff0c;我们对string类的函数有了一个初步的认识&#xff0c;这一篇中&#xff0c;我们将继续学习string类的相关知识。准备好了吗&#xff1f;咱们开始咯~ 二、标准库中的string类 …

【MYSQL】授权远程连接的用户

文章目录 一、Navicat新建查询1.点击新建查询&#xff0c;授权远程连接的用户语句&#xff1a;2.刷新权限 二、Navicat用户 一、Navicat新建查询 1.点击新建查询&#xff0c;授权远程连接的用户语句&#xff1a; GRANT ALL PRIVILEGES ON *.* TO 库的用户名授权IP IDENTIFIED …

php email功能实现:详细步骤与配置技巧?

php email发送功能详细教程&#xff1f;如何使用php email服务&#xff1f; 无论是用户注册、密码重置&#xff0c;还是订单确认&#xff0c;电子邮件都是与用户沟通的重要手段。AokSend将详细介绍如何实现php email功能&#xff0c;并提供一些配置技巧&#xff0c;帮助你更好…

【pytorch】pytorch入门5:最大池化层(Pooling layers )

文章目录 前言一、定义概念 缩写二、参数三、最大池化操作四、使用步骤总结参考文献 前言 使用 B站小土堆课程 一、定义概念 缩写 池化&#xff08;Pooling&#xff09;是深度学习中常用的一种操作&#xff0c;用于降低卷积神经网络&#xff08;CNN&#xff09;或循环神经网…

《软件工程概论》作业一:新冠疫情下软件产品设计(小区电梯实体按钮的软件替代方案)

课程说明&#xff1a;《软件工程概论》为浙江科技学院2018级软件工程专业在大二下学期开设的必修课。课程使用《软件工程导论&#xff08;第6版&#xff09;》&#xff08;张海藩等编著&#xff0c;清华大学出版社&#xff09;作为教材。以《软件设计文档国家标准GBT8567-2006》…

本地生活服务项目有哪些:如何利用本地生活市场,打开线下流量!

随着各大互联网公司在本地生活服务板块的布局力度持续加大&#xff0c;越来越多的人都开始意识到了它背后所蕴含着的发展前景和收益潜力&#xff0c;进而纷纷打听起了与之相关的消息。而就小编与多位创业者的交流情况而言&#xff0c;在众多问题中&#xff0c;属本地生活服务项…

springboot中有哪些方式可以解决跨域问题

文章目录 什么是跨域解决方案CrossOrigin注解实现WebMvcConfigurer接口CorsFilter过滤器如何选择&#xff1f; 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 Talk is cheap &#xff0…

算法专题二: 滑动窗口

目录 1. 长度最小的子数组2. 无重复字符的最长子串3. 最大连续1的格数Ⅲ4. 将x减到0的最小操作数5. 水果成篮6. 找到字符串中所有字母异位词7. 串联所有单词的子串8. 最小覆盖子串 1. 长度最小的子数组 题目思路: 首先暴力解法就是依次枚举出所有的子数组, 从第一个元素为左端…