01.
项目中涉及到50亿+的数据去重操作,大概的情景就是一个超大文件(200G+),文件中的数据为每行是一个字符串,现在要求对字符串进行去重操作,重新生成文件
02.
显然内存不足于容纳文件的大小,即使容纳的话你会这么做么 :) 。一般而言对于这种超大文件去重都会采用外部排序将相同的数据对排在一起,然后再进行去重操作。如果你对 shell 命令熟悉的话,相信你会很快想到 sort | uniq
#!/usr/bin/env bash
MAX_LINES_PER_CHUNK=5000000
ORIGINAL_FILE=$1
SORTED_FILE=$2
CHUNK_FILE_PREFIX=$ORIGINAL_FILE.split.
SORTED_CHUNK_FILES=$CHUNK_FILE_PREFIX*.sorted
usage ()
{
echo Parallel sort
echo usage: psort file1 file2
echo Sorts text file file1 and stores the output in file2
echo Note: file1 will be split in chunks up to $MAX_LINES_PER_CHUNK lines
echo and each chunk will be sorted in parallel
}
# test if we have two arguments on the command line
if [ $# != 2 ]
then
usage
exit
fi
#Cleanup any lefover files
rm -f $SORTED_CHUNK_FILES > /dev/null
rm -f $CHUNK_FILE_PREFIX* > /dev/null
rm -f $SORTED_FILE
#Splitting $ORIGINAL_FILE into chunks ...
split -l $MAX_LINES_PER_CHUNK -a 4 $ORIGINAL_FILE $CHUNK_FILE_PREFIX
for file in $CHUNK_FILE_PREFIX*
do
sort $file > $file.sorted &
done
wait
#Merging chunks to $SORTED_FILE ...
sort -m $SORTED_CHUNK_FILES > $SORTED_FILE
#Cleanup any lefover files
rm -f $SORTED_CHUNK_FILES > /dev/null
rm -f $CHUNK_FILE_PREFIX* > /dev/null
以上是一个shell的排序脚本,可以对内存达到精准的大小控制。当然在排序完成之后,还需要对排好序的文件进行uniq 操作。如果你觉得麻烦,当然你也可以一键完成例如:
一个2000W行的数据对共610M 时间是1分4秒,用外部排序+去重操作
03.
问题:只需要去重,进行排序浪费了时间。有没有一种办法做到不排序而达到去重的办法。
04.
hash 方法。 从大文件中每行取出数据并对数据X 进行 hash(X) % N ,N 是需要hash到的文件数目,就达到了对相同的数据映射到同一个文件的办法,而在分配的过程中根据计算机的内存来调整N的大小,这样做的目的就是为了让内存读入小文件进行 set 操作。从而我们的算法时间复杂度就是o(n).验证下:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Describe:
"""
__author__ = "Tom"
SPLIT_COUNT = 10
def write_data(t_file, value):
t_file.write(value)
def calcu_hash(filename, handle_file):
with open(filename, 'r') as f:
for line in f:
write_data(handle_file[hash(line)%SPLIT_COUNT], line)
def generate_file(dir):
handle_file, files = [], []
for i in range(SPLIT_COUNT):
path = dir+"split_"+str(i)
files.append(path)
f = file(path , 'w')
handle_file.append(f)
return files, handle_file
def close_file(handle_file):
for i in range(len(handle_file)):
handle_file[i].close()
def data_uniq(files, new_file):
dataset = dict()
n_file = file(new_file, 'w')
for filename in files:
f = file(filename, 'r')
for line in f:
dataset[line] = 1
f.close()
for key in dataset.keys():
n_file.write(key)
dataset = {}
n_file.close()
if __name__ == "__main__":
filename = './big_1.csv'
generate_dir = './generate_data/'
new_file = './new_file'
files, handle_file = generate_file(generate_dir)
calcu_hash(filename, handle_file)
close_file(handle_file)
data_uniq(files, new_file)
代码如上,同样的数据文件
结果:27秒。
05.
经验证,两种方法的结果一样