接上篇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
[…] Android ListView 正在加载 异步载入数据 CursorLoader 例子 Android 号码,来电归属地 Jni 使用C++对二进制文件查询(二) C++实现篇 01 […]