在前面已经较为详细的介绍了SkipList,也使用java 做了一个简单的实现。LevelDB中的SkipList实现比较经典,我看的时候觉得上头的就是Random,所以单独拿出来
random方法
首先来看下Random的在RandomHeight方法中的使用方式:
template <typename Key, class Comparator>
int SkipList<Key, Comparator>::RandomHeight() {
// Increase height with probability 1 in kBranching
static const unsigned int kBranching = 4;
int height = 1; // 初始level是1
while (height < kMaxHeight && rnd_.OneIn(kBranching)) {// 根据概率,默认1/4
height++;
}
assert(height > 0);
assert(height <= kMaxHeight); // 当前的level是不是小于kMaxHeight(默认12)
return height;
}
其中最要的是这个rnd_.OneIn 方法,这个方法就是生成均匀分布的随机数。
现在的随机算法都是伪随机,算法为线性同余生成器,算法表示为:
Xn+1 = (a * Xn + c) % m其中,
Xn是当前生成的伪随机数,Xn+1是下一个生成的伪随机数,a是乘法因子,c是增量,m是模数。通常,a取16807,c取0,m取2147483647,这是因为这组参数在计算中具有良好的性质,可以生成均匀分布的伪随机数。
在leveldb中就使用到了上面提到的两个魔法数,具体是现在在util/random.h,因为是一个整体,所以全部复制出来,如下:
class Random {
private:
uint32_t seed_;
public:
explicit Random(uint32_t s) : seed_(s & 0x7fffffffu) { //0x7fffffff u表示无符号 2^31-1 二进制 111 1111 1111 1111 1111 1111 1111 1111 将第一位取0 ,因为无符号转化为有符号的时候头部位1 表示为负数
// 所以此处的主要目的是确保传入的s为正数,默认传入的seed初始值为0xdeadbeef 二进制为1101 1110 1010 1101 1011 1110 1110 1111
// Avoid bad seeds.
if (seed_ == 0 || seed_ == 2147483647L) {
seed_ = 1;
}
}
uint32_t Next() {
static const uint32_t M = 2147483647L; // 2^31-1
static const uint64_t A = 16807; // bits 14, 8, 7, 5, 2, 1, 0 //无符号64位二进制0100 0001 1010 0111
// We are computing
// seed_ = (seed_ * A) % M, where M = 2^31-1
//
// seed_ must not be zero or M, or else all subsequent computed values
// will be zero or M respectively. For all other values, seed_ will end
// up cycling through every number in [1,M-1]
uint64_t product = seed_ * A; //26696993619177 二进制 High[000000 00000 0000 0000 1100 0010 0011 1] low [111 00000 1101 0010 0011 1100 1110 1001]
//这里是计算Mod算法的一个优化,一个64为的数,可以分为高33 位和低31 位的数
// Compute (product % M) using the fact that ((x << 31) % M) == x. product=high<<31+low 又因为product=seed_*A,所以此时product=(high*M+high+low)%M 其中 M = 2^31-1
// 因为seed_ 和A 中,seed_的值在初始化的时候就让他小于2^31-1,A 是固定的16807,所以这两个值都不会大于M的值,所以取余最后的结果就等(high+low)%M=high+low,所以下面的这个计算是获取high和low的值相加,也就得到了seed_
// 但是有一种情况就是product的low为刚好但是 2^31-1,这个时候product=(high*M+high+1*M)%M=high ,但是使用下面的结果会是high+M,因为M&M=M。所以,需要判断是否seed_ 比M大,然后前去M,直接使用high,也确保了seed的值一直小于M
// 最后强制转换为32位的值
seed_ = static_cast<uint32_t>((product >> 31) + (product & M)); // High+low [000000 00000 0000 0000 1100 0010 0011 1]+[111 00000 1101 0010 0011 1100 1110 1001] & [111 1111 1111 1111 1111 1111 1111 1111]
// The first reduction may overflow by 1 bit, so we may need to
// repeat. mod == M is not possible; using > allows the faster
// sign-bit-based test.
if (seed_ > M) {
seed_ -= M;
}
return seed_;
}
// 在[0,n-1]之间返回随机数
// Returns a uniformly distributed value in the range [0..n-1]
// REQUIRES: n > 0
uint32_t Uniform(int n) { return Next() % n; }
// n分之一的概率返回true
// Randomly returns true ~"1/n" of the time, and false otherwise.
// REQUIRES: n > 0
bool OneIn(int n) { return (Next() % n) == 0; }
// //首先求得[0,max_log]的一个随机数,然后求得[0,2^maxlog-1]的一个随机数
// Skewed: pick "base" uniformly from range [0,max_log] and then
// return "base" random bits. The effect is to pick a number in the
// range [0,2^max_log-1] with exponential bias towards smaller numbers.
uint32_t Skewed(int max_log) { return Uniform(1 << Uniform(max_log + 1)); }
};
}
上面的方法就随机数的seed计算比较上头。因为第一个种子确定后,后面的种子都相对确定了,而且这个出现的概率比较均匀,所以也就是OneIn能够保证出现概率接近的原因。