本章介绍一个实现简单的页面索引,并且实现了 DM 层对于上层的抽象:DataItem。
一、页面索引
页面索引,缓存了每一页的空闲空间。用于在上层模块进行插入操作时,能够快速找到一个合适空间的页面,而无需从磁盘或者缓存中检查每一个页面的信息。
1. PageIndex
PageIndex 将一页的空间划分成了 40 个区间。在启动时,就会遍历所有的页面信息,获取页面的空闲空间,安排到这 40 个区间中。insert 在请求一个页时,会首先将所需的空间向上取整,映射到某一个区间,随后取出这个区间的任何一页,都可以满足需求。
// 将一页划成40个区间
private static final int INTERVALS_NO = 40;
private static final int THRESHOLD = PageCache.PAGE_SIZE / INTERVALS_NO; /// 每个区间的大小
private Lock lock;
/// lists 是一个数组,数组中的每一个元素是一个 ArrayList 列表, 每个列表中保存了多个 PageInfo
/// lists 中第 i 个元素表示的是空余容量还剩 i 个区间的页面的集合
private List<PageInfo>[] lists;
PageInfo 实际保存了某页的页号,和该页空闲的区间大小。
public class PageInfo {
public int pgno;
public int freeSpace;
public PageInfo(int pgno, int freeSpace) {
this.pgno = pgno;
this.freeSpace = freeSpace;
}
}
2. 通过 PageIndex 获取页面
public PageInfo select(int spaceSize) {
lock.lock();
try {
int number = spaceSize / THRESHOLD; /// 要存 spaceSize 大小的数据,需要多少个区间
if(number < INTERVALS_NO) number ++; /// 向上取整
while(number <= INTERVALS_NO) {
if(lists[number].size() == 0) { /// 如果还有 number 个空余区间的页面数量为0
number ++; /// 只能换一个更大的区间
continue;
}
return lists[number].remove(0); /// 随意取出任何一页,注意使用的是remove
}
return null;
} finally {
lock.unlock();
}
}
被选择的页,会直接从 PageIndex 中移除,这意味着,同一个页面是不允许并发写的。在上层模块使用完这个页面后,需要将其重新插入 PageIndex。
public void add(int pgno, int freeSpace) {
lock.lock();
try {
int number = freeSpace / THRESHOLD; /// 该页面还有多少个空余空间
lists[number].add(new PageInfo(pgno, freeSpace));
} finally {
lock.unlock();
}
}
3. 填充 PageIndex
在 DataManager 被创建时,需要获取所有页面并填充 PageIndex。
void fillPageIndex()