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树页面的标题部分,包含叶子页面和内部页面共享的信息。 …...
爬虫知识之BeautifulSoup库安装及简单介绍
一. 前言 在前面的几篇文章中我介绍了如何通过Python分析源代码来爬取博客、维基百科InfoBox和图片,其文章链接如下: 其中核心代码如下: # coding=utf-8 import urllib import re #下载静态HTML网页 url=http://www.csdn.net/ content = urllib.urlopen(url).read…...

如何有效取代FTP来帮助企业快速传输大文件
在互联网的发展历史上,FTP是一种具有里程碑意义的协议,它最早出现在1971年,是实现网络上文件传输的基础。FTP的优点是简单、稳定、兼容性强,可以在不同的操作系统和平台之间进行文件交换。然而,时代在进步,…...
免登陆积分商城原理
有客户需要免登陆积分商城,研究了一下发现免登陆用途广泛,实现原理也很简单。如果是浏览器无非就是使用fingerprintjs2之类的扩展来实现获取浏览器指纹ID,如果是APP就获取设备唯一标识,然后在使用cryptojs加密来传递到php…...

muduo源码学习base——Atomic(原子操作与原子整数)
Atomic(原子操作与原子整数) 前置知识AtomicIntegerTget()getAndAdd()getAndSet() 关于原子操作实现无锁队列(lock-free-queue) 前置知识 happens-before: 用来描述两个操作的内存可见性 如果操作 X happens-before 操作 Y,那么 X 的结果对于…...

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

Web前端-Vue2+Vue3基础入门到实战项目-Day5(自定义指令, 插槽, 案例商品列表, 路由入门)
自定义指令 基本使用 自定义指令: 自己定义的指令, 可以封装一些dom操作, 扩展额外功能全局注册// 1. 全局注册指令 Vue.directive(focus, {// inserted 会在 指令所在的元素, 被插入到页面中时触发inserted (el) {// el 就是指令所绑定的元素// console.log(el)el.focus()} …...
mysql json数据类型 相关函数
创建JSON文本的函数 1.JSON_ARRAY(转换json数组) 2.JSON_OBJECT(转换json对象) 3.JSON_QUOTE(转义字符串) 搜索JSON文本的函数 1.JSON_CONTAINS(json当中是否包含指定value) 2.J…...

如何实现前端实时通信(WebSocket、Socket.io等)?
聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅:探索Web开发的奇妙世界 欢迎来到前端入门之旅!感兴趣的可以订阅本专栏哦!这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…...
使用 SSSD 进行网络用户身份验证
文章目录 使用 SSSD 进行网络用户身份验证SSSD和Active Directory先决条件、假设和要求软件安装加域 SSSD配置自动创建HOME目录 检查和验证 Kerberos票证已知的问题参考 使用 SSSD 进行网络用户身份验证 SSSD 代表系统安全服务守护程序,它实际上是处理来自各种网络…...

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

算法通关村第十一关青铜挑战——移位运算详解
大家好,我是怒码少年小码。 计算机到底是怎么处理数字的? 数字在计算机中的表示 机器数 一个数在计算机中的二进制表示形式,叫做这个数的机器数。 机器数是带符号的,在计算机用一个数的最高位存放符号,正数为0&am…...

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

2023年中国汽车覆盖件模具竞争格局、市场规模及行业需求前景[图]
汽车覆盖件模具是汽车车身生产的重要工艺装备,其设计和制造时间约占汽车开发周期的 2/3,是汽车换型的重要制约因素之一。汽车覆盖件模具具有尺寸大、工作型面复杂、技术标准高等特点,属于技术密集型产品。汽车覆盖件模具按以其冲压的汽车覆盖…...

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支持潜在二义性/歧义,也不要使用。 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,来作为内部的日志。日志的jar包是org.springframework:spring-jcl:6.0.10。查看org.apache.commons.logging.LogAdapter Java package org.apache.commons.log…...

一种基于HTTPS实现的Web账号登录Linux桌面系统的实现方案
问题由来 客户需求计划列入支持第三方帐号系统,包括Web账号。需求来源是用户想要用它们的帐号直接登录Linux Deepin操作系统。一个失败的实现方案是用户以较小的成本改造帐号管理系统发布HTTP服务,我们开发一个PAM模块与Web服务器交互,数据格…...
【Linux】psplash制作Linux开机动画
1. 下载psplash软件 下载psplash源码到ubuntu中: 下载地址:https://git.yoctoproject.org/psplash/commit/安装依赖环境 sudo apt-get install libgdk-pixbuf2.0-dev2. 准备图片 开机动画静态图片:psplash-poky.png开机动画进度条图片&…...

测试微信模版消息推送
进入“开发接口管理”--“公众平台测试账号”,无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息: 关注测试号:扫二维码关注测试号。 发送模版消息: import requests da…...
React Native 开发环境搭建(全平台详解)
React Native 开发环境搭建(全平台详解) 在开始使用 React Native 开发移动应用之前,正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南,涵盖 macOS 和 Windows 平台的配置步骤,如何在 Android 和 iOS…...
线程同步:确保多线程程序的安全与高效!
全文目录: 开篇语前序前言第一部分:线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分:synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分ÿ…...

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(Direct Memory Access)直接存储器存取 DMA可以提供外设…...

《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》
在注意力分散、内容高度同质化的时代,情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现,消费者对内容的“有感”程度,正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中࿰…...

ServerTrust 并非唯一
NSURLAuthenticationMethodServerTrust 只是 authenticationMethod 的冰山一角 要理解 NSURLAuthenticationMethodServerTrust, 首先要明白它只是 authenticationMethod 的选项之一, 并非唯一 1 先厘清概念 点说明authenticationMethodURLAuthenticationChallenge.protectionS…...
反射获取方法和属性
Java反射获取方法 在Java中,反射(Reflection)是一种强大的机制,允许程序在运行时访问和操作类的内部属性和方法。通过反射,可以动态地创建对象、调用方法、改变属性值,这在很多Java框架中如Spring和Hiberna…...

前端开发面试题总结-JavaScript篇(一)
文章目录 JavaScript高频问答一、作用域与闭包1.什么是闭包(Closure)?闭包有什么应用场景和潜在问题?2.解释 JavaScript 的作用域链(Scope Chain) 二、原型与继承3.原型链是什么?如何实现继承&a…...

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

MacOS下Homebrew国内镜像加速指南(2025最新国内镜像加速)
macos brew国内镜像加速方法 brew install 加速formula.jws.json下载慢加速 🍺 最新版brew安装慢到怀疑人生?别怕,教你轻松起飞! 最近Homebrew更新至最新版,每次执行 brew 命令时都会自动从官方地址 https://formulae.…...