LevelDB源码解析(三)

LevelDB(三)

三、 include/leveldb

1. slice.h

leveldb作者没有使用c++自带的字符串库,而是封装了一个表示字符串的指针和长度的Slice类,这样可以节省复制字符串带来的开销。

	//构造
	Slice();
	Slice(const char* d, size_t n);
	Slice(const std::string& s);
	Slice(const char* s);
	Slice(const Slice&);
	//拷贝赋值
	Slice& operator=(const Slice&);
	//获取字符串
	const char* data() const;
	//字符串长度
	size_t size() const;
	//判空
	bool empty() const;
	//重载下标[]
	char operator[](size_t n) const;
	//清空字符串
	void clear();
	//删除前缀
	void remove_prefix(size_t n);
	//Slice转换std::string
	std::string ToString() const;
	//Slice之间的比较 
	int compare(const Slice& b) const;
	//判断前缀
	bool starts_with(const Slice& x) const;

2. status.h

自定义状态码的类。
Status类只有一个成员变量const char* state_,主要结构为:

  • [0-3]位表示state_的长度。
  • 4位表示state_的状态码。
  • 5位以及以后表示state_的信息内容。

状态码:

  enum Code {
    kOk = 0,
    kNotFound = 1,
    kCorruption = 2,
    kNotSupported = 3,
    kInvalidArgument = 4,
    kIOError = 5
  };
// 拷贝赋值
// Status类的拷贝赋值函数只是对两个state指针的交换,而不是对state指向的内容的复制,加快复制操作。
inline Status& Status::operator=(Status&& rhs) noexcept {
  std::swap(state_, rhs.state_);
  return *this;
}
/*
1. 因为[0-3]位是代表长度,共四位, `sizeof(uint32_t ) == 4`,所以使用`size`来保存`state`的长度。
2. 开辟一块`size + 5`(前缀)的长度数组
3. 将从state开始`size + 5`长度拷贝到`result `中
4. 返回result 
*/
const char* Status::CopyState(const char* state) {
  uint32_t size;
  std::memcpy(&size, state, sizeof(size));  //1
  char* result = new char[size + 5];
  std::memcpy(result, state, size + 5);
  return result;
}

很有意思,size的大小可以用作前缀的长度,size存储的值表示State第三部分信息的长度。可以学习下!!!

3. comparator.h

1. Comparator

Comparator虚基类,定义了比较器。

主要方法如下 :

// 比较方法,比较a和b的大小:
//	a < b 返回 < 0
//  a = b 返回   0
//  a > b 返回 > 0
virtual int Compare(const Slice& a, const Slice& b) const = 0;
// 返回比较器的名字
virtual const char* Name() const = 0;
// 确认start要小于limit
virtual void FindShortestSeparator(std::string* start, const Slice& limit) const = 0;
// 找到短的字符串
virtual void FindShortSuccessor(std::string* key) const = 0;

继承关系如下:

在这里插入图片描述

2. BytewiseComparatorImpl(util/comparator.cc)

定义了一个按位比较的比较器。

// 比较方法,比较a和b的大小:
int Compare(const Slice& a, const Slice& b) const;
// 返回比较器的名字 leveldb.BytewiseComparator
const char* Name() const;
// 确认start要小于limit
void FindShortestSeparator(std::string* start, const Slice& limit) const;
/*
这里有几个逻辑:
	1. 先用min_length找到两个字符串的最小长度
	2. 用diff_index在min_length中找到两个字符串第一个不相同的位置
	3. 将(*start)[diff_index]char类型转换成uint8_t类型,便于比较
    4. 做了个优化  如果start的diff_byte位+1 都比limit的diff_byte位小,那么把start截取一下,再交给Compare去比
*/
// 找到字符串中第一个小于128(char范围)的字符,将key设置为剩下的长度
void FindShortSuccessor(std::string* key) const;

使用NoDestructor模板构造一个BytewiseComparatorImpl的单例对象。

3. InternalKeyComparator(db/dbformat.h)

db/dbformat.h

4. env.h

1. logger类(env.h)

记录日志的基类。

主要方法如下 :

//记录日志
void Logv(const char* format, std::va_list ap) = 0;

主要功能是封装在Linux和Windows的日志记录方式。

继承关系如下:

在这里插入图片描述

1. NoOpLogger类(memenv.cc)

空实现。

2. PosixLogger类(posix_logger.h)
// 接收一个FILE文件指针,对入参进行判断,初始化PosixLogger的局部变量
explicit PosixLogger(std::FILE* fp);
// 关闭FILE文件
~PosixLogger();
// Linux下记录的主体函数,接收日志格式和参数列表
// 内置缓冲区
void Logv(const char* format, std::va_list arguments);
/*
	1. 获取当前时间
		gettimeofday获取秒和毫秒
		localtime_r将gettimeofday拿到的秒转换为时间
	2. std::this_thread::get_id()获取当前线程的唯一标识,写入ostringstream中
		如果标识长度大于32,就进行自动的截取
	3. 定义一个512字节大小的缓冲区,并校验对齐方式
		第一次使用512字节的缓冲区,第二次动态分配缓冲区大小
	4. 将之前获取到的日期字符串格式化到buffer中,验证字符串偏移量合法
	5. 将可变参数列表格式化并写入缓冲区
	6. 如果512的缓冲区不够,就初始化动态缓冲区的大小,放入动态缓冲区中。
	7. 在字符串结尾加入\n
	8. 写入文件描述符中,并刷盘
*/
3. WindowsLogger类(windows_logger.h)

WindowsLogger类的Logv方法和PosixLogger类唯一不同的是时间的获取和处理方式上

// win
SYSTEMTIME now_components;
::GetLocalTime(&now_components);

int buffer_offset = std::snprintf(
    buffer, buffer_size, "%04d/%02d/%02d-%02d:%02d:%02d.%06d %s ",
    now_components.wYear, now_components.wMonth, now_components.wDay,
    now_components.wHour, now_components.wMinute, now_components.wSecond,
    static_cast<int>(now_components.wMilliseconds * 1000),
    thread_id.c_str());

// linux
struct ::timeval now_timeval;
::gettimeofday(&now_timeval, nullptr);
const std::time_t now_seconds = now_timeval.tv_sec;
struct std::tm now_components;
::localtime_r(&now_seconds, &now_components);

int buffer_offset = std::snprintf(
    buffer, buffer_size, "%04d/%02d/%02d-%02d:%02d:%02d.%06d %s ",
    now_components.tm_year + 1900, now_components.tm_mon + 1,
    now_components.tm_mday, now_components.tm_hour, now_components.tm_min,
    now_components.tm_sec, static_cast<int>(now_timeval.tv_usec),
    thread_id.c_str());
2. FileState类(memenv.cc)

使用引用计数对当前内存池(差点被它的名字骗了,可恶啊~)的操作(读写)进行封装。

成员变量:

// 文件大小
enum { kBlockSize = 8 * 1024 };

// 定义一个锁,对引用计数进行互斥操作
port::Mutex refs_mutex_;
int refs_ GUARDED_BY(refs_mutex_);

// 定义一个锁,对内存池进行互斥操作
mutable port::Mutex blocks_mutex_;
std::vector<char*> blocks_ GUARDED_BY(blocks_mutex_);
// 使用blocks_mutex_对内总字节数进行互斥操作
uint64_t size_ GUARDED_BY(blocks_mutex_);

成员方法:

// 构造函数 将引用计数refs_总字节数size_初始化为0
FileState();
// 释放内存池
~FileState();
// 引用计数++
void Ref();
// 引用计数--  如果引用计数为0, 将当前文件释放
void Unref();
// 返回总字节数size_
uint64_t Size();
// 释放内存池空间
void Truncate();
// 从偏移量offset开始读取n字节
// 返回Slice对象result 和 data开始的指针scratch
/*
	1. offset和size_的有效性判断
	2. 确定 第几个block block的偏移量 要拷贝的字节数
	3. 拷贝,更新变量
*/
Status Read(uint64_t offset, size_t n, Slice* result, char* scratch);
// 将data写入文件中
/*
	while循环,如果当前写入数据不为空
        1. 如果当前文件的总字节数刚好写满 size_ % kBlockSize, 就新加一块kBlockSize大小的内存
                否则拿到内存下标
        2. 将data写入当前下标的内存中
        3. 如果数据没有写完,就再新加一块kBlockSize大小的内存, 写入数据
        		数据写完就结束
*/
Status Append(const Slice& data);
3. SequentialFile类(env.h)

顺序读取文件基类。

主要方法如下 :

// 读n个字节到scratch中, 再将scratch封装成result切片
virtual Status Read(size_t n, Slice* result, char* scratch) = 0;
// 从当前位置往后偏移n个字节
virtual Status Skip(uint64_t n) = 0;

继承关系如下:

在这里插入图片描述

1. SequentialFileImpl类(memenv.cc)

对内存池FileState的封装,读写操作。主要是实现顺序读,使用pos_来记录当前位置,file_保存要读的内存池对象。

private:
  FileState* file_;
  uint64_t pos_;

// 构造函数,传入FileState对象,初始化位置是0.
// 对FileState对象的使用,引用计数++
SequentialFileImpl(FileState* file);
// 不使用FileState对象,引用计数--
~SequentialFileImpl();
// (从pos_开始)顺序读取n个字节,result为切片结果,scratch为数据的指针
Status Read(size_t n, Slice* result, char* scratch);
// 向后偏移n个字节
// 1. 检验内存池大小和偏移量
// 2. 内存不够就跳到尾部
Status Skip(uint64_t n);
2. PosixSequentialFile类(env_posix.cc)

封闭Linux下对文件描述符的读写操作。

private:
  const int fd_;
  const std::string filename_;

// 传入文件名和相应的fd,初始化成员变量
PosixSequentialFile(std::string filename, int fd);
// 关闭文件
~PosixSequentialFile();
// 从fd中读取n个字节,result为切片结果,scratch为数据的指针
Status Read(size_t n, Slice* result, char* scratch);
// 向后偏移n个字节
// 1. 检验偏移n个字节后是否合法
Status Skip(uint64_t n)Status Skip(uint64_t n);

检验偏移n个字节后是否合法: ::lseek(fd_, n, SEEK_CUR) == static_cast<off_t>(-1);`

3. WindowsSequentialFile类(windows_posix.cc)

PosixSequentialFile类相同,成员变量中对文件的操作改成ScopedHandle对象。

4. Limiter类(env_posix.cc)

可以帮助类限制资源的使用,避免耗尽。

private:
  // 可用资源的个数
  // std::atomic互斥的原子操作
  std::atomic<int> acquires_allowed_;

// 构造函数 传入并初始化acquires_allowed_
Limiter(int max_acquires);
// 请求资源
// 如果分配资源--后,资源个数大于0,返回true
// 否则还回资源++,返回false
bool Acquire();
// 释放资源++
void Release();
5. RandomAccessFile类(env.h)

随机读取文件的基类。

主要方法如下 :

// 默认构造函数
RandomAccessFile();
// 析构函数
virtual ~RandomAccessFile();
// 读取n个字节,返回result为切片结果,scratch为数据的指针
virtual Status Read(uint64_t offset, size_t n, Slice* result, char* scratch);

继承关系如下:

在这里插入图片描述

1. RandomAccessFileImpl(memenv.cc)

主要是对FileStatec对象内存池的读操作。

// 构造函数, 传入并初始化FileState对象,file_引用计数++
RandomAccessFileImpl(FileState* file);
// 析构函数  file_引用计数--
~RandomAccessFileImpl();
// 调用FileState对象的read方法
// 从偏移量offset开始读取n字节
// 返回Slice对象result 和 data开始的指针scratch
Status Read(uint64_t offset, size_t n, Slice* result, char* scratch);
2. PosixRandomAccessFile(env_posix.cc)

在Linux下对文件的随机读取操作。

private:
  //  表示是否每次read都需要打开文件, true则不需要
  const bool has_permanent_fd_;  
  const int fd_;                

  // 资源限制类Limiter 限制资源使用,避免耗尽
  Limiter* const fd_limiter_;
  // 文件名
  const std::string filename_;
// 构造函数,传入文件名,打开的文件描述符和资源限制对象
// 请示资源,如果请示成功返回true, 初始化fd
// 			否则 fd = 0 
// 如果资源请示失败,关闭文件fd
PosixRandomAccessFile(std::string filename, int fd, Limiter* fd_limiter);

// 如果之前请示资源成功,关闭fd并释放资源
~PosixRandomAccessFile();

// 从fd中从offset开始读取n个字节,返回result为切片结果,scratch为数据的指针
Status Read(uint64_t offset, size_t n, Slice* result, char* scratch);
// 对has_permanent_fd_标志进行判断,如果has_permanent_fd_为false,每次要open打开文件
// 使用pread从fd中offset偏移量的位置读取n个字节放到scratch中
// 对has_permanent_fd_标志进行判断,如果has_permanent_fd_为false,每次要读取完要关闭文件

pread函数:read 后再更新read位置(lseek操作) 相当于打包成一个原子操作

3. PosixMmapReadableFile(env_posix.cc)

在Linux下对mmap映射的内存空间进行随机读取操作。

private:
  char* const mmap_base_;
  const size_t length_;
  Limiter* const mmap_limiter_;
  const std::string filename_;

//构造函数 传入文件名,mmap映射地址, mmap映射长度, 资源限制 初始化变量
PosixMmapReadableFile(std::string filename, char* mmap_base, size_t length, Limiter* mmap_limiter);
// 使用munmap释放内存
~PosixMmapReadableFile();

// 直接从映射地址开始获得数据
Status Read(uint64_t offset, size_t n, Slice* result, char* scratch);
6. WritableFile类(env.h)

文件可写操作的基类。

主要方法如下 :

// 默认构造函数
WritableFile();
// 析构函数
virtual ~WritableFile();

// 写入操作
virtual Status Append(const Slice& data);
// 关闭文件 
virtual Status Close() = 0;
// 内存刷新
virtual Status Flush() = 0;
// 刷盘
virtual Status Sync() = 0;

继承关系如下:

在这里插入图片描述

1. WritableFileImpl类(memenv.h)

写入内存,只操作缓冲区(FileState对象)。

private:
  FileState* file_;

public:
// 构造函数 传入FileState对象并初始化
// 增加FileState对象的引用计数
WritableFileImpl(FileState* file); 

// 析构函数 减少FileState对象的引用计数
~WritableFileImpl() override { file_->Unref(); }

// 定入数据
Status Append(const Slice& data); 

// 以下函数不涉及,返回OK
Status Close(); 
Status Flush(); 
Status Sync(); 
2. PosixWritableFile类(env_posix.cc)

Linux下对fd的顺序写入。 提供缓冲区和刷盘操作。

private:
  //写入数据的缓冲区
  char buf_[kWritableFileBufferSize];
  // 当前数据在缓冲区的位置
  size_t pos_;
  int fd_;
  // 如果操作的文件名为MANIFEST开头,为true
  const bool is_manifest_;
  // 文件名
  const std::string filename_;
  // 文件夹名
  const std::string dirname_;  

// 构造函数 传入文件名和操作的fd  初始化
PosixWritableFile(std::string filename, int fd);
// 如果fd有效,关闭
~PosixWritableFile();

// 将data数据写入缓冲, 如果缓冲写满刷新缓冲
// 1. 把要写入的字节数和当前剩余的字节数做min, 就是尽可能把当前内存写满
// 2. 把write_data中的copy_size长拷贝到当前位置中
// 3. 当前要写入的数据开始往后偏移copy_size字节
// 4. 如果刚好写满 返回OK
//           否则  5. 将缓冲区数据写入fd中
// 				  6. 如果剩下要写入的大小  < buf_的最大容量, 就先写入buf_
// 				  7. 如果写满剩下的内存 大于等于 buf_的最大容量,就直接写入fd
Status Append(const Slice& data);

// 将缓冲区数据写入fd中,关闭fd
Status Close();
// 把buf_中的数据写入fd
Status Flush();
// 先将manifest文件写入磁盘,再写入缓冲区数据,将fd进行刷盘
Status Sync();
// 把buf_中的数据写入fd
Status FlushBuffer();
//循环调用write 直到所有data全部写完
Status WriteUnbuffered(const char* data, size_t size);
// 将非Manifest文件 fd中的操作系统缓存进行刷盘 fsync
Status SyncDirIfManifest();
// 将fd系统缓存区的内容写入到磁盘上,以确保数据完整性和并发性。
Status SyncFd(int fd, const std::string& fd_path);
// 获取文件夹的全路径 可能返回. 
std::string Dirname(const std::string& filename);
// 获取文件名 
Slice Basename(const std::string& filename);
// 判断文件名是否为MANIFEST为前缀
bool IsManifest(const std::string& filename);
7. FileLock类(env.h)

将fd和文件名作封装。(不要被它的名字骗了~)

继承关系如下:

在这里插入图片描述

1. PosixFileLock(env_posixx.cc)

在Linux下,将文件的操作符fd和文件名进行封装。

// 初始化fd和文件名
PosixFileLock(int fd, std::string filename);
// 返回fd
int fd();
// 返回文件名
std::string& filename();
8. PosixLockTable类(env_posix.h)

维护一个名称的集合,不允许相同名称重复上锁。

 private:
  port::Mutex mu_;
  std::set<std::string> locked_files_ GUARDED_BY(mu_);

// 对fname进行插入操作,返回插入的结果
bool Insert(const std::string& fname);
// 移除fname
void Remove(const std::string& fname);
class PosixLockTable {
 public:
  // LOCKS_EXCLUDED 指示一个互斥锁是否被独占持有。
  bool Insert(const std::string& fname) LOCKS_EXCLUDED(mu_) {
    mu_.Lock();
    bool succeeded = locked_files_.insert(fname).second;
    mu_.Unlock();
    return succeeded;
  }
  
  void Remove(const std::string& fname) LOCKS_EXCLUDED(mu_) {
    mu_.Lock();
    locked_files_.erase(fname);
    mu_.Unlock();
  }

 private:
  port::Mutex mu_;
  // GUARDED_BY  判断变量是否被线程安全地访问的关键字
  std::set<std::string> locked_files_ GUARDED_BY(mu_);
};
9. Env类(env.h)

对Linux和Windows环境下的操作进行封装的基类。

主要方法如下 :主要方法如下 :

//获取Env对象的单例
Default();
// 将fname文件以顺序读取方式打开
Status NewSequentialFile(const std::string& fname, SequentialFile** result);
// 将fname文件以随机读取方式打开
Status NewRandomAccessFile(const std::string& fname, RandomAccessFile** result);
// 将fname文件以可写方式打开
// 打开文件的时候会将文件原本的内容全部丢弃
Status NewWritableFile(const std::string& fname, WritableFile** result);
// 将fname文件以可写方式打开
// 打开文件的时候会以追加的方式 
// 基类不支持
Status NewAppendableFile(const std::string& fname, WritableFile** result);
// 检测filename文件是否存在 
bool FileExists(const std::string& fname);
// 读到directory_path路径下的所有目录信息
Status GetChildren(const std::string& dir, std::vector<std::string>* result);
// 删除一个文件
Status RemoveFile(const std::string& fname);
// 删除一个文件
// RemoveFile 和 DeleteFile实现一个即可
Status DeleteFile(const std::string& fname);
// 创建文件夹
Status CreateDir(const std::string& dirname);
// 删除文件夹
Status RemoveDir(const std::string& dirname);
// 删除文件夹
// RemoveDir 和 DeleteDir
Status DeleteDir(const std::string& dirname);
// 获得文件大小 
Status GetFileSize(const std::string& fname, uint64_t* file_size);
// 重命名文件 
Status RenameFile(const std::string& src,const std::string& target);
// 锁住当前文件: 当前文件只能打开一份
Status LockFile(const std::string& fname, FileLock** lock);
// 解锁当前文件
Status UnlockFile(FileLock* lock);
// work任务函数
void Schedule(void (*function)(void* arg), void* arg);
// 开启线程
void StartThread(void (*function)(void* arg), void* arg);
// 新建日志对象
Status NewLogger(const std::string& fname, Logger** result);
// 获取当前系统时间
uint64_t NowMicros();
// 当前线程休眠 micros毫秒
void SleepForMicroseconds(int micros);

继承关系如下:

在这里插入图片描述

1. PosixEnv类(env_posix.cc)

全局函数:

// 将锁定文件 / 解锁文件
// flock :文件锁
// fctnl :支持字节区域的上锁
int LockOrUnlock(int fd, bool lock) {
  errno = 0;
  struct ::flock file_lock_info;
  std::memset(&file_lock_info, 0, sizeof(file_lock_info));
  // F_WRLCK 写锁 F_RDLCK 读锁  F_UNLCK移除锁
  file_lock_info.l_type = (lock ? F_WRLCK : F_UNLCK);
  // 文件开始
  file_lock_info.l_whence = SEEK_SET;
  file_lock_info.l_start = 0;
  file_lock_info.l_len = 0;  // Lock/unlock entire file.
  // 锁定 / 解锁
  return ::fcntl(fd, F_SETLK, &file_lock_info);
}

// 返回当前mmap最大的个数
int MaxMmaps();

//返回打开的只读文件最大个数
// 1. ::getrlimit(RLIMIT_NOFILE, &rlim) 获取系统资源的限制
// 2. 如果获取失败, 返回50
// 3, 如果获取值为无限 使用std::numeric_limits<int>::max()获取极值 2147483647
// 4. 否则为获取到的20%
int MaxOpenFiles();

成员:

private:
    port::Mutex background_work_mutex_;
    port::CondVar background_work_cv_;
    bool started_background_thread_;
    std::queue<BackgroundWorkItem> background_work_queue_;
    PosixLockTable locks_;  
    Limiter mmap_limiter_; 
    Limiter fd_limiter_;   

// 构造函数 初始化成员变量 
PosixEnv();

// 如果调用,说明单例被析构,输出错误到stderr
~PosixEnv();

// 将fname文件以顺序读取方式打开
Status NewSequentialFile(const std::string& fname, SequentialFile** result);

// 将fname文件以随机读取方式打开
// 1. 如果获取不到mmap资源  !mmap_limiter_.Acquire(), 实例化PosixRandomAccessFile对象, 直接返回
// 2. 否则 先获取文件大小
// 3. 进行mmap映射, 实例化PosixMmapReadableFile对象, 返回
Status NewRandomAccessFile(const std::string& fname, RandomAccessFile** result);

// 将fname文件以可写方式打开
// 打开文件的时候会将文件原本的内容全部丢弃
Status NewWritableFile(const std::string& fname, WritableFile** result);
// 将fname文件以可写方式打开
// 打开文件的时候会以追加的方式 
Status NewAppendableFile(const std::string& fname, WritableFile** result);

// 检测filename文件是否存在
// ::access(filename.c_str(), F_OK) == 0
bool FileExists(const std::string& fname);

// 读到directory_path路径下的所有目录信息
// ::opendir(directory_path.c_str()
// ::readdir(dir)
// ::closedir(dir)
Status GetChildren(const std::string& dir, std::vector<std::string>* result);

// 删除一个文件
// ::unlink(filename.c_str()) != 0
Status RemoveFile(const std::string& fname);

// 创建文件夹
// ::mkdir(dirname.c_str(), 0755) != 0
Status CreateDir(const std::string& dirname);

// 删除文件夹
// ::rmdir(dirname.c_str()) != 0
Status RemoveDir(const std::string& dirname);

// 获得文件大小 
// ::stat(filename.c_str(), &file_stat) != 0
Status GetFileSize(const std::string& fname, uint64_t* file_size);

// 重命名文件 
// (std::rename(from.c_str(), to.c_str()) != 0
Status RenameFile(const std::string& src,const std::string& target);

// 锁住当前文件: 当前文件只能打开一份
// 1. 如果已经打开了,直接返回
// 2, 如果锁定失败,还原locks_集合
// 3. 返回PosixFileLock对象
Status LockFile(const std::string& fname, FileLock** lock);

// 解锁当前文件
// 解锁 + 还原locks_集合
Status UnlockFile(FileLock* lock);

// 新建日志对象 PosixLogger
Status NewLogger(const std::string& fname, Logger** result);

// 获取当前系统时间
// struct ::timeval tv;
// 获取当前系统时间的信息
// ::gettimeofday(&tv, nullptr);
uint64_t NowMicros();



// 开启守护线程
void StartThread(void (*function)(void* arg), void* arg);


// 当前线程休眠 micros毫秒
void SleepForMicroseconds(int micros);

// 循环执行background_work_queue_队列中的函数
void BackgroundThreadMain();
// 调用BackgroundThreadMain函数
void BackgroundThreadEntryPoint(PosixEnv* env);

// worker任务函数
// 1. 如果后台线程没有启动,
// 		1. 实例化std::thread对象,传入BackgroundThreadEntryPoint函数和其参数arg, 调用BackgroundThreadEntryPoint函数。
// 			BackgroundThreadEntryPoint函数会调用BackgroundThreadMain函数  持续消费队列
// 		2. 开启守护线程
// 2. 如果队列为空,那么Signal让后台线程执行,将任务插入队列中
// 这个函数的作用包括StartThread, 当后台线程没有启动的时候,它也会自动启动
void Schedule(void (*function)(void* arg), void* arg);
2. SingletonEnv模板类(env_posix.cc)

PosixEnv的单例实现类。

template <typename EnvType>

private:
// 为变量提供 aligned 存储
// aligned 存储是指使用额外的字节或位来确保变量的存储顺序和访问顺序。
// 这种存储方式可以提高内存访问效率,并减少内存带宽的占用。

// 长度 默认对齐方式
// instance_storage_表示 长度至少为sizeof(EnvType)并且以EnvType大小对齐
// 存储PosixEnv对象
typename std::aligned_storage<sizeof(EnvType), alignof(EnvType)>::type env_storage_;

// 判断PosixEnv有没有被初始化
static std::atomic<bool> env_initialized_;

//构造函数 1. 验证 2. 在env_storage_位置对PosixEnv进行初始化
SingletonEnv();
//实例化PosixEnv单例
using PosixDefaultEnv = SingletonEnv<PosixEnv>;
10. EnvWrapper类(env.h)

Env类的代理基类,实现在Env类的基础上进行自定义的封装。

就是对Env类的方法进行可视化的操作。(便于理解)

private:
  Env* target_;

// 初始化代理类对象
explicit EnvWrapper(Env* t);
virtual ~EnvWrapper();
// 返回持有的Env对象
Env* target();

// 以下都是使用持有的Env对象表现的代理类方法
Status NewSequentialFile(const std::string& f, SequentialFile** r);
Status NewRandomAccessFile(const std::string& f, RandomAccessFile** r);
Status NewWritableFile(const std::string& f, WritableFile** r);
Status NewAppendableFile(const std::string& f, WritableFile** r);
bool FileExists(const std::string& f);
Status GetChildren(const std::string& dir, std::vector<std::string>* r);
Status RemoveFile(const std::string& f);
Status CreateDir(const std::string& d);
Status RemoveDir(const std::string& d);
Status GetFileSize(const std::string& f, uint64_t* s);
Status RenameFile(const std::string& s, const std::string& t);
Status LockFile(const std::string& f, FileLock** l);
Status UnlockFile(FileLock* l); 
void Schedule(void (*f)(void*), void* a);
void StartThread(void (*f)(void*), void* a);
Status GetTestDirectory(std::string* path);
Status NewLogger(const std::string& fname, Logger** result) ;
uint64_t NowMicros(); 
void SleepForMicroseconds(int micros);

5. cache.h

此部分主要涉及缓存的哈希表的内容。

1. LRUHandle(cache.cc)

用作哈希表和LRU缓存的节点。

// 公共部分
// 节点key的地址
char key_data[1];
// 节点key的长度
size_t key_length;
// 返回key的切片
Slice key() const;
// key对应的哈希值
uint32_t hash;


// 用作hashTable
// 指向下一个位置
LRUHandle* next_hash;

// 以下用作LRUCache

// 节点删除的函数参数
void* value;
// 传入节点删除的函数
void (*deleter)(const Slice&, void* value);
LRUHandle* next;
LRUHandle* prev;
// 当前节点大小
size_t charge;
// 是否在缓存中
bool in_cache;
// 引用计数
uint32_t refs;
2. HandleTable(cache.cc)

自定义的哈希表,速度快!

本质上是一个LRUHandle的数组,每个LRUHandle对象有next_hash指针,可以形成一个冲突链表。

// 初始化成员变量 并构造哈希表
HandleTable();
// 析构分配的内存
~HandleTable();


// 返回最后一个冲突的的key hash
LRUHandle* Lookup(const Slice& key, uint32_t hash);

// 插入操作
// 1. 找到key hash 的位置
// 2. 对返回的指针进行覆盖
// 3. 如果之前没有冲突过   或者  有冲突但是向后找到一个空的位置
// 3.1  元素个数++ 重新分配内存
LRUHandle* Insert(LRUHandle* h);

// 删除指定key和hash的元素
LRUHandle* Remove(const Slice& key, uint32_t hash);

// 1. 通过hash & (length_ - 1) 定位到LRUHandle下标
// 1.1 如果这个下标为空,说明没有冲突,可以直接返回
// 1.2 如果这个下标不为空,说明有冲突
// 	1.2.1 如果key和hash值有一个不同就顺序查找下一个位置
//  1.2.2 如果key和hash都相同,返回这个位置
// 2. 顺序遍历,从这个下标开始找起,如果下标不为空 并且 hash值和key值都不是自己, 指向下一个冲突节点
// 3. 返回相同位置  或者  空
LRUHandle** FindPointer(const Slice& key, uint32_t hash);

// 设置哈希表的长度是4的倍数, 分配空间
// 遍历之前长度内的元素,

// 将当前的元素进行重新分配 
void Resize();

for (uint32_t i = 0; i < length_; i++) {
      LRUHandle* h = list_[i];
      // 当前位置不为空,循环复制其冲突元素
      // 循环摘掉当前元素h
      while (h != nullptr) {
        // 记录当前的下一个冲突元素
        LRUHandle* next = h->next_hash;
        // 记录当前的hash值
        uint32_t hash = h->hash;
        // 将hash在新的长度上进行映射, 找到其下标 
        LRUHandle** ptr = &new_list[hash & (new_length - 1)];
        // 将新的长度的下标挂到h的下一个上
        h->next_hash = *ptr;
        // 将新的长度的下标 = h
        *ptr = h;
        // h指向下一个元素
        h = next;
        // 个数++
        count++;
      }
    }



// 哈希表总长度
uint32_t length_;
// 已经占用的个数
uint32_t elems_;
// 哈希表起始节点指针
LRUHandle** list_;
3. LRUCache(cache.cc)

最近最少使用缓存,使用两个链表去记录已缓存的和未缓存的节点 ,并且使用哈希表去快速拿到该节点。

in_use(当前在使用)链表中是在缓存中并且正在使用的节点,lru_链表表示在缓存中但是未使用的节点,越靠近lru_链表的头部,表示节点越新。

插入节点先放入in_use链表中,在in_use的节点引用计数最少都是1.

  • 进行加引用操作的时候,如果节点的引用计数是1并且在缓存中(lru_链表),表示在之前用过,就将节点从lru_链表中删除,放入in_use_链表。
  • 进行减引用操作的时候,如果节点的引用计数是1并且在缓存中(in_use_),表示不再使用了,就将节点从in_use_链表中删除,放入lru_链表
// 初始化容量和使用数,构造空的lru和in_use_链表
LRUCache();
// 循环遍历in_use_链表,删除没有引用计数的节点
~LRUCache();
// 初始化容量
void SetCapacity(size_t capacity);

// 插入节点
/* 1. 分配一块sizeof(LRUHandle) - 1 + key.size()大的内存
   1.1因为LRUHandle结构中已经存了key data的第一个字符,所以要-1
   2. 对变量初始化 
   		  e->value = value;
          e->deleter = deleter;
          e->charge = charge;
          e->key_length = key.size();
          e->hash = hash;
          e->in_cache = false;
          e->refs = 1; // 插入之后就会被调用者使用
   3. 如果当前容量大于0  == 开启缓存
   		当前元素放在缓存中,引用记数++
   		插入in_use_链表, 更新usage_
   		FinishErase 如果已经存在这个节点 , 将老的节点释放掉
   4. 如果当前容量等于0  == 不缓存
   5. 清除哈希链表中没有被引用的节点  
   		和Prune的区别是,这个只有在已使用的内存大于最大容量的时候才会删除
*/
Cache::Handle* Insert(const Slice& key, uint32_t hash, void* value, size_t charge, void (*deleter)(const Slice& key, void* value));

// 在哈希表中快速查询目标节点 如果存在增加节点的引用计数
Cache::Handle* Lookup(const Slice& key, uint32_t hash);

// 减引用操作
void Release(Cache::Handle* handle);

//在哈希表和当前缓存中都删除该节点
void Erase(const Slice& key, uint32_t hash);

// 清除没有被引用的节点
void Prune();
// 返回已经使用的空间大小
size_t TotalCharge();


private:
// 在lru链表中删除节点
  void LRU_Remove(LRUHandle* e);
// 将节点加到list链表中
  void LRU_Append(LRUHandle* list, LRUHandle* e);
// 增加节点的引用计数
// 在lru_链表中,移到到in_use_链表
  void Ref(LRUHandle* e);
// 不使用该节点 引用计数--
// 如果该节点的引用计数为0 调用节点的回调函数进行删除
// 否则放到lru_链表中,如果下次insert不够的时候, 再释放
  void Unref(LRUHandle* e);
// 完成删除操作
  bool FinishErase(LRUHandle* e);

// 定义的容量大小
  size_t capacity_;

  // 可能会被多个线程访问 
  mutable port::Mutex mutex_;
  // 已经使用的容量大小
  size_t usage_;
  // 在缓存中的节点,是一个循环链表
  LRUHandle lru_;
  // 既存在缓存中,又被外部引用的节点,是一个循环链表
  LRUHandle in_use_;
  // 用于快速获取某个节点
  HandleTable table_;
4. Cache(cache.h)

缓存的基类,封装了缓存常用的方法。

其中有一个Handle的空结构体(空类),用来和void*作区分,对函数和返回值进行限制。

空结构体(空类)的大小sizeof为1,因为C++自动给空类插入了一个char。

类图如下:

在这里插入图片描述

函数解释如下:

Cache() = default;
virtual ~Cache();

// Opaque handle to an entry stored in the cache.
// 空对象 void*
struct Handle {};
// 将数据插入到缓存中
// 1. 针对特殊的数据,可能需要特别的析构函数,因此提供deleter回调函数
// 2. 针对返回值,需要手动的调用this->Release(handle)函数,来释放
virtual Handle* Insert(const Slice& key, void* value, size_t charge,
                       void (*deleter)(const Slice& key, void* value)) = 0;

// 在缓存中查找key的数据
virtual Handle* Lookup(const Slice& key) = 0;

// 释放handle句柄
virtual void Release(Handle* handle) = 0;

// 获取key对应的值
virtual void* Value(Handle* handle) = 0;

// 从缓存中删除指定的key
virtual void Erase(const Slice& key) = 0;

// 缓存id,可能不同的客户端会共用相同的cache
virtual uint64_t NewId() = 0;

// 删除所有不活跃的 entry。
// 内存受限的应用程序可能希望调用此方法以减少内存使用。
virtual void Prune() {}

// 计算整个缓存中的所有元素的大小
virtual size_t TotalCharge() const = 0;Cache() = default;
virtual ~Cache();

// Opaque handle to an entry stored in the cache.
// 空对象 void*
struct Handle {};

// 将数据插入到缓存中
// 1. 针对特殊的数据,可能需要特别的析构函数,因此提供deleter回调函数
// 2. 针对返回值,需要手动的调用this->Release(handle)函数,来释放
virtual Handle* Insert(const Slice& key, void* value, size_t charge, void (*deleter)(const Slice& key, void* value)) = 0;

// 在缓存中查找key的数据
virtual Handle* Lookup(const Slice& key) = 0;

// 释放handle句柄
virtual void Release(Handle* handle) = 0;

// 获取key对应的值
virtual void* Value(Handle* handle) = 0;

// 从缓存中删除指定的key
virtual void Erase(const Slice& key) = 0;

// 缓存id,可能不同的客户端会共用相同的cache
virtual uint64_t NewId() = 0;

// 删除所有不活跃的 entry。
// 内存受限的应用程序可能希望调用此方法以减少内存使用。
virtual void Prune() {}

// 计算整个缓存中的所有元素的大小
virtual size_t TotalCharge() const = 0;
5. ShardedLRUCache(cache.cc)

因为一个LRUCache只能存放一种数据,那么ShardedLRUCache就是使用了一个LRUCache的数组,可以将不同种类的数据放到一个容器(ShardedLRUCache)中。

static const int kNumShardBits = 4;
static const int kNumShards = 1 << kNumShardBits; // 16

Cache* NewLRUCache(size_t capacity) { return new ShardedLRUCache(capacity); }

// 初始化16个LRUCache的容量  每个LRUCache的大小至少为1
ShardedLRUCache(size_t capacity);
~ShardedLRUCache();

// 先定位存放的位置,然后调用LRUCache的方法插入节点
Handle* Insert(const Slice& key, void* value, size_t charge,
void (*deleter)(const Slice& key, void* value));

// 先定位存放的位置,然后调用LRUCache的方法查找节点
Handle* Lookup(const Slice& key);

// 先定位存放的位置,然后调用LRUCache的方法释放节点
void Release(Handle* handle);

// 返回handle对应的值
void* Value(Handle* handle);

// 先定位存放的位置,然后调用LRUCache的方法删除节点
void Erase(const Slice& key);

// 获取一个新的id
uint64_t NewId();

// 将所有的LRUCache清除多余节点操作
void Prune();

// 调用LRUCache的方法累加所有已使用的缓存大小
size_t TotalCharge() const;

// 计算s的哈希值
uint32_t HashSlice(const Slice& s);
// 用hash移位操作计算出在哪个shard‘
uint32_t Shard(uint32_t hash);

private:
	// 存放数据的缓存数组
    LRUCache shard_[kNumShards];
    // 可能有多个id取数据  用互斥锁保护 
    port::Mutex id_mutex_;
	// 保存最新的编号
    uint64_t last_id_;

6. options.h

1. Options(Options.cc)

Options记录了leveldb中参数信息。

// 构造函数  初始化一个默认的配置
Options();
// 被用来表中key比较,默认是字典序
const Comparator* comparator;
// 打开数据库,true表示如果数据库不存在,创建新的
bool create_if_missing = false;
// 打开数据库,true表示如果数据库存在,抛出错误
bool error_if_exists = false;
// 如果为 true,则实现将对其正在处理的数据进行积极检查,如果检测到任何错误,则会提前停止。 这可能会产生不可预见的后果:
// 例如,一个数据库条目的损坏可能导致大量条目变得不可读或整个数据库变得无法打开。
bool paranoid_checks = false;
// 封装了Linux或者Windows的相关接口
Env* env;
// db日志句柄
Logger* info_log = nullptr;
// memtable的大小(默认4mb)
size_t write_buffer_size = 4 * 1024 * 1024;
// 允许打开的最大文件数
int max_open_files = 1000;
// block的缓存
Cache* block_cache = nullptr;
// 每个block的数据包大小(未压缩),默认是4k
size_t block_size = 4 * 1024;
// block中记录完整key的间隔 每16个为一个group ?
int block_restart_interval = 16;
// 生成新文件的阈值,默认2k
size_t max_file_size = 2 * 1024 * 1024;
// 数据压缩类型,默认是kSnappyCompression,压缩速度快
// 还有一个状态是 不进行压缩
CompressionType compression = kSnappyCompression;
// true表示在打开数据库折时候读取已有的  MANIFES和log files 文件
bool reuse_logs = false;
// block块中的过滤策略,支持布隆过滤器
const FilterPolicy* filter_policy = nullptr;
2. ReadOptions(Options.cc)

控制读操作的配置。

// 如果为true,则将根据相应的校验和验证从底层存储读取的所有数据。
bool verify_checksums = false;
// 是否应该将为此迭代读取的数据缓存在内存中
bool fill_cache = true;
// 读取相应的快照
const Snapshot* snapshot = nullptr;
3. WriteOptions(Options.cc)

控制写操作的配置。

// 如果为true,则在认为写入完成之前,将从操作系统缓冲区缓存中清除写入(通过调用WritableFile::Sync())。
// 如果此标志为true,则写入速度将变慢。
bool sync = false;

7. write_batch.h

rep_的结构如下:

  • 前8位表示唯一的序列号
  • 9-12位表示key-value的个数
  • 13位表示当前数据操作的类型 kTypeValue 表示插入操作或者kTypeDeletion表示删除操作
  • 后面就是key长度 - key值 - value长度 - value值

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NtK9HBAQ-1691994227383)(D:\Graduate_life\SecondYear\leveldb\images\WriteBatch_1.PNG)]

类图如下:

在这里插入图片描述

1. Handler(write_batch.h)

昌对两种操作类型 kTypeValue 表示插入操作或者kTypeDeletion表示删除操作封装的基类。

virtual ~Handler();
// 插入操作
virtual void Put(const Slice& key, const Slice& value) = 0;
// 删除操作
virtual void Delete(const Slice& key) = 0;
2. MemTableInserter(write_batch.cc)

提供对MemTable操作的方法,目的是将sequence_数据写入MemTable中。

// 要进行操作的数据 
SequenceNumber sequence_;
// MemTable
MemTable* mem_;
// 插入操作 调用MemTable的方法
virtual void Put(const Slice& key, const Slice& value);
// 删除操作 调用MemTable的方法
virtual void Delete(const Slice& key);
3. WriteBatchInternal(write_batch_internal.h)

提供给write_batch类的静态方法,不想在write_batch类暴露。

// 8-byte sequence number  + 4-byte Batch个数
static const size_t kHeader = 12;
private:
	// 8-byte sequence number  + 4-byte Batch个数 + type
	std::string rep_;

和write_batch一样,前8位是序号数,后4位是write_batch的个数。

// 解析rep_的后4位,返回个数
static int Count(const WriteBatch* batch);
// 将n解析到rep_的后4位
static void SetCount(WriteBatch* batch, int n);
// 解析rep_的前8位,返回序号
static SequenceNumber Sequence(const WriteBatch* batch);
// 将seq解析到rep_的前8位
static void SetSequence(WriteBatch* batch, SequenceNumber seq);
// 返回rep_
static Slice Contents(const WriteBatch* batch);
// 返回rep_大小
static size_t ByteSize(const WriteBatch* batch);
// 将contents设置为rep_
static void SetContents(WriteBatch* batch, const Slice& contents);
// 将WriteBatch的数据和MemTable进行绑定,使用WriteBatch的rep_数据写入MemTable
static Status InsertInto(const WriteBatch* batch, MemTable* memtable);
// 将src超过kHeader的部分添加到dst后
static void Append(WriteBatch* dst, const WriteBatch* src);
4. WriteBatch(write_batch.h)

WriteBatch使用批量写来提高性能,支持put和delete。

private:
	//存储具体数据
	std::string rep_;

// 调用Clear()进行初始化 
WriteBatch();
WriteBatch(const WriteBatch&) = default;
WriteBatch& operator=(const WriteBatch&) = default;
~WriteBatch();

// 将rep_9-12位解析出来并+1, 再写入rep_9-12位 
// rep_添加一位数据  插入操作 的类型
// 将key key长度  value value长度加入到rep_中
void Put(const Slice& key, const Slice& value);

// 将rep_9-12位解析出来并+1, 再写入rep_9-12位 
// rep_添加一位数据  删除操作 的类型
// 只将将key key长度 加入到rep_中
void Delete(const Slice& key);

// 删除rep_数据 并重置长度为kHeader
void Clear();
// 返回rep_的长度
size_t ApproximateSize() const;

// 设置当前的数据长度(修改8-12位), 并将source的数据内容写到rep_后面
void Append(const WriteBatch& source);

// 解析数据然后将其插入到memtable
/*
	1. 先把前12位干掉
	2. 循环处理剩下的数据
		解析当前操作位,干掉当前的操作位 
		对memtable进行相应的操作 插入/删除  
	3. 对操作的正确性进行校验
*/
Status Iterate(Handler* handler) const;

8. filterPolicy.h

1. FilterPolicy(filterPolicy.h)

过滤器的基类。

virtual ~FilterPolicy();
// 返回过滤器的名字
virtual const char* Name() const = 0;
// 创建
virtual void CreateFilter(const Slice* keys, int n, std::string* dst) const = 0;
virtual bool KeyMayMatch(const Slice& key, const Slice& filter) const = 0;

类图如下所示:

在这里插入图片描述

2. BloomFilterPolicy(bloom.cc)

布隆过滤器。

// 返回key的一种哈希值
static uint32_t BloomHash(const Slice& key) {
  return Hash(key.data(), key.size(), 0xbc9f1d34);
}

// 每个key占用的bits大小
size_t bits_per_key_;
// 对bits_per_key_变量进行压缩, 保证在1-30之间
size_t k_;
// 初始化 k_
explicit BloomFilterPolicy(int bits_per_key);
// 返回leveldb.BuiltinBloomFilter2
const char* Name() const;
// 创建布隆过滤器
void CreateFilter(const Slice* keys, int n, std::string* dst);
// 如果key在布隆过滤器哈希化的字符集合中 返回true
bool KeyMayMatch(const Slice& key, const Slice& bloom_filter);
3. InternalFilterPolicy(db/dbformat.h)

db/dbformat.h

9. iterator.h

1. Iterator(iterator.h)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Pop6xg50-1691994227385)(D:\Graduate_life\SecondYear\leveldb\images\Iterator_2.PNG)]

在这里插入图片描述

// 清除函数使用链表  头节点内联在迭代器中
struct CleanupNode {
    bool IsEmpty() const { return function == nullptr; }
	
    // 清除函数的执行
    void Run() {
      assert(function != nullptr);
      (*function)(arg1, arg2);
    }
	
    // 该节点的清除函数
    CleanupFunction function;
    // 参数
    void* arg1;
    void* arg2;
    // 指向下一个清除函数
    CleanupNode* next;
  };
// 清除函数头节点
CleanupNode cleanup_head_;
// 将cleanup_head_中的function 和 next置空
Iterator();

// 释放节点空间
// 如果cleanup_head_链表中的有节点 执行每个CleanupNode中的清除函数 并释放节点
~Iterator();

using CleanupFunction = void (*)(void* arg1, void* arg2);
// 将function函数挂到cleanup_head_链表上  便于释放 
RegisterCleanup(CleanupFunction function, void* arg1, void* arg2);
bool Valid();
void SeekToFirst();
void SeekToLast();
void Seek(const Slice& target);
void Next();
void Prev();
Slice key();
Slice value();
Slice status();
2. EmptyIterator(table/iterator.cc)

EmptyIterator类:空的迭代器类。返回false。

如果创建别的迭代器参数出错,为了不报错而创建的空迭代器实例。

3. MemTableIterator(db/memtable.cc)

MemTableIterator是对memtable的迭代,本质是对skiplist迭代器的封装。

db下的MemTableIterator.h部分。

4. LevelFileNumIterator(version_set.cc)
1. FileMetaData

记录一个SST文件中的参数。

FileMetaData();
// 引用计数
int refs;
// 记录已经seek的个数  如果超过一定数量会进行compaction
int allowed_seeks;  // Seeks allowed until compaction
// 唯一标识一个sstable
uint64_t number;
// 文件大小 
uint64_t file_size;    // File size in bytes

InternalKey smallest;  // Smallest internal key served by table
InternalKey largest;   // Largest internal key served by table
2. LevelFileNumIterator

leveldb中level > 0的SST文件的迭代器

// 比较器
const InternalKeyComparator icmp_;
// 打开文件列表
const std::vector<FileMetaData*>* const flist_;
// 当前操作的文件下标
uint32_t index_;
// 一个编码的缓冲
mutable char value_buf_[16];
// 初始化成员变量
LevelFileNumIterator(const InternalKeyComparator& icmp, const std::vector<FileMetaData*>* flist);
// 判断当前操作文件的下标是否在文件列表中
bool Valid();
// 下标指向第一个文件
void SeekToFirst();
// 下标指向最后一个文件  有判空操作
void SeekToLast();
// 找到target所在的文件下标
void Seek(const Slice& target);
// 当前操作的文件++
void Next();
// 当前操作的文件-- 循环
void Prev();
// 当前操作的文件中key的最大值
Slice key();
// 返回当前操作的文件中数据的个数和文件大小
Slice value();
// ok
Status status();
5. Block::Iter(block.cc)

遍历一个block中的数据部分。

table下的block.h部分。

6. IteratorWrapper(iterator_wrapper.h)

封装了一个迭代器指针。主要是用来操作Iterator。

  1. 可以避免虚函数带来的开销。
  2. 可以提供本地缓存
// 迭代器指针
Iterator* iter_;
// 当前迭代器指针的有效性
bool valid_;
// 当前迭代器指针中key的最大值
Slice key_;
// 空初始化
IteratorWrapper();
// 初始化成员变量
IteratorWrapper(Iterator* iter);
// 删除持有的迭代器指针
~IteratorWrapper();
// 返回持有的迭代器指针
Iterator* iter();
// 设置持有的迭代器
void Set(Iterator* iter);
// 当前持有的迭代器指针有效性
bool Valid();
// 持有的迭代器指针 key的最大值
Slice key();
// 持有的迭代器指针 中数据的个数和文件大小
Slice value();
// 持有的迭代器指针的 状态
Status status();
// 持有的迭代器指针的 下一个
void Next();
// 持有的迭代器指针的 前一个
void Prev();
// 找到k所在的文件下标 更新key和有效性
void Seek(const Slice& k);
// 持有的迭代器指针的 第一个位置
void SeekToFirst();
// 持有的迭代器指针的 最后一个位置
void SeekToLast();
// 更新key和有效性
void Update();
7. TwoLevelIterator(two_level__iterator.cc)

两级缓存:一级和LevelFileNumIterator一样,是SST文件的迭代器,二级是table cache的迭代器。

// 用来构建第二层迭代器data_iter_
// 构建结果也有可能是空的  取决于用户传进来的回调函数
typedef Iterator* (*BlockFunction)(void*, const ReadOptions&, const Slice&);
BlockFunction block_function_;
void* arg_;

// 迭代器的状态位
Status status_;

// 读文件的配置信息
const ReadOptions options_;
// 一级迭代器
IteratorWrapper index_iter_;
// 二级迭代器
IteratorWrapper data_iter_;
// 如果data_iter_非空 表示index_iter_调用block_function_创建data_iter_后的结果
std::string data_block_handle_;
// 初始化成员变量
TwoLevelIterator(Iterator* index_iter, BlockFunction block_function, void* arg, const ReadOptions& options);
~TwoLevelIterator();

// 将当前操作的状态保存
SaveError(const Status& s);
// 向后 跳过空的二级迭代器
/*
	1, 二级迭代器为空 || 二级迭代器不合法
	2. 一级迭代器不合法 设置二级迭代器为空 返回 表示有问题了
	3.  一级迭代器合法
		1. 移动到下一个index_iter所对应的二级索引
		2. 如果没有就构建二级迭代器 有就不构建了
		3. 如果二级迭代器之前就有 返回其开始的位置
*/
SkipEmptyDataBlocksForward();
// 向前 跳过空的二级迭代器  同上
SkipEmptyDataBlocksBackward();
// 设置二级索引
SetDataIterator(Iterator* data_iter);
// 构造二级迭代器
/*
	1. 如果最外层都显示无效了,内部也直接设置无效
	2. 否则 
		1。取出 数据的个数和文件大小
		2. 如果 创建过了 不执行
			data_iter_.iter() != nullptr 
			handle.compare(data_block_handle_) == 0 说明创建过了
		   否则 调用block_function_函数创建迭代器,并给data_block_handle_赋值 
		   	设置二级迭代器
*/
InitDataBlock();

// 二级迭代器的有效性
bool Valid();
// 先将一级迭代器定位到开始  再检查二级迭代器的构建
// 如果二级迭代器有数据 也定位到开始 
// 跳过空的二级迭代器(构建结果也有可能是空的)
void SeekToFirst();

// 同上
void SeekToLast();
// 定位target所在的迭代器
/*
	1. 一级索引找到taget在从哪个SST文件开始查找
	2. 初始化data_block 再检查二级迭代器的构建
	3. 在二级索引中查找数据
	4. 跳过背后为空的data_block索引
*/
void Seek(const Slice& target);
// 二级迭代器的下一们  跳过背后为空的data_block索引
void Next();
// 二级迭代器的前一们  跳过背后为空的data_block索引
void Prev();
// 二级迭代器的key 最大的key
Slice key();
// 二级迭代器的value 数据的个数和文件大小
Slice value();
// 返回状态
Slice status();
8. MergingIterator(table/merger.h)

用于合并多个SST文件的迭代器。使用归并排序。

// 实例化MergingIterator对象  如果迭代器个数为0返回空迭代器
// 参数: 比较器 迭代器数组 迭代器个数
Iterator* NewMergingIterator(const Comparator* comparator, Iterator** children, int n);
// 迭代的方向的枚举类型
enum Direction { kForward, kReverse };

// 表示迭代的方向
Direction direction_;

// 保存比较器
const Comparator* comparator_;

// 这里是为了防止迭代器太多,所以直接在堆上分配
IteratorWrapper* children_;

// 迭代器个数
int n_;

// 当前有效的迭代器,因为有多个,所以需要实时记录当前所用的
IteratorWrapper* current_;
// current_指向 key最小的迭代器
void FindSmallest();
// current_指向 key最大的迭代器
void FindLargest();

// 初始化成员变量
// 在children_中单独创建IteratorWrapper对象
MergingIterator(const Comparator* comparator, Iterator** children, int n);


// current_current_不为空返回true
bool Valid();

// 所有迭代器都指向首位 
// current_ 指向最小的迭代器
// 设置正向标记
void SeekToFirst();

// 所有迭代器都指向末位 
// current_ 指向最大的迭代器
// 设置反向标记
void SeekToLast();

// 所有迭代器找到k所在的文件下标 
// current_ 指向最小的迭代器
// 设置正向标记
void Seek(const Slice& k);

// 返回current_ 的key
Slice key();
// 返回current_ 的value
Slice value();

// children_中所有迭代器都为true 返回true
Status status();

// 如果是kForward 直接current_->Next() 找最小的key
// 如果是kReverse 遍历所有迭代器 如果和current_相同 next 设置正向标记
	// current_->Next() 找最小的key
void Next();

// 如果是kReverse 直接current_->Prev() 找最大的key
// 如果是kForward 遍历所有迭代器  Prev 设置反向标记
	// current_->Next() 找最大的key
// 持有的迭代器指针的 前一个
void Prev();
9. DBIter(db/db_iter.cc)

db/db_iter.h

10. dumpfile.h

db/dumpfile.cc

11. table_builder.h

table/table_builder.cc

12. table.h

table/table.cc

13. db.h

1. Range
struct LEVELDB_EXPORT Range {
  Range() = default;
  Range(const Slice& s, const Slice& l) : start(s), limit(l) {}

  Slice start;  // Included in the range
  Slice limit;  // Not included in the range
};
2. Snapshot

快照的基类。

class LEVELDB_EXPORT Snapshot {
 protected:
  virtual ~Snapshot();
};
3. db

LevelDB的基类。

// 删除dbname下所有文件
Status DestroyDB(const std::string& name, const Options& options);

// 构建repair对象,运行恢复db
Status RepairDB(const std::string& dbname, const Options& options);
// 打开db文件
static Status Open(const Options& options, const std::string& name, DB** dbptr);

DB() = default;

virtual ~DB();

virtual Status Put(const WriteOptions& options, const Slice& key, const Slice& value) = 0;

virtual Status Delete(const WriteOptions& options, const Slice& key) = 0;

virtual Status Write(const WriteOptions& options, WriteBatch* updates) = 0;

virtual Status Get(const ReadOptions& options, const Slice& key, std::string* value) = 0;

virtual Iterator* NewIterator(const ReadOptions& options) = 0;

virtual const Snapshot* GetSnapshot() = 0;

virtual void ReleaseSnapshot(const Snapshot* snapshot) = 0;

virtual bool GetProperty(const Slice& property, std::string* value) = 0;

virtual void GetApproximateSizes(const Range* range, int n, uint64_t* sizes) = 0;

virtual void CompactRange(const Slice* begin, const Slice* end) = 0;
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
要阅读Leveldb源码,你可以按照以下步骤进行: 1. 确保你对C++语言有基本的了解。Leveldb是用C++编写的,因此你需要熟悉C++的语法和面向对象编程的概念。 2. 阅读Leveldb的README文件。这个文件提供了关于Leveldb的基本信息,如其用途、功能和性能特征。同时,它还列出了Leveldb的依赖关系,这对于理解源码以及构建和运行Leveldb非常重要。 3. 了解Leveldb的核心概念和数据结构。Leveldb是一个高效的键值存储库,它使用了一些关键的数据结构,如有序字符串表(Skip List)和持久化存储。 4. 查看Leveldb的目录结构。Leveldb源码包含了一些核心文件和目录,如“db”目录下的文件是Leveldb的核心实现。理解源码的组织结构可以帮助你快速找到感兴趣的部分。 5. 阅读核心文件的源码。从“db/db_impl.cc”文件开始,这个文件是Leveldb的主要实现。阅读这个文件可以帮助你了解Leveldb如何管理内存、实施并发控制和实现持久化存储。 6. 跟踪函数调用和数据流。了解Leveldb的主要功能是如何通过函数调用进行实现的很重要。你可以使用调试器或添加日志输出来跟踪函数调用和数据流,这有助于你了解代码的执行流程和逻辑。 7. 阅读Leveldb的测试用例。Leveldb源码中包含了大量的测试用例,这些用例对于理解Leveldb的不同功能和特性非常有帮助。通过阅读和运行这些测试用例,你可以对Leveldb的行为有更深入的了解。 8. 参考文档和论文。如果你想更深入地了解Leveldb的实现原理和技术细节,可以查阅Leveldb的官方文档或相关的论文。这些文档可以为你提供更详细的信息和背景知识。 最后,要理解Leveldb源码并不是一件简单的任务,需要投入大量的时间和精力。所以,建议你在阅读源码之前,对C++和数据库原理有一定的了解和经验,同时也要具备耐心和持续学习的精神。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老李头带你看世界

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值