当前位置: 首页 > news >正文

P2 B+树索引

文章目录

    • Task1 B+树页
      • B+树页
      • B+树内部结点
      • B+树叶子结点
    • Task2 B+树操作
      • Task2 B+树插入和搜索的单一值
        • 插入单一值
        • 搜索单一值
      • Task2 B+树删除
    • Task3 叶子扫描的迭代器
    • Task4 并行索引

Task1 B+树页

B+树页

实际上是每个B+树页面的标题部分,包含叶子页面和内部页面共享的信息。

报头格式(大小以字节为单位,共12字节):
| PageType (4) | CurrentSize (4) | MaxSize (4) |// define page type enum
enum class IndexPageType { INVALID_INDEX_PAGE = 0, LEAF_PAGE, INTERNAL_PAGE };
// 成员变量,内部页和叶页共享的属性
IndexPageType page_type_ __attribute__((__unused__));
int size_ __attribute__((__unused__));      // 键值对的数量,内部页的第一个键为无效值
int max_size_ __attribute__((__unused__));  // 键值对的容量

B+树内部结点

在内部页面中存储n个索引键和n+1个子指针(page_id)。

指针PAGE_ID(i)指向一个子树,其中所有键K满足:K(i) <= K < K(i+1)。

注意:由于键的数量不等于子指针的数量,所以第一个键总是无效的。也就是说,任何搜索/查找都应该忽略第一个键。

内部页面格式(键按递增顺序存储):
| HEADER | KEY(1)+PAGE_ID(1) | KEY(2)+PAGE_ID(2) | ... | KEY(n)+PAGE_ID(n) |#define MappingType std::pair<KeyType, ValueType>
//key -> page_id 的映射
MappingType array_[0];

B+树叶子结点

只支持唯一键。

叶页格式(键按顺序存储):
| HEADER | KEY(1) + RID(1) | KEY(2) + RID(2) | ... | KEY(n) + RID(n)HEADER格式(大小为字节,共16字节):
| PageType (4) | CurrentSize (4) | MaxSize (4) |  NextPageId (4)page_id_t next_page_id_;  // 先定位到一个叶子,再顺序向后扫描
//key->record_id 的映射,record id = page id combined with slot id
MappingType array_[0];

Task2 B+树操作

大部分操作在b_plus_tree.cpp实现

Context类用于记录操作B+树过程中需要记忆的路径、读写保护等

class Context {public:// 当您插入/删除B+树时,将头页的写保护存储在这里。// 当您想要解锁所有内容时,请记住取消标题页保护并将其设置为空值,即header_page.reset()std::optional<WritePageGuard> header_page_{std::nullopt};//在这里保存根页面id,以便更容易地知道当前页面是否是根页面。page_id_t root_page_id_{INVALID_PAGE_ID};// 要修改的页面的写保护的队列。std::deque<WritePageGuard> write_set_;// 读保护队列std::deque<ReadPageGuard> read_set_;auto IsRootPage(page_id_t page_id) -> bool { return page_id == root_page_id_; }
};

Task2 B+树插入和搜索的单一值

插入单一值

BPLUSTREE_TYPE::Insert()实现,若BPLUSTREE_TYPE::header_page_id_属性标识的根节点的页(BPlusTreeHeaderPage)的根节点页id无效,说明没有根页面,调用缓冲区的NewPage()新建root页,然后强转为BPlusTreeLeafPage类型,此时根节点就是叶子节点,然后BPlusTreeLeafPage::InsertAtLast()根叶后插k-v,标识正在使用该页,清空header_page_(检查header_page_id_标识的根节点页时,页就存在这里)。若有根节点就获取根节点页id,调用InsertIntoLeaf()

BPLUSTREE_TYPE::InsertIntoLeaf()开头就调用了BPLUSTREE_TYPE::FindLeaf(),此函数的作用是为插入/删除结点操作寻找目标叶,结果存在B+树类的Context类成员的write_set_中。回到函数,取出查找到的页,强转为BPlusTreeLeafPage后直接调用BPlusTreeLeafPage::Insert()插入k-v,若插入失败说明是重复键值对,清空header_page_write_set_后退出,并且若叶页大小没超过叶子最大容量则此插入是安全插入,清空header_page_write_set_后退出,剩下的情况就是叶页满了,需要分裂,调用Split()进行分裂,返回的新页引用是满页分裂出的兄弟叶页,之后调用InsertIntoParent()修改父结点结构。

BPLUSTREE_TYPE::InsertIntoParent()是个直接递归,在函数开头有个判断write_set_内的锁有一个时,这些锁是在InsertIntoLeaf()中调用FindLeaf()时产生的,表示修改该叶子会导致write_set_中的这些结点页被修改,即最接近叶子的安全结点到叶子节点这一路径上的所有结点的锁。在write_set_中只剩一个页时,NewPage()新建一个页,当作新根节点,然后旧根节点和分裂出的新结点做新根的子节点,返回。通过write_set_获取父结点,然后调用BPlusTreeInternalPage::InsertAfterValue()将分裂后产生的新节点插入父节点,write_set_弹出旧结点(原来不安全的结点)。当父节点的大小超过了最大容量,则父节点不安全,调用Split()分裂并调用InsertIntoParent()递归,若是安全结点,则清理header_page_write_set_,结束。

BPLUSTREE_TYPE::Split()分裂结点,返回新的兄弟页。NewPage()创建新页,叶子节点分裂就将新页和待分裂的页强转为叶页,初始化新叶页后旧叶页调用BPlusTreeLeafPage::SplitLeafTo(),取出旧叶页的后一半数据给新叶页,并且设置旧叶页和新叶页的next_page_id。最后设置正在使用新叶页,然后返回。若分裂内部节点,将新页和待分裂的页强转为内部结点页,新结点页初始化后旧结点页调用BPlusTreeInternalPage::SplitInternalTo()取内部节点的最大容量加一的一半(后半段)的内容给新内部节点,限定结点大小。设置新结点页正在使用后返回。

这里我不理解的是在InsertIntoParent()中仅仅用if (ctx.write_set_.size() == 1)就确定是根节点分裂

搜索单一值

BPLUSTREE_TYPE::GetValue()实现,获取头部页的写锁header_page_,通过它获得根节点id,将根节点读锁加入read_set_然后强转为BPlusTreePage,清空header_page_,强转结点为BPlusTreeInternalPage,调用BPlusTreeInternalPage::FindInternelKey()按key二分查找中间节点的子节点引用,查到的页加读锁,加入read_set_然后将原先在read_set_中的父页弹出,这样就实现了读锁的"下沉",之后进入循环直到页是叶页。循环出来后将页强转为BPlusTreeLeafPage类,之后调用BPlusTreeLeafPage::FindKey()二分查找叶子节点的值,找到后组装传参vector。

Task2 B+树删除

BPLUSTREE_TYPE::Remove()实现,获取头部页写锁并得到根节点id,调用FindLeaf()查找key,获取查到的页强转成BPlusTreeLeafPage,调用BPlusTreeLeafPage::DeleteKey()先二分查找叶子结点key,若没有调用DeleteKeyAt()删除数组中对应的k-v,删除失败就清理header_page_write_set_并退出,若删除的k-v所在的叶页大小大于等于最小容量(叶子结点是max_size_/2,内部节点是(max_size_+1)/2)时,即为安全删除,清空write_set_并退出,若根节点时叶子,叶子根节点有一个键值对就能退出。最后都没有退出的就是需要非安全删除情况,可能会有修改父节点key、小于最小尺寸需从兄弟节点处拿甚至兄弟节点也不足直接合并,调用DealNode()合并或删除叶页。

BPLUSTREE_TYPE::DealNode()是个间接递归,它调用Merge()Merge()调用它。处理借取键值对或合并节点,write_set_的最后一个值是这个函数体需要处理的不安全的节点(已进行删除工作)。

  • 若是根节点

    • 根叶:清理write_set_header_page_
    • 不是叶:强转为内部节点,取第0个的引用做根节点id(header_page_强转为BPlusTreeHeaderPage可取root_page_id_

    返回

  • 不是根

    write_set_取出该页的父页,强转为内部节点

    • 是叶
      强转为叶页,父页调用FindInternelKey()二分查找叶子key获得index

      左边有值,有左兄弟(index > 0):取左兄弟(父页的index-1)的写锁存入write_set_,强转为叶页后保存变量

      右边有值,有右兄弟(index < 父页容量-1):取右兄弟(父页的index+1)的写锁存入write_set_,强转为叶页后保存变量

      • 左右兄弟都在(左叶页变量 && 右叶页变量):
        • 两个兄弟都不足(兄弟大小 <= 最小容量):调用Merge()与右节点合并,返回
        • 右边有剩余(右兄弟大小 > 最小容量):清空header_page_,向兄弟节点借键值对表示父页是安全节点,释放write_set_中父页的祖先节点及根的锁,叶页调用LendFromBrother()向右兄弟借,之后父页调用SetKeyAt()更新父页中的右兄弟结点在父页的索引,然后清理write_set_
        • 左边有剩余(else):清空header_page_,释放write_set_中父页的祖先节点及根的锁,叶页调用LendFromBrother()向左兄弟借,父页调用SetKeyAt()更新父页中的右兄弟结点在父页的索引,然后清理write_set_
      • 只有左兄弟(左叶页变量):
        • 左兄弟不足(左兄弟大小 <= 最小容量):调用Merge()与左兄弟合并,返回
        • 左边有剩余:清空header_set_,释放write_set_中父页祖先节点及根的锁,叶页调用LendFromBrother()向左兄弟借,父页调用SetKeyAt()更新父页中的左兄弟结点在父页的索引,然后清理write_set_
      • 只有右兄弟(右叶页变量):
        • 右兄弟不足(右兄弟大小 <= 最小容量):调用Merge()与右兄弟合并,返回
        • 右边有剩余:清空header_set_,释放write_set_中父页祖先节点及根的锁,叶页调用LendFromBrother()向右兄弟借,父页调用SetKeyAt()更新父页中的右兄弟结点在父页的索引,然后清理write_set_
    • 是内部结点

      强转为内部结点页,父页调用FindInternelKey()二分查找内部结点获得index

      左边有值,有左兄弟(index > 0):取左兄弟(父页的index-1)的写锁存入write_set_,强转为内部结点页后保存变量

      右边有值,有右兄弟(index < 父页容量-1):取右兄弟(父页的index+1)的写锁存入write_set_,强转为内部结点后保存变量

      • 左右兄弟都在(左结点页变量 && 右结点页变量):
        • 两个兄弟都不足(兄弟大小 <= 最小容量):调用Merge()与右节点合并,返回
        • 右边有剩余(右兄弟大小 > 最小容量):清空header_page_,释放write_set_中父页的祖先节点及根的锁,内部结点页调用LendFromBrother()向右兄弟借,之后父页调用SetKeyAt()更新父页中的右兄弟结点在父页的索引,然后清理write_set_
        • 左边有剩余(else):清空header_page_,释放write_set_中父页的祖先节点及根的锁,叶页调用LendFromBrother()向左兄弟借,父页调用SetKeyAt()更新父页中的右兄弟结点在父页的索引,然后清理write_set_
      • 只有左兄弟(左结点页变量):
        • 左兄弟不足(左兄弟大小 <= 最小容量):调用Merge()与左兄弟合并,返回
        • 左边有剩余:清空header_set_,释放write_set_中父页祖先节点及根的锁,叶页调用LendFromBrother()向左兄弟借,父页调用SetKeyAt()更新父页中的左兄弟结点在父页的索引,然后清理write_set_
      • 只有右兄弟(右结点页变量):
        • 右兄弟不足(右兄弟大小 <= 最小容量):调用Merge()与右兄弟合并,返回
        • 右边有剩余:清空header_set_,释放write_set_中父页祖先节点及根的锁,叶页调用LendFromBrother()向右兄弟借,父页调用SetKeyAt()更新父页中的右兄弟结点在父页的索引,然后清理write_set_

BPLUSTREE_TYPE::Merge()总是把右边节点的键值对转移给左边,再删除右边节点,这样方便更新NextPageId,若传入的经删除操作的页是叶子节点,将该页和兄弟页强转为叶页后,判断是右兄弟,兄弟结点就调用BPlusTreeLeafPage::MoveAllTo()将自己的k-v顺序插入该页的末尾,将该页的NextPageId设置为右兄弟的,设置删除结点索引为右兄弟索引(该页索引+1)。若判断是左兄弟,该页调用BPlusTreeLeafPage::MoveAllTo()将自己的k-v顺序插入左兄弟的尾部,左兄弟的NextPageId设置为该页的,设置删除节点索引为该页索引。若进行删除操作的页是内部结点,强转该页和兄弟页为内部节点页,判断是右兄弟就把兄弟k-v插入该页末尾,设置删除索引为兄弟,反之同理。之后将write_set_中该页父页的孩子出栈解锁,只剩父页和其祖先节点,把父页强转为内部节点页,调用DeleteKeyAt()将删除结点索引指定的结点页删除。若父页大小大于等于最小容量(安全删除),清理write_set_后就可返回,若是根结点,内部根节点右两个键值对(容量大于1)就行,返回,其他情况就是不安全删除,调用DealNode()进行间接递归。

BPlusTreeLeafPage::LendFromBrother()从brother移动一个键值对过来,兄弟调用KeyAt()ValueAt(),针对传入是否是左兄弟,将k-v插入结点的头或尾并设置兄弟的大小或删除k-v,最后返回新key。

Task3 叶子扫描的迭代器

必须添加一个c++迭代器,以有效地支持对叶页中的数据进行有序扫描。基本思想是存储兄弟指针,这样就可以有效地遍历叶页,然后实现一个迭代器,按顺序遍历每个叶页中的每个键值对。

Begin()End()函数在b_plus_true.cpp中,而迭代器类在index_iterator.cpp中实现

//索引迭代器类的私有属性
BPlusTreeLeafPage<KeyType, ValueType, KeyComparator> *leaf_page_{nullptr};  // 此迭代器指向的叶子
int index_{-1};  // 表示此迭代器现在指向leaf_page_中某叶页的第几个键值对
page_id_t my_page_id_;//表示迭代器指向的leaf_page_中某叶页的页id
BufferPoolManager *bpm_{nullptr};

重载了* ++ == !=四个符号来对索引迭代器进行操作

BPLUSTREE_TYPE::Begin()有重载,分别对应处理没给key和给了key的情况。没给就直接调用FindLeafPage()传入空key并指定找最左边的k-v,这样就返回了叶页链表首页,然后强转为叶页,构造索引迭代器后返回。传入key的Begin()多加了一层检测传回的页是否真的有key,没有说明FindLeafPage()没有找到,返回空索引迭代器。

BPLUSTREE_TYPE::End()同无参Begin()只不过指定找的是最右边的k-v(即index=internal_page->GetSize()-1

BPLUSTREE_TYPE::FindLeafPage()无锁获取根节点,根据传入的indextype判断需要的是最左/右的页还是进行k-v查找。

Task4 并行索引

见Task 2

相关文章:

P2 B+树索引

文章目录 Task1 B树页B树页B树内部结点B树叶子结点 Task2 B树操作Task2 B树插入和搜索的单一值插入单一值搜索单一值 Task2 B树删除 Task3 叶子扫描的迭代器Task4 并行索引 Task1 B树页 B树页 实际上是每个B树页面的标题部分&#xff0c;包含叶子页面和内部页面共享的信息。 …...

爬虫知识之BeautifulSoup库安装及简单介绍

一. 前言 在前面的几篇文章中我介绍了如何通过Python分析源代码来爬取博客、维基百科InfoBox和图片,其文章链接如下: 其中核心代码如下: # coding=utf-8 import urllib import re #下载静态HTML网页 url=http://www.csdn.net/ content = urllib.urlopen(url).read…...

如何有效取代FTP来帮助企业快速传输大文件

在互联网的发展历史上&#xff0c;FTP是一种具有里程碑意义的协议&#xff0c;它最早出现在1971年&#xff0c;是实现网络上文件传输的基础。FTP的优点是简单、稳定、兼容性强&#xff0c;可以在不同的操作系统和平台之间进行文件交换。然而&#xff0c;时代在进步&#xff0c;…...

免登陆积分商城原理

有客户需要免登陆积分商城&#xff0c;研究了一下发现免登陆用途广泛&#xff0c;实现原理也很简单。如果是浏览器无非就是使用fingerprintjs2之类的扩展来实现获取浏览器指纹ID&#xff0c;如果是APP就获取设备唯一标识&#xff0c;然后在使用cryptojs加密来传递到php&#xf…...

muduo源码学习base——Atomic(原子操作与原子整数)

Atomic(原子操作与原子整数&#xff09; 前置知识AtomicIntegerTget()getAndAdd()getAndSet() 关于原子操作实现无锁队列(lock-free-queue) 前置知识 happens-before&#xff1a; 用来描述两个操作的内存可见性 如果操作 X happens-before 操作 Y&#xff0c;那么 X 的结果对于…...

最短路相关笔记

Floyd Floyd 算法&#xff0c;是一种在图中求任意两点间最短路径的算法。 Floyd 算法适用于求解无负边权回路的图。 时间复杂度为 O ( n 3 ) O(n^3) O(n3)&#xff0c;空间复杂度 O ( n 2 ) O(n^2) O(n2)。 对于两点 ( i , j ) (i,j) (i,j) 之间的最短路径&#xff0c;有…...

Web前端-Vue2+Vue3基础入门到实战项目-Day5(自定义指令, 插槽, 案例商品列表, 路由入门)

自定义指令 基本使用 自定义指令: 自己定义的指令, 可以封装一些dom操作, 扩展额外功能全局注册// 1. 全局注册指令 Vue.directive(focus, {// inserted 会在 指令所在的元素, 被插入到页面中时触发inserted (el) {// el 就是指令所绑定的元素// console.log(el)el.focus()} …...

mysql json数据类型 相关函数

创建JSON文本的函数 1.JSON_ARRAY&#xff08;转换json数组&#xff09; 2.JSON_OBJECT&#xff08;转换json对象&#xff09; 3.JSON_QUOTE&#xff08;转义字符串&#xff09; 搜索JSON文本的函数 1.JSON_CONTAINS&#xff08;json当中是否包含指定value&#xff09; 2.J…...

如何实现前端实时通信(WebSocket、Socket.io等)?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…...

使用 SSSD 进行网络用户身份验证

文章目录 使用 SSSD 进行网络用户身份验证SSSD和Active Directory先决条件、假设和要求软件安装加域 SSSD配置自动创建HOME目录 检查和验证 Kerberos票证已知的问题参考 使用 SSSD 进行网络用户身份验证 SSSD 代表系统安全服务守护程序&#xff0c;它实际上是处理来自各种网络…...

紫光展锐携中国联通完成RedCap芯片V517孵化测试

近日&#xff0c;紫光展锐携手中国联通5G物联网OPENLAB开放实验室&#xff08;简称“OPENLAB实验室”&#xff09;共同完成RedCap芯片V517创新孵化&#xff0c;并实现在联通5G全频段3.5GHz、2.1GHz、900MHz下的端到端业务验证测试。 V517是一款基于紫光展锐5G成熟平台设计与研发…...

算法通关村第十一关青铜挑战——移位运算详解

大家好&#xff0c;我是怒码少年小码。 计算机到底是怎么处理数字的&#xff1f; 数字在计算机中的表示 机器数 一个数在计算机中的二进制表示形式&#xff0c;叫做这个数的机器数。 机器数是带符号的&#xff0c;在计算机用一个数的最高位存放符号&#xff0c;正数为0&am…...

2023年面试测试工程师一般问什么问题?

面试和项目一起&#xff0c;是自学路上的两大拦路虎。面试测试工程师一般会被问什么问题&#xff0c;总结下来一般是下面这4类&#xff1a; 1.做好自我介绍 2.项目相关问题 3.技术相关问题 4.人事相关问题 接下来&#xff0c;主要从以上四个方向分别展开介绍。为了让大家更有获…...

2023年中国汽车覆盖件模具竞争格局、市场规模及行业需求前景[图]

汽车覆盖件模具是汽车车身生产的重要工艺装备&#xff0c;其设计和制造时间约占汽车开发周期的 2/3&#xff0c;是汽车换型的重要制约因素之一。汽车覆盖件模具具有尺寸大、工作型面复杂、技术标准高等特点&#xff0c;属于技术密集型产品。汽车覆盖件模具按以其冲压的汽车覆盖…...

vue3项目运行报错import zhCn from “element-plus/lib/locale/lang/zh-cn“

解决办法 import zhCn from "element-plus/lib/locale/lang/zh-cn";修改为 import zhCn from "element-plus/dist/locale/zh-cn.mjs";...

读书笔记:Effective C++ 2.0 版,条款26(歧义)、条款27(禁止部分隐式生成的函数)

条款26: 当心潜在的歧义 即使cpp支持潜在二义性/歧义&#xff0c;也不要使用。 void f(int); void f(char); double d 6.02; f(d); //需要明确转换多继承充满了潜在二义性/歧义的可能。 class Base1 {public: int doIt();}; class Base2 {public: void doIt();}; class Deri…...

MySQL基本操作之数据库设计理论

1、数据的设计准则 1)糟糕的数据库设计表现在以下几个方面: 访问数据效率低下存在大量的数据冗余,浪费存储空间更新和检索数据时会出现许多问题2)良好的数据库设计表现在以下几方面: 访问效率高减少数据冗余,节省存储空间便于进一步扩展可以使应用程序的开发变得更容易…...

SpringBoot的日志系统(日志分组、文件输出、滚动归档)

[toc](目录) > SpringBoot3需要jdk17 # 1. 简介 1. Spring5及以后Spring自己实现了commons-logging&#xff0c;来作为内部的日志。日志的jar包是org.springframework:spring-jcl:6.0.10。查看org.apache.commons.logging.LogAdapter Java package org.apache.commons.log…...

一种基于HTTPS实现的Web账号登录Linux桌面系统的实现方案

问题由来 客户需求计划列入支持第三方帐号系统&#xff0c;包括Web账号。需求来源是用户想要用它们的帐号直接登录Linux Deepin操作系统。一个失败的实现方案是用户以较小的成本改造帐号管理系统发布HTTP服务&#xff0c;我们开发一个PAM模块与Web服务器交互&#xff0c;数据格…...

【Linux】psplash制作Linux开机动画

1. 下载psplash软件 下载psplash源码到ubuntu中&#xff1a; 下载地址&#xff1a;https://git.yoctoproject.org/psplash/commit/安装依赖环境 sudo apt-get install libgdk-pixbuf2.0-dev2. 准备图片 开机动画静态图片&#xff1a;psplash-poky.png开机动画进度条图片&…...

测试微信模版消息推送

进入“开发接口管理”--“公众平台测试账号”&#xff0c;无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息&#xff1a; 关注测试号&#xff1a;扫二维码关注测试号。 发送模版消息&#xff1a; import requests da…...

React Native 开发环境搭建(全平台详解)

React Native 开发环境搭建&#xff08;全平台详解&#xff09; 在开始使用 React Native 开发移动应用之前&#xff0c;正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南&#xff0c;涵盖 macOS 和 Windows 平台的配置步骤&#xff0c;如何在 Android 和 iOS…...

线程同步:确保多线程程序的安全与高效!

全文目录&#xff1a; 开篇语前序前言第一部分&#xff1a;线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分&#xff1a;synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分&#xff…...

STM32标准库-DMA直接存储器存取

文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA&#xff08;Direct Memory Access&#xff09;直接存储器存取 DMA可以提供外设…...

《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》

在注意力分散、内容高度同质化的时代&#xff0c;情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现&#xff0c;消费者对内容的“有感”程度&#xff0c;正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中&#xff0…...

ServerTrust 并非唯一

NSURLAuthenticationMethodServerTrust 只是 authenticationMethod 的冰山一角 要理解 NSURLAuthenticationMethodServerTrust, 首先要明白它只是 authenticationMethod 的选项之一, 并非唯一 1 先厘清概念 点说明authenticationMethodURLAuthenticationChallenge.protectionS…...

反射获取方法和属性

Java反射获取方法 在Java中&#xff0c;反射&#xff08;Reflection&#xff09;是一种强大的机制&#xff0c;允许程序在运行时访问和操作类的内部属性和方法。通过反射&#xff0c;可以动态地创建对象、调用方法、改变属性值&#xff0c;这在很多Java框架中如Spring和Hiberna…...

前端开发面试题总结-JavaScript篇(一)

文章目录 JavaScript高频问答一、作用域与闭包1.什么是闭包&#xff08;Closure&#xff09;&#xff1f;闭包有什么应用场景和潜在问题&#xff1f;2.解释 JavaScript 的作用域链&#xff08;Scope Chain&#xff09; 二、原型与继承3.原型链是什么&#xff1f;如何实现继承&a…...

AI+无人机如何守护濒危物种?YOLOv8实现95%精准识别

【导读】 野生动物监测在理解和保护生态系统中发挥着至关重要的作用。然而&#xff0c;传统的野生动物观察方法往往耗时耗力、成本高昂且范围有限。无人机的出现为野生动物监测提供了有前景的替代方案&#xff0c;能够实现大范围覆盖并远程采集数据。尽管具备这些优势&#xf…...

MacOS下Homebrew国内镜像加速指南(2025最新国内镜像加速)

macos brew国内镜像加速方法 brew install 加速formula.jws.json下载慢加速 &#x1f37a; 最新版brew安装慢到怀疑人生&#xff1f;别怕&#xff0c;教你轻松起飞&#xff01; 最近Homebrew更新至最新版&#xff0c;每次执行 brew 命令时都会自动从官方地址 https://formulae.…...