关于Bitvet
/*
A bitmap is an instance of the following structure.
This bitmap records the existence of zero or more bits
with values between 1 and iSize, inclusive.
There are three possible representations of the bitmap.
If iSize<=BITVEC_NBIT, then Bitvec.u.aBitmap[] is a straight
bitmap. The least significant bit is bit 1.
If iSize>BITVEC_NBIT and iDivisor==0 then Bitvec.u.aHash[] is
a hash table that will hold up to BITVEC_MXHASH distinct values.
Otherwise, the value i is redirected into one of BITVEC_NPTR
sub-bitmaps pointed to by Bitvec.u.apSub[]. Each subbitmap
handles up to iDivisor separate values of i. apSub[0] holds
values between 1 and iDivisor. apSub[1] holds values between
iDivisor+1 and 2*iDivisor. apSub[N] holds values between
N*iDivisor+1 and (N+1)*iDivisor. Each subbitmap is normalized
to hold deal with values between 1 and iDivisor.
*/
位图有三种不同的表现形式。当iSize<=BITVEC_NBIT时,那么Bitvec.u.aBitmap[]直接是一个位图。
如果iSize>BITVEC_NBIT是,对应两种情况。当iDivisor==0,那么Bitvec.u.aHash[]是一个哈希表,这个哈希表有BITVEC_MXHASH个不同的bucket.当iDvisor>0时,i的值会重定向到由指针Bitvec.u.apSub[]指针指向的BITVEC_NPTR子位图。在[1,iDivisor]范围内的值可以映射到指针apSub[0]指向的位图上。在[iDivisor+1,2*Divisor]范围内的值可以映射到指针apSub[1]指向的的位图上。同理,在[N*iDivisor+1,(N+1)*iDivisor]范围内的值可以映射到指针apSub[N]指向的位图上。每个位图都已经标准化,处理从1到iDivisor之间的值。
struct Bitvec {
u32 iSize; /* Maximum bit index. Max iSize is 4,294,967,296. */
u32 nSet; /* Number of bits that are set - only valid for aHash
element. Max is BITVEC_NINT. For BITVEC_SZ of 512,
this would be 125. */
u32 iDivisor; /* Number of bits handled by each apSub[] entry. */
/* Should >=0 for apSub element. */
/* Max iDivisor is max(u32) / BITVEC_NPTR + 1. */
/* For a BITVEC_SZ of 512, this would be 34,359,739. */
union {
BITVEC_TELEM aBitmap[BITVEC_NELEM]; /* Bitmap representation */
u32 aHash[BITVEC_NINT]; /* Hash table representation */
Bitvec *apSub[BITVEC_NPTR]; /* Recursive representation */
} u;
};
A bitmap is used to record which pages of a database file have been
journalled during a transaction, or which pages have the "dont-write"
property. Usually only a few pages are meet either condition.
So the bitmap is usually sparse and has low cardinality.
But sometimes (for example when during a DROP of a large table) most
or all of the pages in a database can get journalled. In those cases,
the bitmap becomes dense with high cardinality. The algorithm needs
to handle both cases well.
位图被用来记录数据库中的那些页面在事务当中被日志记录了,或者记录那些设置了"dont-write"标志的页面。通常来说,都是这两种情况中的一种。所以,位图一般很稀疏并且基数很小。但是,如果使用了drop table的操作,那么该表中的所有数据库页面中的大部分都将被记录到日志当中,在这种情况下,位图将会变得很稠密并且基数也很大。综上,需要使用适当的算法来对以上两种情况进行不同的处理。
The size of the bitmap is fixed when the object is created.
All bits are clear when the bitmap is created. Individual bits
may be set or cleared one at a time.
Test operations are about 100 times more common that set operations.
Clear operations are exceedingly rare. There are usually between
5 and 500 set operations per Bitvec object, though the number of sets can
sometimes grow into tens of thousands or larger. The size of the
Bitvec object is the number of pages in the database file at the
start of a transaction, and is thus usually less than a few thousand,
but can be as large as 2 billion for a really big database.
当位图对象被创建时,位图的大小是固定的。当位图被创建时,位图当中所有的比特位都将被清零。一次可以设置或者清零一个比特位。
测试操作的次数比设置操作的次数大约多100倍,清零操作的次数微乎其微。尽管设置操作有时会有数万次或者更多,但是对于每个Bitvet对象,平均大约5到500个设置操作。Bitvet对象的大小是事务开始时数据库中页面的个数,所以它的大小通常少于几千个,但是对于一个非常大的数据库,这个数目可能会达到20亿。
(p->u.aBitmap[i/BITVEC_SZELEM] & (1<<(i&(BITVEC_SZELEM-1))))!=0;
对于u.aBitmap[i/BITVEC_SZELEM] 相当于定位到位图中的某个字节(因为把位图当做是一个数组来理解,为了便于管理,数组中元素的类型是u8,即unsigned char),这个字节有8个比特位。因此,下一步是定位到这个字节内部的某个比特位(这个比特位在这个字节当中的下标,即个比特位是这个字节内部的第几个比特位),而1<<(i&(BITVEC_SZELEM-1))正是为了完成这一定位操作的。所以最后进行与(&)操作,就能找出这个字节中对象下标的那个比特位了。
/* Hashing function for the aHash representation.
Empirical testing showed that the *37 multiplier
(an arbitrary prime)in the hash function provided
no fewer collisions than the no-op *1. */
通过经验来看,乘以37或者乘以一个任意的素数所带来的冲突的次数可能并不比乘以1带来的哈希冲突的次数少,所以这里直接乘以1。
#define BITVEC_HASH(X) (((X)*1)%BITVEC_NINT)
/*
Check to see if the i-th bit is set. Return true or false.
If p is NULL (if the bitmap has not been created) or if
i is out of range, then return false.
*/
int sqlite3BitvecTestNotNull(Bitvec *p, u32 i){
assert( p!=0 );
i--;
if( i>=p->iSize ) return 0;//i超出了位图的范围(iSize),iSize表示的是位图中所有的比特位中下标最大的那个下标对应的索引
while( p->iDivisor ){//iDivisor>0说明位图被划分成了许多位图,每个位图中得大小都是iDivisor
u32 bin = i/p->iDivisor;//找到被划分之后的那个位图对应的下标(while循环相当于进行迭代处理)
i = i%p->iDivisor;//在这个位图内部,对应的是第几个比特位(这不是参照一个字节,而是参照划分之后的位图)
p = p->u.apSub[bin];//找到对应的那个划分之后的位图,更新指针
if (!p) {//如果指针为空,则返回0
return 0;
}
}
if( p->iSize<=BITVEC_NBIT ){//如果iSize小于BITVEC_NBIT,那么直接进行判断
return (p->u.aBitmap[i/BITVEC_SZELEM] & (1<<(i&(BITVEC_SZELEM-1))))!=0;//找到某个字节内部的某个比特位
} else{//如果iSize大于BITVEC_NBIT,那么说明这个位图是使用哈希表来存储的,而不是直接通过数组来储存(因为可能数据量大)
u32 h = BITVEC_HASH(i++);//
while( p->u.aHash[h] ){//在哈希表中的某个存储桶上查找对应的比特位
if( p->u.aHash[h]==i ) return 1;//如果找到了这个比特位
h = (h+1) % BITVEC_NINT;//h=h+1,是为了实现在这个存储桶中向后移动,因为哈希冲突无法避免,因此这种操作也是无法避免的。
}
return 0;
}
}
设置位图的比特位
/*
Set the i-th bit. Return 0 on success and an error code if
anything goes wrong.
This routine might cause sub-bitmaps to be allocated. Failing
to get the memory needed to hold the sub-bitmap is the only
that can go wrong with an insert, assuming p and i are valid.
The calling function must ensure that p is a valid Bitvec object
and that the value for "i" is within range of the Bitvec object.
Otherwise the behavior is undefined.
*/
在位图中设置第i个比特位。这个操作可能会导致分配子位图。只有一种情况会导致崩溃:当插入时,没有给子位图分配到其需要的内存空间。
调用这个方法时需要确定,p是有效的(正经的),i也是正经的。否则,行为将是难以预料的。
int sqlite3BitvecSet(Bitvec *p, u32 i){
u32 h;
if( p==0 ) return SQLITE_OK;
assert( i>0 );
assert( i<=p->iSize );
i--;
while((p->iSize > BITVEC_NBIT) && p->iDivisor) {//这采用的是union中的第三种表示形式:apSub[BITVEC_NPTR]
u32 bin = i/p->iDivisor;//找到对应的那个Bitvec对象
i = i%p->iDivisor;//找到相对于该Bitvec对象的某个比特位(并不完全是,因为可能这个我们认为是Bitvec对象的东西,可能是一个apSub[])
if( p->u.apSub[bin]==0 ){//如果这个Bitvec对象不存在
p->u.apSub[bin] = sqlite3BitvecCreate( p->iDivisor );//那么就创建这个Bitvec对象
if( p->u.apSub[bin]==0 ) return SQLITE_NOMEM_BKPT;//如果创建失败,返回SQLITE_NOMEN_BKPT
}
p = p->u.apSub[bin];//更新p指针
}
if( p->iSize<=BITVEC_NBIT ){//如果iSize<=BITVEC_NBIT,那么就是直接使用数组来表示位图对象
p->u.aBitmap[i/BITVEC_SZELEM] |= 1 << (i&(BITVEC_SZELEM-1));
return SQLITE_OK;
}
h = BITVEC_HASH(i++);//上面处理了两种情况,那么这是就剩下最后一种情况了:使用哈希表来表示的位图,严格意义上来说,并不是位图,因为在哈希表中使用一个u32类型的整数来表示一个页号。而不是使用一个比特位
/* if there wasn't a hash collision, and this doesn't */
/* completely fill the hash, then just add it without */
/* worring about sub-dividing and re-hashing. */
//如果没有哈希冲突(或者对应的这个bucket没有一个元素),那么直接添加即可
if( !p->u.aHash[h] ){
if (p->nSet<(BITVEC_NINT-1)) {//如果还不需要进行rehash操作
goto bitvec_set_end;
} else {
goto bitvec_set_rehash;//影响因子乘以capacity可以得到一个阈值threshould,当threshould大于指定值时,需要进行rehash操作。rehash操作可以减少哈希冲突的次数,提高哈希的命中率。
}
}
/* there was a collision, check to see if it's already */
/* in hash, if not, try to find a spot for it */
//如果发生了冲突,那么需要查看是否这个存储桶内部已经存在i了
do {
if( p->u.aHash[h]==i ) return SQLITE_OK;//如果查到了,则返回
h++;//如果没有,则移动到下一个元素
if( h>=BITVEC_NINT ) h = 0;//如果遍历到达了末尾,则从头开始循环,因为刚开始并没有从开头开始的
} while( p->u.aHash[h] );
/* we didn't find it in the hash. h points to the first */
/* available free spot. check to see if this is going to */
/* make our hash too "full". */
bitvec_set_rehash: //进行rehash操作
if( p->nSet>=BITVEC_MXHASH ){//如果负载过大
unsigned int j;
int rc;
u32 *aiValues = sqlite3StackAllocRaw(0, sizeof(p->u.aHash));
if( aiValues==0 ){
return SQLITE_NOMEM_BKPT;
}else{
memcpy(aiValues, p->u.aHash, sizeof(p->u.aHash));
memset(p->u.apSub, 0, sizeof(p->u.apSub));
p->iDivisor = (p->iSize + BITVEC_NPTR - 1)/BITVEC_NPTR;//iSize==BITVEC_NPTR时,iDiviros=1,当iSize==BITVEC_NPTR+1时,iDivisor=2,所以,这个设计也非常巧妙
rc = sqlite3BitvecSet(p, i);//首先对i进行设置
for(j=0; j<BITVEC_NINT; j++){//因为aiValues是使用memcpy宏定义从u.aHash拷贝过来的,所以现在看到的aiValues可以想象成之前的u.aHash
if( aiValues[j] ) rc |= sqlite3BitvecSet(p, aiValues[j]);//rc是需要返回的,只有rc等于0时,表示操作是成功的,所以,如果之前的哈希表中所有的值(i),都需要进行set操作,每个set操作都有一个返回值,0和0相或等于0,0和1相或等于1,所以只要出现一个1(或者只要set操作失败一次),那么rc就等于1,即从全局意义上来说,set操作并没有完全成功,rehash操作也没有做到尽善尽美
}
sqlite3StackFree(0, aiValues);
return rc;
}
}
bitvec_set_end:
p->nSet++;//nSet增加,表示哈希表中的元素的个数增加
p->u.aHash[h] = i;//哈希表中制定的位置存入值
return SQLITE_OK;
}
下面的是SQLite内部的拷贝函数,该函数被定义成了一个宏,第一个参数D是目的起始地址,第二个参数S是源起始地址。第三个参数N是拷贝的字节的个数。
# define memcpy(D,S,N) {char*xxd=(char*)(D);const char*xxs=(const char*)(S);\
int xxn=(N);while(xxn-->0)*(xxd++)=*(xxs++);}
清零比特位,一次调用就对一个比特位进行清零。
/*
** Clear the i-th bit.
**
** pBuf must be a pointer to at least BITVEC_SZ bytes of temporary storage
** that BitvecClear can use to rebuilt its hash table.
*/
void sqlite3BitvecClear(Bitvec *p, u32 i, void *pBuf){//i的下表是从1开始的
if( p==0 ) return;
assert( i>0 );
i--;//为了便于处理,当使用真正的比特位表示时,小标从0开始,即i=1时,其对应的bit位在aBitmap中的下表是0
while( p->iDivisor ){//如果被分了层(这里进行迭代查找,知道找到最后一层,因为一次只操作一个比特位,所以如果分层的话,这个比特位一定在最内层被找到,因为所有的外部的层的作用仅仅是索引,只有最里面的一层是真是表示数据的)
u32 bin = i/p->iDivisor;//在第几个Bitvec对象
i = i%p->iDivisor;//相对于一个Bitvec对象内部的偏移
p = p->u.apSub[bin];//指针往内部指
if (!p) {//如果指针为空
return;
}
}
if( p->iSize<=BITVEC_NBIT ){//这时,直接使用位图来表示
p->u.aBitmap[i/BITVEC_SZELEM] &= ~(1 << (i&(BITVEC_SZELEM-1)));//~(1 << (i&(BITVEC_SZELEM-1)))达到的目的是eg:11101111,或者11011111等等,在这个字节当中,只有一个比特位是0,其余的比特位都是1
}else{//还剩余一种表示方式,那就是使用hash表的方式,因为前面进行了i--操作,所以这里需要进行还原,即i+1
unsigned int j;
u32 *aiValues = pBuf;
memcpy(aiValues, p->u.aHash, sizeof(p->u.aHash));//一、先把哈希表中的内容保存到aiVlues当中
memset(p->u.aHash, 0, sizeof(p->u.aHash));//二、再对整个哈希表进行清零
p->nSet = 0;//三、重新对所有的原先哈希表中的内容进行复制,当然,i除外
for(j=0; j<BITVEC_NINT; j++){
if( aiValues[j] && aiValues[j]!=(i+1) ){//对于所有的可能(下表j),都需要查找对应的哈希表,使用条件aiValues[j]!=(i+1)是为了对i对应的在哈希表中的位置进行清零(清零才是最终的目的,其他的都是铺垫)
u32 h = BITVEC_HASH(aiValues[j]-1);//找到一个新元素对应的哈希表中的下标
p->nSet++;//每当设置一个值,就令nSet++
while( p->u.aHash[h] ){//如果之前就已经存在了,说明发生了哈希冲突,所以需要进行冲突处理,即h++(寻找下一个可用的空间)
h++;
if( h>=BITVEC_NINT ) h = 0;//如果走到最后了,就循环从0开始
}
p->u.aHash[h] = aiValues[j];//不管有没有冲突,这一步才是实质性操作
}
}
}
}