从leveldb学编码技巧(3)

leveldb中的大部分文件都是用一种类似日志的方式来写数据的,比如和memtable一一对应的log文件,以及manifest文件。他们的特点是:文件内容都是一条条记录,每条记录都有特定的格式。

为了方便读取这类文件中的内容,leveldb使用一个log::Reader类完成读取和解析的工作。log::Reader提供了从文件中读取出一条完整记录的方法,定义如下:

bool ReadRecord(Slice* record, std::string* scratch);

使用ReadRecord方法大概是这样:

log::Reader reader;
while (reader.ReadRecord(&record, &scratch)) {
    // do something with record
}

这样的一个Class并不复杂,接口比较少,功能也很单一,实现起来比较简单。这里值得注意的是log::Reader对错误的处理方法。

按照某种特定格式解析日志内容,必须要考虑内容不符合预期的情况,也就是出现错误的数据,导致解析失败的情况。ReadRecord()方法返回一个bool值,表示此次读取记录是否成功。但是,具体的内部错误并没有反应出来。这里要记录内部错误也很简单,在解析出错的时候打印一条error日志就可以了。事实上,大部分程序中都是这么做的。

但是在有些时候,情况会变得比较复杂一点。log::Reader这个Class是一个通用的工具类,用来读取日志文件,它可能在不同的场景下被使用。比如,解析manifest文件的时候,解析.log文件的时候,并且可能在数据库初始化的时候被调用,也可能在其他时候被调用。不同的场景下,会对日志解析出错有不同的处理,有时候要打印error日志,有时候可能不需要打印error日志,还有时候可能要记下来出错的数据在文件哪个位置,有多少数据出错了。

leveldb通过在log::Reader中加入了一个虚基类来解决这个问题,log::Reader 的定义大概如下:

class Reader {
 public:
  // Interface for reporting errors.
  class Reporter {
   public:
    virtual ~Reporter();

    // Some corruption was detected.  "size" is the approximate number
    // of bytes dropped due to the corruption.
    virtual void Corruption(size_t bytes, const Status& status) = 0;
  };
  Reader(SequentialFile* file, Reporter* reporter, bool checksum,
         uint64_t initial_offset);

  ~Reader();
  
  bool ReadRecord(Slice* record, std::string* scratch);
  
  ...
  
}

log::Reader::Reporter是一个辅助类,只有一个纯虚函数。在解析文件内容的时候,如果遇到错误就调用一下Reporter的Corruption()方法。 按照这样的设计,使用log::Reader需要提供一个Reporter类,这个类来处理解析过程具体的错误,无论是打印error日志,还是其他什么动作,都可以在Reporter类里面来完成。

在使用log::Reader的地方,具体的log::Reader::Reporter子类都是在函数内部定义一个struct,然后再实例化这个Reporter来使用。这也是非常独特的一点,在函数内部定义struct,只在函数内使用。看leveldb的代码,发现大量的使用了这样的设计:在类内部定义类,在函数内部定义类,之前WriteBatch中也是这样定义了一个WriteBatch::Handler。

这种实现方式非常巧妙,使用起来很灵活,并且很好的保证了封装性。阅读代码的时候也比较容易搞清楚这些类的作用范围。不过不知道为什么,在日常的工作中很少看到这样写代码的。

你可能感兴趣的:(从leveldb学编码技巧(3))