这些思路网上都有,就是自己实践一下。ps:昨晚写了40多分钟的东西,本来想发的,后来崩溃了...> . < , 以后在也不能在网页上直接写了
/*
* =====================================================================================
*
* Filename: find_the_same.c
*
* Description: 查找两个文件中相同的用户名(下面的用户名是用26个小写字母随即组成的)
* 主要的思路:
* 1) 两个文件都是billions级的数字 1000,000,000 * 28(字符串结束赋和分割符) bytes
* 26G 的大小,这是没有办法一次性导入并做比较的。
* 所以第一步就是把大的文件 分割成 小的文件,没个小的文件都是由一个标志符决定,
* 为了能确定 a 和 b相同的用户名一定存在同个等级的小文件,因为这样就不需要不同级别
* 的小文件在进行比较
* 2) 为了完成第1)步后面的要求,要构建一个哈希函数,同个username在 同个哈希函数的映射下
* 并定存在同个等级的小文件
* 因此,第二步,就是确定 分割大文件的哈希函数,我这里这是简单的进行一些四则运算,只要
* 能把大文件打散就行了
* 3) 在前面两步的基础上,
* 就是进行小文件之间的比较,这里我用的是哈希查找:
* 1)先导入第一个小文件的所有username, 并建立一个哈希链表
* 2)再从第二个小文件逐步取出一定的username,再哈希表中进行查找,如果存在的话就把结果
* 取出来,这里是直接输出到stdout上
* 注意:这里哈希表的函数必须和上面的不同
*
* 以上就是我用于解决这道题的思路
* Version: 1.0
* Created: 08/18/2012 12:27:34 AM
* Revision: none
* Compiler: gcc
*
* Author: lsff (刘世锋)
* Organization:
*
* =====================================================================================
*/
// 代码运行:
// 我的测试是通过生成 a.dat 和 b.dat 两个大文件,让后在运行的目录下先家里a 、b 两个目录,用于存放小文件的
// 所以如果 有测试数据的, 就把 数据 命名位 a.dat 和 b.dat , 然后把源程序可执行文件放到你文件的目录下运行
// 第一次运行把#undef DIVIVE 注释掉,第二次运行是就可以取消注释
//
// 如果没有测试数据,就把 #undef WRITE 注释掉,然后就会自动生成数据,数据的到小可以在 注释掉,第二次运行是就可以取消注释
//
// 如果没有测试数据,就把 #undef WRITE 注释掉,然后就会自动生成数据,数据的到小可以在 testData()函数里面修改
// 注意!!! 生成数据的时间会比较长
//
//
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#define WTESTBUF 1000 //写测试文件时候的缓冲大小
#define READBUF 1000 //读大文件时候的读取缓冲区大小
#define STYPES 1000 //分割文件哈希函数生成的数值类别数
#define SIZE 100 //分割文件是每个小文件待写入前的缓冲区
#define LINKSIZE 1000 // 哈希表的长度,也就是hash2 哈希函数产生的值范围
#define WRITE //写测试数据
#undef WRITE // 已写测试数据 !!!!如果还没有生成测试数据,就注释掉这一行
#define DIVIVE// dive 文件
#undef DIVIVE //已dive 大文件 !!!!如果还没有分割大文件,就注释掉这一行
// 哈希函数(用于分割文件的)
// usr_name 长度不超过27
// return : 0~999 之间
unsigned short hash( char * usr_name)
{
unsigned short sum=0;
int i=0;
for(;i<strlen(usr_name);i++)
sum += i*usr_name[i];
return sum%1000;
}
// 哈希函数(用于建立查找链表的)
// return : 0~999 之间
unsigned short hash2( char *usr_name)
{
unsigned short sum=0;
int i=0;
for(;i<strlen(usr_name);i++)
sum += i*i*usr_name[i];
return sum%1000;
}
// 存储用户名的结构体
// 可在里面加成员进行扩展
typedef struct
{
char name[28];
}usr;
// 哈希表的节点
typedef struct lnote
{
usr value;
struct lnote* next;
}lnote;
lnote* hash_link[LINKSIZE]={};// 哈希链表,用于查找两个10m大小级别文件中相同的usr
// 在哈希表中插入某个节点
void add_lnote( unsigned short index, usr adder)
{
if( NULL == hash_link[index])
{
hash_link[index]= malloc(sizeof(lnote));
bzero(hash_link[index], sizeof(lnote));
hash_link[index]-> value = adder;
hash_link[index]-> next = NULL;
return ;
}
lnote* tmp = hash_link[index], *pre;
while( tmp!=NULL)
{
if( 0==strcmp( tmp->value.name, adder.name)) return ;
pre=tmp;
tmp=tmp->next;
}
pre->next = malloc( sizeof(lnote) );
bzero(pre->next,sizeof(lnote));
(pre->next)->value =adder;
(pre->next)->next=NULL;
}
//第二个文件查找usrname是否已存在
//存在 返回0 ,并删除该链表项,减少下次查找次数
//不存在 返回-1
int find_lnote( int index ,usr finder)
{
if(NULL==hash_link[index]) return -1;
lnote* pre=hash_link[index], *cur=hash_link[index];
while(1)
{
if(0==strcmp( cur->value.name, finder.name))
{
if(cur == hash_link[index]) hash_link[index]=cur->next;
else pre->next= cur->next;
return 0;
}
pre=cur; cur=cur->next;
if( cur==NULL) break;
}
return -1;
}
// 删除哈希表申请的堆空间
void delete_link()
{
int i=0;
lnote *pre, *tmp;
for(;i<LINKSIZE;++i)
{
pre=tmp=hash_link[i];
while(tmp!=NULL) { pre=tmp->next; free(tmp); tmp=pre; }
}
}
// 这个函数是用于生成测试数据用的
// fname: 存发生成的数据的文件名
// sand: 随机种子
void testData(const char *fname, int sand)
{
usr *buf = calloc(WTESTBUF, sizeof(usr));
FILE* file = fopen(fname, "a+");
int i=0, num=0x00ffffff; //先测试小的
if( NULL==buf) { puts("calloc error!"); return ;}
if( NULL==fname ) {puts("file name error!");return;}
if( NULL==file ) {puts("open file error!"); return ;}
srand(sand);
int j;
for( ;i<num;i++)
{
if(0==(i+1)%WTESTBUF ) // 生成 WTESTBUF个用户名在输入文件,减少IO操作时间
{
if(fwrite(buf,sizeof(usr),WTESTBUF,file)!=WTESTBUF) { puts("generate testData error!"); return ; }
bzero(buf,WTESTBUF*sizeof(usr));
}
for(j=0;j<26;j++) buf[i%WTESTBUF].name[j]= 'a'+ rand()%26;
buf[i%WTESTBUF].name[26]='\0'; buf[i%WTESTBUF].name[27]='\n';
}
if( num%WTESTBUF != 0 && fwrite(buf, sizeof(usr), num%WTESTBUF,file)!= num%WTESTBUF) //剩余缓冲的继续写入
{
puts("Last generate testData error!");
return ;
}
free(buf); buf=NULL;
fclose(file);file=NULL;
printf("Write successfully! %d \n", num);
}
// 把特定的用户名写入小文件中
void write_sort_file(char *filename, usr* buf, size_t t)
{
if( NULL == buf ) { puts("write sort file error!"); return ;}
if( NULL == filename) { puts("file name error!");return ;}
FILE *file = fopen(filename, "a+");
if( NULL == file) {puts(filename); puts("open sort file error!");return ;}
if( t>0 )
if( fwrite( buf, sizeof(usr), t, file)!=t)
puts("write sort file error!");
fclose(file);
}
// filename: 待分割的大文件
// folder: 分割之后的小文件存储的目录
void sort_by_hash(const char* file_name, char *folder)
{
usr *rbuf = calloc(READBUF, sizeof(usr)); // 用于从大文件中接入的用于名缓冲区,一次缓存的大小是READBUF(1000)个用户
usr *sortbufs[STYPES]; // 存储分割小文件的数组,元素是一个指向usr的指针,可以存储一串待存入小文件的特定数据
char *sort_names[STYPES]; // 存储每个小文件特定的文件名
size_t cur_sizes[STYPES]; // 记录当前各个小文件缓冲区中存储的用户个数,达到SIZE个数就输入各自的小文件
if( NULL==rbuf) { puts("calloc error!"); return ;}
FILE* file = fopen(file_name, "r");
int i=0,tmpSize, hasNum;
if( NULL==file_name ) {
printf("%s %d", __FUNCTION__, __LINE__);
puts("file name error!"); free(rbuf);return;}
if( NULL==file ) {
printf("%s %d\n", __FUNCTION__, __LINE__);
puts("open file error!"); free(rbuf); return ;}
bzero(sort_names, sizeof(sort_names));
bzero(cur_sizes, sizeof(cur_sizes));
bzero(rbuf, sizeof(usr)*READBUF);
for(i=0;i<STYPES;i++){
sort_names[i]= malloc(sizeof(char)*15);
sprintf(sort_names[i],"%s/%s%d",folder,file_name, i);
sortbufs[i] = calloc(SIZE, sizeof(usr));
}
while((tmpSize= fread( rbuf, sizeof(usr), READBUF, file)) >0)
{
for(i=0;i<tmpSize;i++)
{
hasNum = hash( rbuf[i].name);
memcpy( &(sortbufs[hasNum][cur_sizes[hasNum]]), &rbuf[i], sizeof(usr));
++cur_sizes[hasNum];
if(SIZE==cur_sizes[hasNum]) // 同样也是减少IO 的操作时间
{
write_sort_file(sort_names[hasNum], sortbufs[hasNum], cur_sizes[hasNum]);
cur_sizes[hasNum]=0;
bzero(sortbufs[hasNum], sizeof(usr)*SIZE);
}
}
bzero(rbuf,READBUF*sizeof(usr));
}
for(i=0;i<STYPES; i++)
{
if(!cur_sizes[i]) continue;
write_sort_file(sort_names[i], sortbufs[i],cur_sizes[i]);
}
for(i=0;i<STYPES; i++)
{
free (sort_names[i]);
free (sortbufs[i]);
}
fclose(file);
free(rbuf);
file=NULL; rbuf=NULL;
}
// 根据filename 和 i 组合的小文件读入的数据建立哈希表
// 这是我上面生成小文件的规则,就是源文件名加上哈希值
void create_hash_link(char *filename, int i)
{
delete_link(); //如果是多对小文件进行匹配,就需要把前一对的生成的哈希表释放
bzero(hash_link,sizeof(hash_link));
if(!(filename!=NULL && i>=0 &&i<=999)){
printf("%s %d filename error!", __FUNCTION__, __LINE__);
return ;
}
char str[30];
sprintf(str,"%s%d", filename, i);
FILE* file = fopen(str, "r");
if( NULL==file ) {
printf("%s %d open file error!", __FUNCTION__, __LINE__);
return ;
}
usr users[200];
size_t t=0,j=0;
unsigned short hashNum=0;
while( (t=fread(users, sizeof(usr), 200, file))>0)
for(j=0;j<t;++j)
{
hashNum = hash2(users[j].name);
add_lnote(hashNum, users[j]);
}
fclose(file);
file=NULL;
}
// 在导入前一个文件后并且建立了一个哈希表之后
// 用第二个同位文件(分割文件时候哈希码相同)的
// 名字在哈希表上进行查找
void find_in_link(char *filename ,int i)
{
if(!(filename!=NULL && i>=0 &&i<=999)){
printf("%s %d filename error!", __FUNCTION__, __LINE__);
return ;
}
char str[30];
sprintf(str,"%s%d", filename, i);
FILE* file = fopen(str, "r");
if( NULL==file ) {
printf("%s %d open file error!", __FUNCTION__, __LINE__);
return ;
}
usr users[200];
size_t t=0,j=0;
unsigned short hashNum=0;
while( (t=fread(users, sizeof(usr), 200, file))>0)
for(j=0;j<t;++j)
{
hashNum = hash2(users[j].name);
if(!find_lnote( hashNum, users[j]))
printf("%s\n", users[j].name);
}
fclose(file);
file=NULL;
}
int main()
{
#ifdef WRITE
testData("a.dat",123);
testData("b.dat",321);
#endif
#ifdef DIVIVE
sort_by_hash("a.dat","a");
sort_by_hash("b.dat","b");
#endif
bzero(hash_link, sizeof(hash_link));
int i=0;
for(i=0;i<STYPES;i++)
{
create_hash_link("a/a.dat",i);
printf("The same name between a.dat%d and b.dat%d:\n", i,i);
find_in_link("b/b.dat",i);
}
delete_link();
return 0;
}