接上篇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中。
http://www.waitingfy.com/?p=541
相关文章:
Android 号码, 来电归属地 Jni 使用C++对二进制文件查询(一) 理论篇
Android 号码, 来电归属地 Jni 使用C++对二进制文件查询(二) C++实现篇
Android 号码, 来电归属地 Jni 使用C++对二进制文件查询(三) APK 实现篇
项目下载: