Bsdiff差分算法讲解

https://blog.csdn.net/qazw9600/article/details/10811161

说明

  • 解析基于算法库bsdiff-4.3.tar.gz。
  • 算法文件组成是两个C文件:bsdiff.c(根据老版本和新版本生成补丁文件)和bspatch.c(根据老版本和补丁文件生成新版本)。
  • 个人还未完全理解BSDiff算法,主要是后缀数组的实现未理解,暂时将后缀数组实现函数qsufsort和split当做黑盒,记录说明下其它操作。

解析

  • 算法主要集中于bsdiff.c,bspatch.c生成新版本较为简单,只是对数据的使用,并没有复杂操作,但是查看代码时可用来对照判断bsdiff.c中写入数据的作用。
  1. 生成后缀数组
Line 230: qsufsort(I,V,old,oldsize);
  • 1
  • 调用qsufsort该函数生成后缀数组,old为老版本文件数据,oldsize为文件长度,V是辅助数组,使用完就free了,I保存了根据老版本文件数据生成的后缀数组,后缀数组就不解释,可在网上找后缀数组 罗穗骞的论文理解。
  • 注意:I数组的大小是oldsize+1,I[0]的值为oldsize;可构造一些简单的数据对生成的I数组进行观察。
  • 算法中实现后缀数组的算法,个人未理解,非常见的实现算法,是Jesper Larsson, Faster Suffix Sorting,虽然未理解,但是生成的结果是后缀数组,理解后缀数组的特性,将其当做黑盒,也能先理解后续操作。
  1. 循环处理新文件数据,找到代码操作导致二进制数据相差字节多于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循环。
  1. 对上一个位置到新位置之间的数据进行处理
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保存的是相差值,可以很好被压缩。
  1. 循环结束后将内存中的diff string和extra string压缩写入文件。

说明

  • diff string (上图的差异文件)和 extra string (上图的更新文件)如上图所示。
  • bsdiff 算法循环中截取出的每段数据类似于上图中的数据,但是每段数据操作的新文件和老文件数据长度是一样的,和上图不一样。
  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
bsdiff 算法是一种用于生成两个二进制文件差异的算法,常用于软件更新中。它的实现基于三个步骤:生成差异文件、合并差异文件和生成新文件。 1. 生成差异文件 生成差异文件的过程是将旧版本文件和新版本文件进行对比,找出两个文件之间的差异,并将差异写入到一个新的文件中,这个新文件就是差异文件。bsdiff 算法生成差异文件的方法是将旧版本文件分成若干个块,然后对每个块进行编码,得到一个编码表。然后将新版本文件分成同样大小的块,对每个块进行哈希匹配,找到与旧版本文件中相同内容的块,并在编码表中查找相应的编码。如果找到了,则将编码写入到差异文件中;如果没有找到,则将原始块写入到差异文件中。 2. 合并差异文件 合并差异文件的过程是将旧版本文件和差异文件进行合并,得到新版本文件。bsdiff 算法合并差异文件的方法是将差异文件分成若干个块,然后对每个块进行解码,得到一个解码表。然后将旧版本文件分成同样大小的块,对每个块进行哈希匹配,找到与差异文件中相同内容的块,并在解码表中查找相应的解码。如果找到了,则将解码得到的新块写入到新版本文件中;如果没有找到,则将原始块写入到新版本文件中。 3. 生成新文件 生成新文件的过程是将合并后的文件与旧版本文件进行比较,确保新文件与旧文件一致。bsdiff 算法生成新文件的方法是对新版本文件和旧版本文件进行哈希匹配,确保两个文件的哈希值相同。如果哈希值相同,则说明新文件与旧文件一致;如果哈希值不同,则说明生成新文件出现了错误。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值