CMU 15-445 Project #2 - B+Tree(CHECKPOINT #1)

news2024/11/23 13:25:07

CHECKPOINT #1

  • 一、题目链接
  • 二、准备工作
  • 三、部分实现
    • 1.查找操作
    • 2.插入操作
    • 3.删除操作
  • 四、评测结果

在这里插入图片描述

一、题目链接


二、准备工作

见 CMU 15-445 Project #0 - C++ Primer 中的准备工作。


三、部分实现

对于B+树的节点定义,通过节点类的命名 b_plus_tree_page 不难发现,每一个节点本质上都是从缓冲池中通过 Fetch 操作获得的一个页面(准确来说是缓冲池页面的数据部分,这个数据部分通过 reinterpret_cast 强制转换后就是B+树节点的全部内容),因此B+树节点中的 page_id 与缓冲池和磁盘上的页面的 page_id 是一致的。

对于分支节点(即内部节点),它保存的是 (BUSTUB_PAGE_SIZE - LEAF_PAGE_HEADER_SIZE) / sizeof(MappingType)GenericKeypage_id_t 组成的键值对。而对于叶子结点,它保存的则是 (BUSTUB_PAGE_SIZE - LEAF_PAGE_HEADER_SIZE) / sizeof(MappingType)GenericKeyRID 组成的键值对。源码中对于 RID 的定义是一个记录标识符,可见叶子结点中保存的不是实际数据,而是一个键值,因此这里实现的B+树索引是一个非聚簇索引

1.查找操作

INDEX_TEMPLATE_ARGUMENTS
auto BPLUSTREE_TYPE::GetValue(const KeyType &key, std::vector<ValueType> *result, Transaction *transaction) -> bool {
    /* 获取可能包含key的叶子页面 */
    LeafPage *target_leaf_page = FindLeafPage(key);

    /* 查找key是否存在 */
    for (int i = 0; i < target_leaf_page->GetSize(); i++) {
        if (comparator_(target_leaf_page->KeyAt(i), key) == 0) {
            result->emplace_back(target_leaf_page->ValueAt(i));
            return true;
        }
    }

    /* 查找失败 */
    return false;
}

INDEX_TEMPLATE_ARGUMENTS
auto BPLUSTREE_TYPE::FindLeafPage(const KeyType &key) -> LeafPage * {
    page_id_t cur_page_id = root_page_id_;  // 标识页面遍历

    while (cur_page_id != INVALID_PAGE_ID) {
        /* 从缓冲池中获取cur_page_id对应的页面,并将该页面的数据部分强制转换为一个B+树页面。 */
        auto cur_page = reinterpret_cast<BPlusTreePage *>(buffer_pool_manager_->FetchPage(cur_page_id)->GetData());

        /* 如果当前B+树页面为叶子页面表明查找成功 */
        if (cur_page->IsLeafPage()) {
            return static_cast<LeafPage *>(cur_page);
        }

        /* 将当前B+树页面转换为一个分支页面 */
        auto internal_page = static_cast<InternalPage *>(cur_page);

        /* 查找下一层待处理的B+树页面,本质是查找最右侧的小于等于key的键值对。 */
        cur_page_id = internal_page->ValueAt(0);
        for (int i = 1; i < cur_page->GetSize() && comparator_(internal_page->KeyAt(i), key) <= 0; i++) {
            cur_page_id = internal_page->ValueAt(i);
        }

        /* 取消对缓冲池页面的引用 */
        buffer_pool_manager_->UnpinPage(cur_page_id, false);
    }

    return nullptr;
}

2.插入操作

如果将向B+树页面的插入操作全部交给页面(包括叶子页面和内部页面)自己来处理,那么就需要将缓冲池指针等其他变量也传给B+树页面,这无疑会增加逻辑上的复杂性,很有可能出现B+树页面对象和B+树对象重复执行 UnpinPage 操作等问题。而如果全部交给B+树来处理,对于页面的私有数据成员 array_ 的访问处理又会非常复杂。因此这里叶子页面和内部页面只负责单纯的数据插入和移动,而溢出处理(页面分裂)、根节点更新、递归向上插入等操作都交给B+树来实现,其中具体的内部页面的递归向上插入由 InsertToInternalPage 实现。

插入函数

INDEX_TEMPLATE_ARGUMENTS
auto BPLUSTREE_TYPE::Insert(const KeyType &key, const ValueType &value, Transaction *transaction) -> bool {
    /* 如果当前B+树为一颗空树则需要创建一个叶子页面作为B+树的根页面,
     * 否则直接向可能的叶子节点进行插入。 */
    if (IsEmpty()) {
        /* 从缓冲池中获取页面并转换为叶子页面,这个叶子页面即新的根页面。 */
        Page *new_buffer_page = buffer_pool_manager_->NewPage(&root_page_id_);
        auto new_root_page = reinterpret_cast<LeafPage *>(new_buffer_page->GetData());

        /* 初始化根页面 */
        new_root_page->Init(root_page_id_, INVALID_PAGE_ID, leaf_max_size_);
        new_root_page->Insert(key, value, comparator_);
        new_root_page->SetNextPageId(INVALID_PAGE_ID);

        /* 取消缓冲区页面的固定 */
        buffer_pool_manager_->UnpinPage(root_page_id_, true);

        /* 更新header_page中的根页面信息 */
        UpdateRootPageId(true);

        return true;
    }

    /* 获取key应该被插入的叶子页面 */
    LeafPage *target_leaf_page = FindLeafPage(key);

    /* 如果叶子页面插入失败表明发生了键值重复,取消页面固定并返回false。 */
    if (!target_leaf_page->Insert(key, value, comparator_)) {
        buffer_pool_manager_->UnpinPage(target_leaf_page->GetPageId(), false);
        return false;
    }

    /* 如果叶子页面插入后仍未满,无需进行分裂操作,直接返回即可,否则需要进行分裂操作。 */
    if (target_leaf_page->GetSize() < target_leaf_page->GetMaxSize()) {
        return true;
    }

    /* 如果当前页面是根页面,需要新建一个根页面和分裂页面并更新相关信息;
     * 否则需新建一个分裂页面并进行相关信息更新,同时递归更新祖先页面的相关信息。 */
    if (target_leaf_page->IsRootPage()) {
        Page *new_buffer_page;

        /* 从缓冲池中获取页面并转换为叶子页面,这个叶子页面即新的分裂页面。 */
        page_id_t split_page_id;
        new_buffer_page = buffer_pool_manager_->NewPage(&split_page_id);
        auto split_leaf_page = reinterpret_cast<LeafPage *>(new_buffer_page->GetData());

        /* 从缓冲池中获取页面并转换为内部页面,这个内部页面即新的根页面。 */
        new_buffer_page = buffer_pool_manager_->NewPage(&root_page_id_);
        auto new_root_page = reinterpret_cast<InternalPage *>(new_buffer_page->GetData());

        /* 初始化分裂页面 */
        split_leaf_page->Init(split_page_id, root_page_id_, leaf_max_size_);
        split_leaf_page->SetNextPageId(target_leaf_page->GetNextPageId());
        target_leaf_page->MoveHalfDataTo(split_leaf_page);
        target_leaf_page->SetNextPageId(split_page_id);

        /* 初始化根页面 */
        new_root_page->Init(root_page_id_, INVALID_PAGE_ID, internal_max_size_);
        new_root_page->SetValueAt(0, target_leaf_page->GetPageId());
        new_root_page->Insert(split_leaf_page->KeyAt(0), split_page_id, comparator_);
        target_leaf_page->SetParentPageId(root_page_id_);

        /* 取消缓冲区页面的固定 */
        buffer_pool_manager_->UnpinPage(target_leaf_page->GetPageId(), true);
        buffer_pool_manager_->UnpinPage(split_page_id, true);
        buffer_pool_manager_->UnpinPage(root_page_id_, true);

        /* 更新header_page中的根页面信息 */
        UpdateRootPageId();
    } else {
        /* 从缓冲池中获取页面并转换为叶子页面,这个叶子页面即新的分裂页面。 */
        page_id_t split_page_id;
        Page *new_buffer_page = buffer_pool_manager_->NewPage(&split_page_id);
        auto split_leaf_page = reinterpret_cast<LeafPage *>(new_buffer_page->GetData());

        /* 初始化分裂页面 */
        split_leaf_page->Init(split_page_id, target_leaf_page->GetParentPageId(), leaf_max_size_);
        split_leaf_page->SetNextPageId(target_leaf_page->GetNextPageId());
        target_leaf_page->MoveHalfDataTo(split_leaf_page);
        target_leaf_page->SetNextPageId(split_page_id);

        /* 将分裂处的键值插入到父页面中 */
        KeyType insert_key = split_leaf_page->KeyAt(0);
        InsertToParentPage(target_leaf_page, split_leaf_page, insert_key);

        /* 取消页面固定 */
        buffer_pool_manager_->UnpinPage(target_leaf_page->GetPageId(), true);
        buffer_pool_manager_->UnpinPage(split_page_id, true);
    }

    return true;
}

INDEX_TEMPLATE_ARGUMENTS
void BPLUSTREE_TYPE::InsertToParentPage(BPlusTreePage *old_child_page, BPlusTreePage *new_child_page, const KeyType &insert_key) {
    /* 如果旧页面是根页面,需要新建一个根页面并更新相关信息;
     * 否则向父页面插入,同时插入前需要判断父页面是否溢出。 */
    if (old_child_page->IsRootPage()) {
        /* 从缓冲池中获取页面并转换为内部页面,这个内部页面即新的根页面。 */
        Page *new_buffer_page = buffer_pool_manager_->NewPage(&root_page_id_);
        auto new_root_page = reinterpret_cast<InternalPage *>(new_buffer_page->GetData());

        /* 初始化根页面 */
        new_root_page->Init(root_page_id_, INVALID_PAGE_ID, internal_max_size_);
        new_root_page->SetValueAt(0, old_child_page->GetPageId());
        new_root_page->SetKeyAt(1, insert_key);
        new_root_page->SetValueAt(1, new_child_page->GetPageId());
        new_root_page->IncreaseSize(1);
        old_child_page->SetParentPageId(root_page_id_);
        new_child_page->SetParentPageId(root_page_id_);

        /* 取消缓冲区页面的固定 */
        buffer_pool_manager_->UnpinPage(root_page_id_, true);

        /* 更新header_page中的根页面信息 */
        UpdateRootPageId();

        return;
    }

    /* 从缓冲池获取父页面 */
    Page *parent_buffer_page = buffer_pool_manager_->FetchPage(old_child_page->GetParentPageId());
    auto parent_page = reinterpret_cast<InternalPage *>(parent_buffer_page->GetData());

    /* 如果父页面插入后仍未满,无需进行分裂操作,直接返回即可,否则需要进行分裂操作。 */
    if (parent_page->GetSize() < parent_page->GetMaxSize()) {
        parent_page->Insert(insert_key, new_child_page->GetPageId(), comparator_);
        return;
    }

    /* 从缓冲池中获取页面并转换为内部页面,这个内部页面即新的分裂页面。 */
    page_id_t split_page_id;
    Page *new_buffer_page = buffer_pool_manager_->NewPage(&split_page_id);
    auto split_internal_page = reinterpret_cast<InternalPage *>(new_buffer_page->GetData());

    /* 初始化分裂页面 */
    split_internal_page->Init(split_page_id, parent_page->GetParentPageId(), internal_max_size_);

    /* key和parent_page中的全部数据分裂到parent_page和split_internal_page中 */
    parent_page->InsertAndMoveHalfDataTo(split_internal_page, insert_key, new_child_page->GetPageId(), comparator_, buffer_pool_manager_);

    /* 将分裂处的键值插入到父页面的父页面中 */
    KeyType next_insert_key = split_internal_page->KeyAt(0);
    InsertToParentPage(parent_page, split_internal_page, next_insert_key);

    /* 取消缓冲区页面的固定 */
    buffer_pool_manager_->UnpinPage(parent_page->GetPageId(), true);
    buffer_pool_manager_->UnpinPage(split_page_id, true);
}

叶子节点的插入和数据移动操作

INDEX_TEMPLATE_ARGUMENTS
auto B_PLUS_TREE_LEAF_PAGE_TYPE::Insert(const KeyType &key, const ValueType &value, const KeyComparator &comparator) -> bool {
    assert(GetSize() < GetMaxSize());

    int insert_pos = 0;  // 用于记录插入位置

    /* 查找插入位置 */
    while (insert_pos < GetSize() && comparator(array_[insert_pos].first, key) < 0) {
        insert_pos++;

        /* 键值不能重复 */
        if (comparator(array_[insert_pos].first, key) == 0) {
            return false;
        }
    }

    /* 增加键值对数量 */
    IncreaseSize(1);

    /* 插入位置后面的元素后移 */
    for (int i = GetSize() - 1; i > insert_pos; i--) {
        array_[i] = array_[i - 1];
    }

    /* 在插入位置插入 */
    array_[insert_pos].first = key;
    array_[insert_pos].second = value;

    return true;
}

INDEX_TEMPLATE_ARGUMENTS
void B_PLUS_TREE_LEAF_PAGE_TYPE::MoveHalfDataTo(B_PLUS_TREE_LEAF_PAGE_TYPE *des_page) {
    for (int i = 0; i < (GetMaxSize() + 1) / 2; i++) {
        des_page->array_[i] = array_[GetMaxSize() / 2 + i];
    }

    des_page->SetSize((GetMaxSize() + 1) / 2);
    SetSize(GetMaxSize() / 2);
}

内部节点的插入和数据移动操作

INDEX_TEMPLATE_ARGUMENTS
void B_PLUS_TREE_INTERNAL_PAGE_TYPE::Insert(const KeyType &key, const ValueType &value, const KeyComparator &comparator) {
    assert(GetSize() < GetMaxSize());

    int insert_pos = 1;  // 用于记录插入位置

    /* 查找插入位置 */
    while (insert_pos < GetSize() && comparator(array_[insert_pos].first, key) < 0) {
        insert_pos++;
    }

    /* 增加键值对数量 */
    IncreaseSize(1);

    /* 插入位置后面的元素后移 */
    for (int i = GetSize() - 1; i > insert_pos; i--) {
        array_[i] = array_[i - 1];
    }

    /* 在插入位置插入 */
    array_[insert_pos].first = key;
    array_[insert_pos].second = value;
}

INDEX_TEMPLATE_ARGUMENTS
void B_PLUS_TREE_INTERNAL_PAGE_TYPE::InsertAndMoveHalfDataTo(B_PLUS_TREE_INTERNAL_PAGE_TYPE *des_page, const KeyType &key, const ValueType &value, const KeyComparator &comparator,  BufferPoolManager *buffer_pool_manager) {
    /* 将源页面中的数据和待插入的数据整合在一个vector中 */
    std::vector<MappingType > tmp_array(GetMaxSize() + 1);
    int i = 0;
    int j = 0;
    while (i < GetMaxSize()) {
        if (comparator(array_[i].first, key) < 0) {
            tmp_array.at(i) = array_[i];
            i++;
        } else {
            break;
        }
    }
    tmp_array.at(i++) = std::make_pair(key, value);
    while (i < GetMaxSize() + 1) {
        tmp_array.at(i) = array_[i - 1];
        i++;
    }

    /* 将整合好的数据对半分配到两个子页面中,其中目标页面的首个key值用作哨兵,以保存要向两个页面的父页面中插入的key值。 */
    for (i = 0; i < (GetMaxSize() + 1) / 2; i++) {
        array_[i] = tmp_array.at(i);
    }
    for (i = (GetMaxSize() + 1) / 2; i < GetMaxSize() + 1; i++, j++) {
        des_page->array_[j] = tmp_array.at(i);
        auto child_page =
                reinterpret_cast<BPlusTreePage *>(buffer_pool_manager->FetchPage(des_page->ValueAt(j))->GetData());
        child_page->SetParentPageId(des_page->GetPageId());
        buffer_pool_manager->UnpinPage(child_page->GetPageId(), true);
    }

    des_page->SetSize((GetMaxSize() + 1) / 2);
    SetSize((GetMaxSize() + 1) / 2);
}

3.删除操作

还在做

四、评测结果

在这里插入图片描述


参考:

https://xiaolincoding.com/mysql/index/page.html
https://blog.csdn.net/Altair_alpha/article/details/129071063

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

linux-centos7操作系统查看系统未挂载的磁盘,挂载磁盘

linux-centos7操作系统查看系统未挂载的磁盘,挂载磁盘 查看当前磁盘空间 根目录 / 下也只有44G,其他目录只有10几G,正式环境肯定不够用 df -h查看硬盘数量和分区情况 fdisk -l查看到/dev/vdb 有500多G了 将/dev/vdb在分出一个区使用 第一步:编辑分区。执行命令fdisk …

pr视频叠加,即原视频右上角添加另外一个视频方法,以及pr导出视频步骤

一、pr视频叠加&#xff0c;即原视频右上角添加另外一个视频方法 在使用pr制作视频时&#xff0c;我们希望在原视频的左上角或右上角同步播放另外一个视频&#xff0c;如下图所示&#xff1a; 具体方法为&#xff1a; 1、导入原视频&#xff0c;第一个放在v1位置&#xff0c;第…

Selenium编写自动化用例的8种技巧

在开始自动化时&#xff0c;您可能会遇到各种可能包含在自动化代码中的方法&#xff0c;技术&#xff0c;框架和工具。有时&#xff0c;与提供更好的灵活性或解决问题的更好方法相比&#xff0c;这种多功能性导致代码更加复杂。在编写自动化代码时&#xff0c;重要的是我们能够…

【序列dp】最长上升子序列(一)

文章目录 最长上升子序列-序列dp概览895 最长上升子序列-O(n^2)1017 怪盗基德的滑翔翼1014 登山482 合唱队形1012 友好城市 最长上升子序列-序列dp 什么是序列相关的 DP &#xff1f;序列相关 DP&#xff0c;顾名思义&#xff0c;就是将动态规划算法用于数组或者字符串上&…

textgen教程(持续更新ing...)

诸神缄默不语-个人CSDN博文目录 官方GitHub项目&#xff1a;shibing624/textgen: TextGen: Implementation of Text Generation models, include LLaMA, BLOOM, GPT2, BART, T5, SongNet and so on. 文本生成模型&#xff0c;实现了包括LLaMA&#xff0c;ChatGLM&#xff0c;B…

C++课程学习记录

目录 1. 前置说明2. 二叉树的模拟2.1 参考资料2.2 二叉树的构建2.2.1 递归构建2.2.2 迭代构建 2.3 二叉树的遍历2.4 二叉树的应用 3. 继承与派生3.1 最简单的生死3.2 动态申请空间的生死3.3 继承中的protectd权限3.4 三种继承方式3.5 修改某些继承成员的继承类型3.6 多级派生3.…

C++57个入门知识点_番外1_C++指针偏移在类中的应用及指针偏移原理

这是对C指针偏移介绍比较好的博文&#xff0c;但是比较分散&#xff0c;我把其进行了整理&#xff0c;原博文地址请见最后&#xff0c;讲的很详细。 C57个入门知识点_番外1_C指针偏移在类中的应用及指针偏移原理 1. C指针偏移原理2. C显示十进制内存地址&#xff08;不用理解&…

AQS原理

目录 一、原理概述二、AQS 对资源的共享方式三、AQS底层使用了模板方法模式四、使用demo&#xff0c;使用AQS实现不可重入锁五、AQS使用到的几个框架 一、原理概述 AQS全称是 AbstractQueuedSynchronizer&#xff0c;是阻塞式锁和相关的同步器工具的框架 AQS核心思想是&#…

Appian低代码平台

国外老牌低代码开发平台Appian Appian在国内用的比较少&#xff0c;资料也很匮乏。需要自己主动去官网寻找。 Appian 学习平台 进入Appian Community可以选择学习路径&#xff0c;可以选择适合自己的学习路径&#xff1b;我选择的是Builder路径&#xff0c; 看了足足80个小…

opencv检测二维码和条形码

文章目录 1 excel制作简单二维码2 识别二维码和条形码2.1 相关库2.2 decode解码2.3 圈出二维码的位置2.4 判断二维码是否授权 3 完整代码3.1 使用图片进行识别3.2 使用摄像头实时识别 4 总结 1 excel制作简单二维码 使用excel可以实现制作二维码&#xff0c;但只能实现做英文和…

基于51单片机的简易电子琴设计

目录 摘 要 基于51单片机的简易电子琴设计 一、系统设计 1、项目概要 2.设计任务和基本要求 二、硬件设计 1、硬件设计概要 2、时钟振荡电路模块 3.复位电路模块 5.数码管电路模块 6.蜂鸣器模块 7、乐曲切换电路模块 三、软件原理 四、软件流程图 五、代码实现 …

解析Transformer基本结构与实现

1.基本结构 ​ Transformer总体架构可分为4个部分&#xff1a; 输入部分-输出部分-编码器部分-解码器部分 输入部分包含&#xff1a; 原文本嵌入层&#xff08;Input embedding&#xff09;及其位置编码(position encoding)目标文本嵌入层及其位置编码器 文本嵌入层的作…

TCP/UDP协议重温三次握手四次挥手 简单笔记

术语储备&#xff1a; SYN&#xff1a;同步位 &#xff1b;SYN1,表示进行一个连接请求 ACK&#xff1a;确认位 &#xff1b;ACK1,确认有效 ACK0&#xff0c;确认无效 ack : 确认号 &#xff1b;对方发送序号1 seq &#xff1a; 序号 ; 标识从TCP发端向TCP收端发送的数据字节流 …

基于JPA的Repository使用详解

Spring Data JPA Spring Data是Spring提供的操作数据的框架&#xff0c;Spring Data JPA是Spring Data的一个模块&#xff0c;通过Spring data 基于jpa标准操作数据的模块。 Spring Data的核心能力&#xff0c;就是基于JPA操作数据&#xff0c;并且可以简化操作持久层的代码。…

BLOND:ISH VoxEdit 创作大赛来啦!

准备好随着 BLOND:ISH 的节拍释放你们的创造力和节奏&#xff0c;因为我们将举办一场与众不同的刺激比赛。你们可以在 BLOND:ISH VoxEdit 大赛中展示你们的才华并赢得 SAND 奖励&#xff01; &#x1f3dd;️ 比赛主题&#xff1a;ABRA 夏日派对 &#x1f3dd;️ 释放你们的想象…

【Python学习】—Python基础语法(二)

文章目录 【Python学习】—Python基础语法&#xff08;二&#xff09;一、字面量二、注释三、变量四、数据类型五、数据类型转换六、标识符七、运算符八、字符串扩展九、获取键盘输入 【Python学习】—Python基础语法&#xff08;二&#xff09; 一、字面量 字面量&#xff1…

文件类型识别的实现思路

一些网络设备&#xff0c;比如防火墙或者审计系统&#xff0c;一般都有文件过滤的功能&#xff0c;可以对用户上网传输的文件进行过滤&#xff0c;比如可以限制用户通过ftp下载word文档&#xff0c;也就是文件类型为doc或者docx的文件。 那么文件过滤的功能是怎么实现呢&#…

2023年计算机专业还值得无脑梭哈吗——计算机类专业在读一年学生个人分享

长忆观潮&#xff0c;满郭人争江上望。来疑沧海尽成空&#xff0c;万面鼓声中。 弄潮儿向涛头立&#xff0c;手把红旗旗不湿。别来几向梦中看&#xff0c;梦觉尚心寒。 IT 界每隔几年就有一波浪潮或者泡沫&#xff0c;新的一波大潮已经打过来了&#xff0c;躲&#xff1f;能跑多…

【计算机视觉】CVPR 23 新论文 | 异常检测最新改进方法:DeSTSeg

文章目录 一、导读二、背景2.1 主要贡献2.2 网络介绍&#xff1a;DeSTSeg 三、方法3.1 Synthetic Anomaly Generation 合成异常生成3.2 Denoising Student-Teacher Network 去噪教师学生网络3.3 Segmentation Network 分割网络 四、实验结果 一、导读 DeSTSeg: Segmentation G…

数字图像处理【10】直方图反向投影与模板匹配

本篇简单描述直方图在图像处理中典型的应用场景&#xff0c;属于是比较老旧的应用技术&#xff0c;但不妨拿出来再学习&#xff0c;温故而知新&#xff0c;为新知识做一个铺垫。 直方图反向投影&#xff08;Back Projection&#xff09; 还记得之前学习过的图像直方图的计算/…