Android 号码,来电归属地 Jni 使用C++对二进制文件查询(二) C++实现篇

接上篇Android 号码,来电归属地 Jni 使用C++对二进制文件查询(一)

 

1. 二进制文件第二版

 

通过上篇文章提到的压缩方式,我们得到了一个二进制的文件。格式如图1

 

// -------------------------------------------------------
	//  Name:         ChangeTxtToBinary
	//  Description:  Read every line in txt file, convert it to special customized format
	//                binary file.
	//                binary file content: count of total records, records, cities  
	//  Arguments:    txt file name, binary file name
	//  Return Value: true means success
	// -------------------------------------------------------
	bool ChangeTxtToBinary(const char* inFileName,const char* outFileName){
		FILE* inFile = 0;
		inFile = fopen(inFileName,"rb");
		if(inFile == 0)
			return false;
		FILE* outFile = 0;
		outFile = fopen(outFileName,"wb");
		if(outFile == 0)
			return false;

		int phoneInfoCompressCount = 0;
		//firstly, write the count of total phoneInfoCompress.
		fwrite(&phoneInfoCompressCount,sizeof(int),1,outFile);

		//secondly, write all the records
		WriteRecords(inFile, phoneInfoCompressCount, outFile);

		//thirdly, rewrite the phoneInfoCompressCount.
		fseek(outFile,0,SEEK_SET);
		fwrite(&phoneInfoCompressCount,sizeof(int),1,outFile);

		//last, write all the cities
		WriteCities(outFile);

		fclose(inFile);
		fclose(outFile);
		return true;
	}

 

 

2. 一些C语言中的文件操作函数解释

 

 

因为要考虑到放在Android中编译,就没有使用stl标准库,而是用C语言中的文件操作函数。如果不熟悉的话可以看下下面的例子。

FILE* inFile = 0;
inFile = fopen("test.txt","rb"); //使用fopen打开一个文件,rb表示以二进制方式读
if(inFile == 0)
	return false;

FILE* outFile = 0;
outFile = fopen("test.dat","wb");//使用fopen打开一个文件,wb表示以二进制方式写入
if(outFile == 0)
	return false;

int num = 0;
fwrite(&num,sizeof(int),1,outFile);//使用fwrite往文件中写入数据,这是一个写入int的例子

int count = 0;
fread(&count,sizeof(int),1,outFile);//使用fread读取文件中的值,这是一个读int的例子

int number;
char* firstCityName = new char[256];
fscanf(inFile,"%d,%s",&number,firstCityName);//使用fscanf读取文件中的一行,分别存到两个变量中

fseek(outFile,0,SEEK_SET);//使用fseek来改变读写位置,SEEK_SET表示文件开始处。SEEK_END表示文件尾部
                          //比如,我想跳到文件头部写了一个int,想直接跳到文件尾部写其他
						  //内容,就可以这样使用
						  //fseek(outFile,0,SEEK_SET);
						  //fwrite...
						  //fseek(outFile,0,SEEK_END);
						  //fwrite...

fclose(inFile); //使用fclose来关闭读取文件
fclose(outFile);//使用fclose来关闭写入文件

 

3.对文件进行二分查找

下面是最简单的二分查找。

/** 
* search n in a[], return the index, if not find, return -1. 
*/  
template <class T>  
int BSearch(T a[],const int& length,const int& n){  
    int left = 0, right = length - 1;  
    while(left <= right){  
        int middle = (left + right) / 2;  
        if(n < a[middle]){  
            right = middle - 1;  
        }else if(n > a[middle]){  
            left = middle + 1;  
        }else{  
            return middle;  
        }  
    }  
    return -1;  
}

我们这次要对文件进行二分查找,还好我们存的每条压缩记录大小都是一样的。假设我们要得到中间的值,我们就可以用fseek到中间记录,再用fread,读取到记录。

NumberInfoCompress infoMiddle;

int middle = (left + right) / 2;

fseek(file,sizeof(int) + middle * sizeof(NumberInfoCompress),SEEK_SET);

fread(&infoMiddle,sizeof(NumberInfoCompress),1,file);

原来的二分查找是比中间值小就去前半部分搜索,如果比中间值大就去后半部分搜索。这次我们记录的是一个区间,就是如果查找值比区间最小部分还要小的话,去前半部分搜索,如果查找值比区间最大值还要大的话,去后半部分搜索。其他情况就表示找到记录了。

 

// -------------------------------------------------------
	//  Name:         GetCityNameByNumber
	//  Description:  input the phone number, find the city in the binary file 
	//  Arguments:    bFileName:the binary file name,number: the phone number
	//  Return Value: city name,not find return ""
	// -------------------------------------------------------
	char* GetCityNameByNumber(const char* bFileName,const int& number){
        FILE* file = 0;
		file = fopen(bFileName,"rb");
		if(file == 0)
			return (char*)"";

		int phoneInfoCompressCount = 0;
		//get total phoneInfoCompress count
		fread(&phoneInfoCompressCount,sizeof(int),1,file);

		int left = 0, right = phoneInfoCompressCount - 1;
		NumberInfoCompress infoMiddle;
		//begin binary search
		while(left <= right){
			int middle = (left + right) / 2;
			//put the write point in the  middle phoneInfoCompress 
			fseek(file,sizeof(int) + middle * sizeof(NumberInfoCompress),SEEK_SET);		
			fread(&infoMiddle,sizeof(NumberInfoCompress),1,file);

			if(number < infoMiddle.getBegin()){
				right = middle - 1;
			}else if(number > (infoMiddle.getBegin() + infoMiddle.getSkip())){
				left = middle + 1;
			}else{// find the result
				return DoFindResultThing(file, phoneInfoCompressCount, infoMiddle);
			}
		}
        fclose(file);
		return (char*)"";
	}
char* DoFindResultThing( FILE* file,const int& phoneInfoCompressCount,const NumberInfoCompress &infoMiddle ) 
	{
		//put the read point at the beginning of the cities
		fseek(file,sizeof(int) + phoneInfoCompressCount*sizeof(NumberInfoCompress),SEEK_SET);
		int length = 0;
		unsigned short searchIndex = 0;
		//read every city
		while(!feof(file)){
			fread(&length,sizeof(int),1,file);
			char* location = new char[length];
			fread(location,length,1,file);
			if(searchIndex == infoMiddle.getCityIndex()){//find the city string
				fclose(file);
				return location;
			}else{//keep reading the file
               ++searchIndex;
			}
		}
		return (char*)"";
	}

 

4提高查询速度

 

查看上面的二分查找,速度其实非常快了,但是查询城市的时候却用了类似链表的方式,如果查询第400个城市,要从第0个城市开始读,一个个读,直到读到第400个城市。有没有什么快的方式?把所有城市按照一样的长度存储!这样我们就可以像在数组中查询城市一样,查询任何城市都只需要一次。可以把所有城市按照最长的城市长度存储。查了下最长的是”湖北江汉(天门/仙桃/潜江)”,加上”\0″是25。

 

 

所有城市都按照最长城市存储可能会增大存储空间。试了下,文件从417KB涨到422KB,完全可以接受。就是后面如果有更长的城市名,记得要修改这个25魔鬼数字。

 

 

5.效果图

 

 

C++转换txt到二进制文件和查询都已经处理完毕,可以下载这个项目查看,用的是vs2005。下一步就是通过JNI整合到Android中。

相关文章:
 Android 号码,来电归属地 Jni 使用C++对二进制文件查询(一) 理论篇

Android 号码,来电归属地 Jni 使用C++对二进制文件查询(二) C++实现篇

 Android 号码,来电归属地 Jni 使用C++对二进制文件查询(三) APK 实现篇

项目下载:

http://www.waitingfy.com/wp-content/uploads/2013/06/callhelper.rar(本地下载)

http://download.csdn.net/detail/fox64194167/5467009(CSDN下载,免积分)

 

541

One Response to Android 号码,来电归属地 Jni 使用C++对二进制文件查询(二) C++实现篇

  1. […] Android ListView 正在加载 异步载入数据 CursorLoader 例子 Android 号码,来电归属地 Jni 使用C++对二进制文件查询(二) C++实现篇 01 […]

Leave a Reply

Name and Email Address are required fields.
Your email will not be published or shared with third parties.