说明
- 解析基于算法库bsdiff-4.3.tar.gz。
- 算法文件组成是两个C文件:bsdiff.c(根据老版本和新版本生成补丁文件)和bspatch.c(根据老版本和补丁文件生成新版本)。
- 个人还未完全理解BSDiff算法,主要是后缀数组的实现未理解,暂时将后缀数组实现函数qsufsort和split当做黑盒,记录说明下其它操作。
解析
- 算法主要集中于bsdiff.c,bspatch.c生成新版本较为简单,只是对数据的使用,并没有复杂操作,但是查看代码时可用来对照判断bsdiff.c中写入数据的作用。
- 生成后缀数组
Line 230: qsufsort(I,V,old,oldsize);
- 调用qsufsort该函数生成后缀数组,old为老版本文件数据,oldsize为文件长度,V是辅助数组,使用完就free了,I保存了根据老版本文件数据生成的后缀数组,后缀数组就不解释,可在网上找后缀数组 罗穗骞的论文理解。
- 注意:I数组的大小是oldsize+1,I[0]的值为oldsize;可构造一些简单的数据对生成的I数组进行观察。
- 算法中实现后缀数组的算法,个人未理解,非常见的实现算法,是Jesper Larsson, Faster Suffix Sorting,虽然未理解,但是生成的结果是后缀数组,理解后缀数组的特性,将其当做黑盒,也能先理解后续操作。
- 循环处理新文件数据,找到代码操作导致二进制数据相差字节多于8bytes的偏移点
while(scan<newsize) {
for(scsc=scan+=len;scan<newsize;scan++) {
len=search(I,old,oldsize,new+scan,newsize-scan,
0,oldsize,&pos);
for(;scsc<scan+len;scsc++)
if((scsc+lastoffset<oldsize) &&
(old[scsc+lastoffset] == new[scsc]))
oldscore++;
if(((len==oldscore) && (len!=0)) ||
(len>oldscore+8)) break;
if((scan+lastoffset<oldsize) &&
(old[scan+lastoffset] == new[scan]))
oldscore--;
};
....
}
2.1 新版本文件和老版本文件都从数据开头开始,通过二分法,在整个后缀数组I中找到与新版本数据匹配最长的长度len和数组编号pos。
len=search(I,old,oldsize,new+scan,newsize-scan,
0,oldsize,&pos);
* search和matchlen函数略。
- 返回的数组编号即为在老版本文件中的偏移。
2.2 计算出当前偏移的old数据与new数据相同的字节个数,再与len比较。
for(;scsc<scan+len;scsc++)
if((scsc+lastoffset<oldsize) &&
(old[scsc+lastoffset] == new[scsc]))
oldscore++;
if(((len==oldscore) && (len!=0)) ||
(len>oldscore+8)) break;
2.2.1 如果相差小于8则继续for循环。
- 相差小于8,可以认为插入的数据较少,没必要切换old数据的offset,每切换一次就需要进行一次diff和extra处理。
2.2.2 如果相差大于8则退出for循环,下面则对这一段数据进行处理。
- 相差大于8,可以认为插入或改变的数据大于8字节,如下:
老版本程序:
printf("hello");
....
新版本程序:
printf("world");
....
- 更改位置的后续处理都一样,可以想象出编译出来的二进制数据也是相差不大的,只是由于插入新代码或者修改了部分代码,导致后续二进制数据在新老程序中的偏移不同。
- 退出for循环的条件就是找到这样的位置,少于8个字节的相差会被跳过,继续for循环。
- 对上一个位置到新位置之间的数据进行处理
while(scan<newsize) {
....
if((len!=oldscore) || (scan==newsize)) {
s=0;Sf=0;lenf=0;
for(i=0;(lastscan+i<scan)&&(lastpos+i<oldsize);) {
if(old[lastpos+i]==new[lastscan+i]) s++;
i++;
if(s*2-i>Sf*2-lenf) { Sf=s; lenf=i; };
};
,
lenb=0;
if(scan<newsize) {
s=0;Sb=0;
for(i=1;(scan>=lastscan+i)&&(pos>=i);i++) {
if(old[pos-i]==new[scan-i]) s++;
if(s*2-i>Sb*2-lenb) { Sb=s; lenb=i; };
};
};
if(lastscan+lenf>scan-lenb) {
overlap=(lastscan+lenf)-(scan-lenb);
s=0;Ss=0;lens=0;
for(i=0;i<overlap;i++) {
if(new[lastscan+lenf-overlap+i]==
old[lastpos+lenf-overlap+i]) s++;
if(new[scan-lenb+i]==
old[pos-lenb+i]) s--;
if(s>Ss) { Ss=s; lens=i+1; };
};
lenf+=lens-overlap;
lenb-=lens;
};
for(i=0;i<lenf;i++)
db[dblen+i]=new[lastscan+i]-old[lastpos+i];
for(i=0;i<(scan-lenb)-(lastscan+lenf);i++)
eb[eblen+i]=new[lastscan+lenf+i];
dblen+=lenf;
eblen+=(scan-lenb)-(lastscan+lenf);
offtout(lenf,buf);
BZ2_bzWrite(&bz2err, pfbz2, buf, 8);
if (bz2err != BZ_OK)
errx(1, "BZ2_bzWrite, bz2err = %d", bz2err);
offtout((scan-lenb)-(lastscan+lenf),buf);
BZ2_bzWrite(&bz2err, pfbz2, buf, 8);
if (bz2err != BZ_OK)
errx(1, "BZ2_bzWrite, bz2err = %d", bz2err);
offtout((pos-lenb)-(lastpos+lenf),buf);
BZ2_bzWrite(&bz2err, pfbz2, buf, 8);
if (bz2err != BZ_OK)
errx(1, "BZ2_bzWrite, bz2err = %d", bz2err);
lastscan=scan-lenb;
lastpos=pos-lenb;
lastoffset=pos-scan;
};
};
3.1 计算diff string的长度
s=0;Sf=0;lenf=0;
for(i=0;(lastscan+i<scan)&&(lastpos+i<oldsize);) {
if(old[lastpos+i]==new[lastscan+i]) s++;
i++;
if(s*2-i>Sf*2-lenf) { Sf=s; lenf=i; };
};
- 由于任务2中找的是差异较大的点,因此差异较大的部分就是该段数据的末尾数据,从头开始比较,通过以下判断可以近似找出类似的最长字符串。
if(s*2-i>Sf*2-lenf) { Sf=s; lenf=i; };
* Sf*2-lenf 和 s*2-i 都是等式: a*2 - b。
* Sf*2-lenf可以理解为 上一组s和i,s*2-i计算出的值。
* s*2 - i, i和s的增长步长都为1,也就是i走两步,s走一步就可以维持s*2 - i结果不变,
如果结果要增加,也就是s增加的频率要>50%,即后续增加的数据超过50%的数据需要是相等的。
3.2 获取与下一段数据近似相同数据的长度
lenb=0;
if(scan<newsize) {
//printf("lastscan:%d, pos:%d\n", lastscan, pos);
s=0;Sb=0;
for(i=1;(scan>=lastscan+i)&&(pos>=i);i++) {
if(old[pos-i]==new[scan-i]) s++;
if(s*2-i>Sb*2-lenb) { Sb=s; lenb=i; };
};
};
- 上面lenf的值即为diff string的长度,该段数据剩余部分即可认为是extra string,这样长度相减即可获得extra string的长度;但是保存的diff string是新老数据的相差值,可以更好的被压缩,而extra string保存的就是原始数据,压缩层度不高,为了减少extea string的长度,采取了将部分extra string与下一段数据近似相同的数据遗留下来,在下一段数据可以充当diff string。
- scan等于newsize,就没有下一段数据了,因此这里需要判断scan小于newsize。
3.3 上面两段数据可能重叠,处理重叠
if(lastscan+lenf>scan-lenb) {
overlap=(lastscan+lenf)-(scan-lenb);
s=0;Ss=0;lens=0;
for(i=0;i<overlap;i++) {
if(new[lastscan+lenf-overlap+i]==
old[lastpos+lenf-overlap+i]) s++;
if(new[scan-lenb+i]==
old[pos-lenb+i]) s--;
if(s>Ss) { Ss=s; lens=i+1; };
};
lenf+=lens-overlap;
lenb-=lens;
};
- 如果上面两段数据存在重叠,说明不存在extra string,重叠部分可以划分给当前diff string,也可以划分给下一段diff string,这里根据相似数据分布获取到lens值。
3.4 将diff string和extra string保存到内存中,再将长度数据使用bzip压缩写入文件
for(i=0;i<lenf;i++)
db[dblen+i]=new[lastscan+i]-old[lastpos+i]; //diff string
for(i=0;i<(scan-lenb)-(lastscan+lenf);i++)
eb[eblen+i]=new[lastscan+lenf+i]; //extra string
dblen+=lenf;
eblen+=(scan-lenb)-(lastscan+lenf);
//长度数据压缩写入文件
offtout(lenf,buf);
BZ2_bzWrite(&bz2err, pfbz2, buf, 8); //保存的是diff string 的长度
if (bz2err != BZ_OK)
errx(1, "BZ2_bzWrite, bz2err = %d", bz2err);
offtout((scan-lenb)-(lastscan+lenf),buf); //保存的是extern string 的长度
BZ2_bzWrite(&bz2err, pfbz2, buf, 8);
if (bz2err != BZ_OK)
errx(1, "BZ2_bzWrite, bz2err = %d", bz2err);
offtout((pos-lenb)-(lastpos+lenf),buf); //保存的是old程序中的新偏移pos相对于上一个偏移lastpos的相差
BZ2_bzWrite(&bz2err, pfbz2, buf, 8);
if (bz2err != BZ_OK)
errx(1, "BZ2_bzWrite, bz2err = %d", bz2err);
//保存偏移
lastscan=scan-lenb;
lastpos=pos-lenb;
lastoffset=pos-scan;
- lenf值即为diff string的长度;(scan-lenb)-(lastscan+lenf)即为extra string的长度。
- diff string保存的是相差值,可以很好被压缩。
- 循环结束后将内存中的diff string和extra string压缩写入文件。
说明
- diff string (上图的差异文件)和 extra string (上图的更新文件)如上图所示。
- bsdiff 算法循环中截取出的每段数据类似于上图中的数据,但是每段数据操作的新文件和老文件数据长度是一样的,和上图不一样。