两个50亿url文件找出共同的url

海量数据处理面试题集锦

两个50亿url文件找出共同的url
给A,B两个文件,各存放50亿条URL,每条URL占用64个字节,内存限制为4G,找出A,B中相同的URL。

方案1:
可以估计每个文件安的大小为50G×64=320G,远远大于内存限制的4G。所以不可能将其完全加载到内存中处理。考虑采取分而治之的方法。

遍历文件a,对每个url求取,然后根据所取得的值将url分别存储到1000个小文件(记为,这里漏写个了a1)中。这样每个小文件的大约为300M。
遍历文件b,采取和a相同的方式将url分别存储到1000小文件中(记为)。这样处理后,所有可能相同的url都在对应的小文件()中,不对应的小文件不可能有相同的url。然后我们只要求出1000对小文件中相同的url即可。
求每对小文件中相同的url时,可以把其中一个小文件的url存储到hash_set中。然后遍历另一个小文件的每个url,看其是否在刚才构建的hash_set中,如果是,那么就是共同的url,存到文件里面就可以了。
方案2:
如果允许有一定的错误率,可以使用Bloom filter,4G内存大概可以表示340亿bit。将其中一个文件中的url使用Bloom filter映射为这340亿bit,然后挨个读取另外一个文件的url,检查是否与Bloom filter,如果是,那么该url应该是共同的url(注意会有一定的错误率)。

hash后要判断每个文件大小,如果hash分的不均衡有文件较大,还应继续hash分文件,换个hash算法第二次再分较大的文件,一直分到没有较大的文件为止。这样文件标号可以用A1-2表示(第一次hash编号为1,文件较大所以参加第二次hash,编号为2)
由于1存在,第一次hash如果有大文件,不能用直接set的方法。建议对每个文件都先用字符串自然顺序排序,然后具有相同hash编号的(如都是1-3,而不能a编号是1,b编号是1-1和1-2),可以直接从头到尾比较一遍。对于层级不一致的,如a1,b有1-1,1-2-1,1-2-2,层级浅的要和层级深的每个文件都比较一次,才能确认每个相同的uri。

以下是对上述算法的实现,

Step 1 产生50亿 URL
实际操作中,自定义N值大小即可,量力(硬盘)而行。这里没有产生50亿URL。

//[a,b]
#define random(a,b) ((rand()%(b-a+1))+a)
#define N 500000
string url  = "-0123456789abcdefghijklmnopqrstuvwxyz";
void generateUrl(string file)
{
	ofstream out(file);
	int n = 0;
	if (out.is_open()){
		for (int i = 0; i < N; ++i){
			int size = random(1,64);//64bit
			string s = "https://www.";
			for (int j = 0, l = 1; j < size; ++j){
				s += url[random(l,36)];//1+10+26-1=36
				l = (s[s.size()-1] == '-' || j >= size-1) ? 1: 0;
			}
			s+=".com/";
			out << s <<endl;
		}
		out.close();
	}
}

Step 2 将50亿URL大文件哈希为10000个小文件
具体牵涉到不少其他函数,下文将给出。

bool split_big_file(string file_name, string suffix, string store_path, unsigned long count_to_split)
{
	if (!file_name.size())
		return false;
	ifstream in(file_name);
	if (!in.is_open())
		return false;
	string line;
	while (getline(in, line)){
		string split_file_name = store_path;
		split_file_name += to_string(bkdr_hash(line.c_str()) % count_to_split);
		split_file_name += suffix;
		ofstream out(split_file_name, ios::app);
		if (!out.is_open()){
			in.close();
			return false;
		}
		out << line << endl;
		out.close();
	}
	in.close();
	return true;
}

Step 2.1 字符串哈希函数BKDRHash
更多的哈希函数,可以参阅上文中的链接。

unsigned long bkdr_hash(const char* str)
{
	unsigned int seed = 131;
	unsigned int hash = 0;
	while (*str){
		hash = hash*seed+(*str++);
	}
	return (hash & 0x7FFFFFFF);
}

Step 2.2 获取文件大小
获取文件大小的主要作用是——其一,使程序更具有鲁棒性,可以适应于任意大小的文件拆分,保证拆分后的小文件不超过指定内存大小。其二,判断拆分后的文件是否满足要求。

unsigned long get_file_size(string file_name)
{
if (!file_name.size())
return 0;
struct stat file_info;
return stat(file_name.c_str(),&file_info) ? 0 : file_info.st_size;
}

Step 2.3 获取某一目录下指定后缀的所有文件
这里仿照JAVA写了个endsWith函数,用于过滤后缀。

bool str_ends_with(string s, string sub)
{
	return s.rfind(sub)==(s.length()-sub.length());
}

vector<string> get_folder_file_name_list(string folder, string ends_with)
{
	struct dirent *ptr = NULL;
    DIR *dir = opendir(folder.c_str());
    vector<string> files_name;
    while ((ptr=readdir(dir))!=NULL){
        if (ptr->d_name[0] == '.')
            continue;
		if (str_ends_with(ptr->d_name, ends_with))
			files_name.push_back(ptr->d_name);
    }
    closedir(dir);
    return files_name;
}

Step 3 使用set将小文件进行求交操作,最终得到相同URL

bool write_same_url_to_file(string folder, string same_url_file_name)
{
	vector<string> files_name_a = get_folder_file_name_list(folder, ".a.txt");
	vector<string> files_name_b = get_folder_file_name_list(folder, ".b.txt");
	vector<string>::iterator iter;
	ofstream out(same_url_file_name, ios::app);
	if (!out.is_open())
		return false;
	for (int i = 0; i < files_name_a.size(); ++i){
		string s = files_name_a[i];
		s[s.size()-5] = 'b';
		if (get_file_size(files_name_a[i]) <= PER_FILE_SIZE \
				&& (iter = find(files_name_b.begin(), files_name_b.end(),s))\
			   	!= files_name_b.end()\
				&& get_file_size(*iter) <= PER_FILE_SIZE){
			set<string> a_set = get_file_hash_set(folder+files_name_a[i]);
			set<string> b_set = get_file_hash_set(folder+*iter);
			set<string> same_url_set = get_same_url_set(a_set, b_set);
			set<string>::iterator it = same_url_set.begin();
			for (; it != same_url_set.end(); ++it){
				out << *it << endl;
				cout<<*it<<endl;
			}
		}
	}
	out.close();
	return true;
}

可运行的程序获取地址:https://github.com/qingdujun/algorithm/tree/master/others/50billion-url

©qingdujun 2018-9-3 于 北京 海淀

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值