SQLite3源码学习(23) 链表的归并排序

一般数组排序采用快速排序最佳,但是在链表中2个元素的交换不是很方便,所以采用归并排序比较好,先把链表分割成一些已经排好序的子链表,最后再串起来就好了。

SQLite中对于脏页链表按照页号重新排序采用的就是归并排序。

1.基本思路

      子链表存放在一个临时数组里,这个数组定义为

PgHdr *a[N_SORT_BUCKET]

子链表是按照2的幂次方的大小依次存放,也就是说a[0]存放1个元素,a[1]存放2个元素,a[2]存放4个元素,依次类推。

下面举个最简单的例子:

假设初始链表的顺序为23 10 22 21 20,排序步骤如下:

23先存放在a[0]:23

1023归并存放到a[1]:10 23

22存放到a[0]里:22

2122归并后本想存放到a[1],但是a[1]已经有值了,继续归并后存放到a[2]:10 21 22 23

20存放到a[0]:20

现在所有子链表已经划分完毕,将其全部归并就得到最后的结果:

10 20 21 22 23

2.代码实现

首先实现2个子链表的合并,result是合并后的链表,pTail指向result的最后一个结点,pDirty可以理解成pNext(即指向下一个结点)pApB2个已经排序好的链表的头结点,先比较pApB哪个页序比较小,把较小的节点从链表中摘除,添加到pTail->pDirty,再更新pTail即可。

/*
** Merge two lists of pages connected by pDirty and in pgno order.
** Do not bother fixing the pDirtyPrev pointers.
*/
static PgHdr *pcacheMergeDirtyList(PgHdr *pA, PgHdr *pB){
  PgHdr result, *pTail;
  pTail = &result;
  assert( pA!=0 && pB!=0 );
  //遍历2个输入子链表,然后按顺序归并
  for(;;){
    //当pA的页序比较小时
if( pA->pgno<pB->pgno ){
  //把pA添加到result的末尾
      pTail->pDirty = pA;
      pTail = pA;//更新result的尾结点
      pA = pA->pDirty;//把pA从输入链表中摘除
      if( pA==0 ){
        //因为pA和pB事先已经排好序了
        //遍历到pA的末尾之后,直接把pB插入的结果中即可
        pTail->pDirty = pB;
        break;
      }
}else{
  //当pB的页序比较小时,逻辑和上面相同
      pTail->pDirty = pB;
      pTail = pB;
      pB = pB->pDirty;
      if( pB==0 ){
        pTail->pDirty = pA;
        break;
      }
    }
  }
  return result.pDirty;
}

下面的代码就是把要排序的链表拆分成2次幂长度的子链表,最后归并。这里假定要排序的元素个数不超过2^31

#define N_SORT_BUCKET  32
static PgHdr *pcacheSortDirtyList(PgHdr *pIn){
  PgHdr *a[N_SORT_BUCKET], *p;
  int i;
  memset(a, 0, sizeof(a));
  //遍历输入链表的元素
  while( pIn ){
    //从pIn里摘下头结点
    p = pIn;
    pIn = p->pDirty;
    p->pDirty = 0;
    //遍历a[i],直到找到空的地址存放子链表
    for(i=0; ALWAYS(i<N_SORT_BUCKET-1); i++){
      //如果a[i]为空,那么把子链表保存到a[i]
      if( a[i]==0 ){
        a[i] = p;
        break;
      }else{
        //如果a[i]不为空,那么p与a[i]归并,再继续下一次循环
        p = pcacheMergeDirtyList(a[i], p);
        a[i] = 0;
      }
    }
    //下面这个条件是永远进不去的,这里假定元素最多2^31个
    if( NEVER(i==N_SORT_BUCKET-1) ){
      /* To get here, there need to be 2^(N_SORT_BUCKET) elements in
      ** the input list.  But that is impossible.
      */
      a[i] = pcacheMergeDirtyList(a[i], p);
    }
  }
  p = a[0];
  //子链表拆分完毕后,把所有的子链表再归并成一个链表
  for(i=1; i<N_SORT_BUCKET; i++){
    if( a[i]==0 ) continue;
    p = p ? pcacheMergeDirtyList(p, a[i]) : a[i];
  }
  return p;
}

脏页的原始链表是按照添加到链表的先后顺序排列的,用pDirtyNext连接,下面代码先拷贝一份原始链表,然后返回按照页序排列的链表,通过pDirty来连接。

/*
** Return a list of all dirty pages in the cache, sorted by page number.
*/
PgHdr *sqlite3PcacheDirtyList(PCache *pCache){
  PgHdr *p;
  for(p=pCache->pDirty; p; p=p->pDirtyNext){
    p->pDirty = p->pDirtyNext;
  }
  return pcacheSortDirtyList(pCache->pDirty);
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值