2026-09-24 23:56:07 +08:00
|
|
|
use std::collections::{HashMap, HashSet};
|
2026-09-24 15:37:55 +08:00
|
|
|
use std::fs::{File, OpenOptions};
|
|
|
|
|
use std::io::{self, Read, Seek, SeekFrom, Write};
|
|
|
|
|
use std::path::Path;
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
// 页管理:定长页的读写,外加一层页缓冲池。
|
2026-09-24 15:37:55 +08:00
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
// 页
|
|
|
|
|
/// 页号:从 0 开始
|
2026-09-24 15:37:55 +08:00
|
|
|
pub type PageId = u64;
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
/// 缓冲池默认容量(页数)
|
|
|
|
|
pub const DEFAULT_POOL_SIZE: usize = 64;
|
|
|
|
|
|
2026-09-24 15:37:55 +08:00
|
|
|
#[derive(Debug, Clone)]
|
2026-09-24 21:28:49 +08:00
|
|
|
/// 一页:定长字节 + 页号
|
2026-09-24 15:37:55 +08:00
|
|
|
pub struct Page {
|
|
|
|
|
pub id: PageId,
|
|
|
|
|
pub data: Vec<u8>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Page {
|
2026-09-24 21:28:49 +08:00
|
|
|
/// 新建一页,全 0
|
2026-09-24 15:37:55 +08:00
|
|
|
pub fn new(id: PageId, size: usize) -> Self {
|
|
|
|
|
Page {
|
|
|
|
|
id,
|
|
|
|
|
data: vec![0u8; size],
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
/// 清零
|
2026-09-24 15:37:55 +08:00
|
|
|
pub fn zero(&mut self) {
|
|
|
|
|
self.data.fill(0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
2026-09-24 21:28:49 +08:00
|
|
|
/// 页文件:定长页读写 + 页缓存
|
2026-09-24 15:37:55 +08:00
|
|
|
pub struct PageFile {
|
|
|
|
|
file: File,
|
|
|
|
|
page_size: usize,
|
2026-09-24 21:28:49 +08:00
|
|
|
capacity: usize,
|
|
|
|
|
cache: HashMap<PageId, Page>,
|
2026-09-24 23:56:07 +08:00
|
|
|
last_used: HashMap<PageId, u64>,
|
|
|
|
|
clock: u64,
|
2026-09-24 21:28:49 +08:00
|
|
|
dirty: HashSet<PageId>,
|
|
|
|
|
hits: u64,
|
|
|
|
|
misses: u64,
|
2026-09-24 15:37:55 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PageFile {
|
2026-09-24 21:28:49 +08:00
|
|
|
/// 打开页文件(默认池容量)
|
2026-09-24 15:37:55 +08:00
|
|
|
pub fn open<P: AsRef<Path>>(path: P, page_size: usize) -> io::Result<Self> {
|
2026-09-24 21:28:49 +08:00
|
|
|
Self::with_options(path, page_size, DEFAULT_POOL_SIZE)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 打开页文件并指定池容量
|
|
|
|
|
pub fn with_options<P: AsRef<Path>>(
|
|
|
|
|
path: P,
|
|
|
|
|
page_size: usize,
|
|
|
|
|
capacity: usize,
|
|
|
|
|
) -> io::Result<Self> {
|
2026-09-24 15:37:55 +08:00
|
|
|
let file = OpenOptions::new()
|
|
|
|
|
.read(true)
|
|
|
|
|
.write(true)
|
|
|
|
|
.create(true)
|
|
|
|
|
.truncate(false)
|
|
|
|
|
.open(path)?;
|
2026-09-24 21:28:49 +08:00
|
|
|
Ok(PageFile {
|
|
|
|
|
file,
|
|
|
|
|
page_size,
|
|
|
|
|
capacity,
|
|
|
|
|
cache: HashMap::new(),
|
2026-09-24 23:56:07 +08:00
|
|
|
last_used: HashMap::new(),
|
|
|
|
|
clock: 0,
|
2026-09-24 21:28:49 +08:00
|
|
|
dirty: HashSet::new(),
|
|
|
|
|
hits: 0,
|
|
|
|
|
misses: 0,
|
|
|
|
|
})
|
2026-09-24 15:37:55 +08:00
|
|
|
}
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
/// 页大小
|
2026-09-24 15:37:55 +08:00
|
|
|
pub fn page_size(&self) -> usize {
|
|
|
|
|
self.page_size
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
// 缓冲池
|
|
|
|
|
/// 池容量(页数)
|
|
|
|
|
pub fn capacity(&self) -> usize {
|
|
|
|
|
self.capacity
|
2026-09-24 15:37:55 +08:00
|
|
|
}
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
/// 当前缓存了多少页
|
|
|
|
|
pub fn cached_pages(&self) -> usize {
|
|
|
|
|
self.cache.len()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 命中次数
|
|
|
|
|
pub fn hits(&self) -> u64 {
|
|
|
|
|
self.hits
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 未命中次数
|
|
|
|
|
pub fn misses(&self) -> u64 {
|
|
|
|
|
self.misses
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 命中率
|
|
|
|
|
pub fn hit_rate(&self) -> f64 {
|
|
|
|
|
let total = self.hits + self.misses;
|
|
|
|
|
if total == 0 {
|
|
|
|
|
0.0
|
|
|
|
|
} else {
|
|
|
|
|
self.hits as f64 / total as f64
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// 标记为最近使用
|
|
|
|
|
fn touch(&mut self, id: PageId) {
|
2026-09-24 23:56:07 +08:00
|
|
|
self.clock += 1;
|
|
|
|
|
self.last_used.insert(id, self.clock);
|
2026-09-24 21:28:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 淘汰一个最冷的页
|
|
|
|
|
fn evict_one(&mut self) -> io::Result<bool> {
|
2026-09-24 23:56:07 +08:00
|
|
|
let victim = self
|
|
|
|
|
.cache
|
|
|
|
|
.keys()
|
|
|
|
|
.min_by_key(|id| self.last_used.get(id).copied().unwrap_or(0))
|
|
|
|
|
.copied();
|
|
|
|
|
let Some(victim) = victim else {
|
|
|
|
|
return Ok(false);
|
|
|
|
|
};
|
|
|
|
|
if self.dirty.contains(&victim) {
|
|
|
|
|
let p = self.cache[&victim].clone();
|
|
|
|
|
self.write_raw(&p)?;
|
|
|
|
|
self.dirty.remove(&victim);
|
2026-09-24 21:28:49 +08:00
|
|
|
}
|
2026-09-24 23:56:07 +08:00
|
|
|
self.cache.remove(&victim);
|
|
|
|
|
self.last_used.remove(&victim);
|
|
|
|
|
Ok(true)
|
2026-09-24 21:28:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 直接写文件
|
|
|
|
|
fn write_raw(&mut self, page: &Page) -> io::Result<()> {
|
2026-09-24 15:37:55 +08:00
|
|
|
if page.data.len() != self.page_size {
|
|
|
|
|
return Err(io::Error::new(
|
|
|
|
|
io::ErrorKind::InvalidInput,
|
|
|
|
|
format!(
|
|
|
|
|
"page has {} bytes, this file uses {}",
|
|
|
|
|
page.data.len(),
|
|
|
|
|
self.page_size
|
|
|
|
|
),
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
self.file
|
|
|
|
|
.seek(SeekFrom::Start(page.id * self.page_size as u64))?;
|
2026-09-24 21:28:49 +08:00
|
|
|
self.file.write_all(&page.data)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 直接从文件读
|
|
|
|
|
fn read_raw(&mut self, id: PageId) -> io::Result<Page> {
|
|
|
|
|
let mut data = vec![0u8; self.page_size];
|
|
|
|
|
self.file.seek(SeekFrom::Start(id * self.page_size as u64))?;
|
|
|
|
|
self.file.read_exact(&mut data)?;
|
|
|
|
|
Ok(Page { id, data })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// 读一页(走缓存)
|
|
|
|
|
pub fn read_page(&mut self, id: PageId) -> io::Result<Page> {
|
2026-09-24 23:56:07 +08:00
|
|
|
self.note_access(id)?;
|
|
|
|
|
Ok(self.cache[&id].clone())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 只读地用一页(走缓存,不整页拷贝)
|
|
|
|
|
pub fn with_page<R>(&mut self, id: PageId, f: impl FnOnce(&Page) -> R) -> io::Result<R> {
|
|
|
|
|
self.note_access(id)?;
|
|
|
|
|
Ok(f(&self.cache[&id]))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 就地改一页(走缓存,不整页拷贝)
|
|
|
|
|
pub fn update_page<R>(&mut self, id: PageId, f: impl FnOnce(&mut Page) -> R) -> io::Result<R> {
|
|
|
|
|
self.note_access(id)?;
|
|
|
|
|
let r = f(self.cache.get_mut(&id).expect("cached above"));
|
|
|
|
|
self.dirty.insert(id);
|
|
|
|
|
Ok(r)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 记一次访问:命中加 hits,未命中读进来并记 misses
|
|
|
|
|
fn note_access(&mut self, id: PageId) -> io::Result<()> {
|
|
|
|
|
if self.cache.contains_key(&id) {
|
2026-09-24 21:28:49 +08:00
|
|
|
self.hits += 1;
|
2026-09-24 23:56:07 +08:00
|
|
|
} else {
|
|
|
|
|
self.misses += 1;
|
|
|
|
|
let p = self.read_raw(id)?;
|
|
|
|
|
if self.cache.len() >= self.capacity {
|
|
|
|
|
self.evict_one()?;
|
|
|
|
|
}
|
|
|
|
|
self.cache.insert(id, p);
|
2026-09-24 21:28:49 +08:00
|
|
|
}
|
2026-09-24 23:56:07 +08:00
|
|
|
self.touch(id);
|
|
|
|
|
Ok(())
|
2026-09-24 21:28:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 写一页(走缓存)
|
|
|
|
|
pub fn write_page(&mut self, page: &Page) -> io::Result<()> {
|
|
|
|
|
if page.data.len() != self.page_size {
|
|
|
|
|
return Err(io::Error::new(
|
|
|
|
|
io::ErrorKind::InvalidInput,
|
|
|
|
|
format!(
|
|
|
|
|
"page has {} bytes, this file uses {}",
|
|
|
|
|
page.data.len(),
|
|
|
|
|
self.page_size
|
|
|
|
|
),
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
if self.cache.len() >= self.capacity && !self.cache.contains_key(&page.id) {
|
|
|
|
|
self.evict_one()?;
|
|
|
|
|
}
|
|
|
|
|
self.cache.insert(page.id, page.clone());
|
|
|
|
|
self.dirty.insert(page.id);
|
|
|
|
|
self.touch(page.id);
|
2026-09-24 15:37:55 +08:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
/// 末尾追加一页
|
2026-09-24 15:37:55 +08:00
|
|
|
pub fn append_page(&mut self) -> io::Result<Page> {
|
|
|
|
|
let id = self.num_pages()?;
|
|
|
|
|
let page = Page::new(id, self.page_size);
|
|
|
|
|
self.write_page(&page)?;
|
|
|
|
|
Ok(page)
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
/// 文件里有多少页
|
2026-09-24 15:37:55 +08:00
|
|
|
pub fn num_pages(&mut self) -> io::Result<PageId> {
|
|
|
|
|
let len = self.file.metadata()?.len();
|
|
|
|
|
let size = self.page_size as u64;
|
|
|
|
|
if len % size != 0 {
|
|
|
|
|
return Err(io::Error::new(
|
|
|
|
|
io::ErrorKind::InvalidData,
|
|
|
|
|
format!(
|
|
|
|
|
"file length {} is not a multiple of page size {} (did page.size change?)",
|
|
|
|
|
len, size
|
|
|
|
|
),
|
|
|
|
|
));
|
|
|
|
|
}
|
2026-09-24 21:28:49 +08:00
|
|
|
let on_disk = len / size;
|
|
|
|
|
let cached_max = self
|
|
|
|
|
.cache
|
|
|
|
|
.keys()
|
|
|
|
|
.copied()
|
|
|
|
|
.max()
|
|
|
|
|
.map(|m| m + 1)
|
|
|
|
|
.unwrap_or(0);
|
|
|
|
|
Ok(on_disk.max(cached_max))
|
2026-09-24 15:37:55 +08:00
|
|
|
}
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
/// 把脏页写回磁盘
|
|
|
|
|
pub fn flush(&mut self) -> io::Result<()> {
|
|
|
|
|
let dirty: Vec<PageId> = self.dirty.iter().copied().collect();
|
|
|
|
|
for id in dirty {
|
|
|
|
|
if let Some(p) = self.cache.get(&id) {
|
|
|
|
|
let p = p.clone();
|
|
|
|
|
self.write_raw(&p)?;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
self.file.sync_all()?;
|
|
|
|
|
self.dirty.clear();
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// flush 之后 fsync
|
2026-09-24 15:37:55 +08:00
|
|
|
pub fn sync(&mut self) -> io::Result<()> {
|
2026-09-24 21:28:49 +08:00
|
|
|
self.flush()
|
2026-09-24 15:37:55 +08:00
|
|
|
}
|
|
|
|
|
|
2026-09-24 21:28:49 +08:00
|
|
|
/// 清空文件
|
2026-09-24 15:37:55 +08:00
|
|
|
pub fn truncate(&mut self) -> io::Result<()> {
|
2026-09-24 21:28:49 +08:00
|
|
|
self.cache.clear();
|
2026-09-24 23:56:07 +08:00
|
|
|
self.last_used.clear();
|
2026-09-24 21:28:49 +08:00
|
|
|
self.dirty.clear();
|
2026-09-24 15:37:55 +08:00
|
|
|
self.file.set_len(0)?;
|
|
|
|
|
self.file.seek(SeekFrom::Start(0))?;
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-09-24 21:28:49 +08:00
|
|
|
|
|
|
|
|
/// 退出前把脏页写回
|
|
|
|
|
impl Drop for PageFile {
|
|
|
|
|
fn drop(&mut self) {
|
|
|
|
|
let _ = self.flush();
|
|
|
|
|
}
|
|
|
|
|
}
|