我有以下数据:
number1
I am writing line1 .
number2
First line .
number3
I am writing line2.
number4
Second line .
number5
I am writing line3 .
number6
Third line.
number7
I am writing line2 .
number8
Fourth line .
number9
I am writing line5 .
number10
Fifth line .
现在我想从这个文本文件中删除重复的行 - 与此同时我要删除重复行的前1行和后续2行。这样,删除后我的数据如下:
number1
I am writing line1 .
number2
First line .
number3
I am writing line2.
number4
Second line .
number5
I am writing line3 .
number6
Third line.
number9
I am writing line5 .
number10
Fifth line .
我的文件大小为60 GB,我使用的是64 GB RAM的服务器。我使用以下代码删除重复项:
fOutput = open('myfile','w')
table_size = 2**16
seen = [False]*table_size
infile = open('test.ttl', 'r')
while True:
inFileLine1=infile.readline()
if not inFileLine1:
break #EOF
inFileLine2=infile.readline()
inFileLine3=infile.readline()
inFileLine4=infile.readline()
h = hash(inFileLine2) % table_size
if seen[h]:
dup = False
with open('test.ttl','r') as f:
for line1 in f:
if inFileLine2 == line1:
dup = True
break
if not dup:
fOutput.write(inFileLine1)
fOutput.write(inFileLine2)
fOutput.write(inFileLine3)
fOutput.write(inFileLine4)
else:
seen[h] = True
fOutput.write(inFileLine1)
fOutput.write(inFileLine2)
fOutput.write(inFileLine3)
fOutput.write(inFileLine4)
fOutput.close()
然而,事实证明这段代码非常慢。我是否可以通过某种方式使用并行化来提高代码的效率,即使用我系统上可用的所有24个内核或使用任何其他技术。
虽然上面的代码是用python编写的 - 但我可以使用c ++或python或Java或使用linux命令的高效解决方案
这里test.ttl是我的输入文件,大小为60GB
答案 0 :(得分:2)
您的代码似乎只读取每一行,并且每行写入(需要写入)也只需要一次。因此,无法在文件读取 - 写入部分上优化算法。
我强烈怀疑你的代码很慢,因为哈希表的使用非常糟糕。您的哈希表只有2 ^ 16的大小,而您的文件可能包含大约2 ^ 28行,假设每行平均240个字节。
由于你有这么大的RAM(足以包含所有文件),我建议你将哈希表改为2 ^ 30的大小。这应该有很大帮助。
编辑: 在这种情况下,您可以尝试使用一些非常简单的Hash函数。例如:
long long weight[] = {generate some random numbers};
long long Hash(char * s, int length)
{
long long result = 0;
int i = 0, j = 0;
while (i < length)
{
result += s[i] * weight[j ++];
i += j;
}
return result & ((1 << 30) - 1); // assume that your hash table has size 2^30
}
答案 1 :(得分:2)
如果重复的行非常常见,那么我认为解决问题的正确方法与您拥有的方法类似,但您必须使用可以按需增长并将自动处理冲突的哈希表。尝试使用Python set
数据类型来存储已到达的行。使用set
,您无需确认重复的行确实是重复的;如果他们已经在set
,他们肯定是重复的。这将有效,并且效率很高。但是,Python的内存管理可能效率不高,set
数据类型可能超出可用内存,在这种情况下需要重新考虑。试试吧。
修改:好的,所以set
变得太大了。
要获得一个好的解决方案,您希望避免重复读取输入文件。在原始解决方案中,每个可能的副本都会再次读取输入文件,因此如果有N行,则读取的总行数可能达到N ^ 2。优化(分析)和并行性不会使这更好。并且,由于文件大小庞大,您还有一个内存约束,它排除了简单的技巧,比如将目前为止看到的所有行存储在哈希表中(如set
)。
这是我的第二个建议。在这个建议中,内存需求将扩展到适合您可用的任何内容。您将需要足够的磁盘空间来存储输入文件的至少一个副本。这些步骤构成了一个管道 - 一步的输出就是下一步的输入。
第1步。我认为你有兴趣研究4行组。你想保持整个4组,或者没有。您的第一步应该是将每组4条线组合成一条线。例如:
number1
I am writing line1 .
number2
First line .
number3
I am writing line2.
number4
Second line .
变为
number1#I am writing line1 .#number2#First line .
number3#I am writing line2 .#number4#Second line .
请注意,我使用过&#39;#&#39;标记换行符的位置。这个很重要。您可以在此处使用任何字符,前提是它未在输入文件中的任何其他位置使用。
第2步。在每行前面加上行号。
1#number1#I am writing line1 .#number2#First line .
2#number3#I am writing line2 .#number4#Second line .
第3步。使用Unix sort
实用程序(或其Windows端口)。它已经高度优化。甚至还有选项可以并行进行排序以提高速度。使用以下选项排序:
sort '-t#' -k3
这些sort
选项会导致程序仅考虑第3个字段 - 这是每个组中的第2行。
第4步。现在逐步完成前一阶段的输出,寻找重复项,利用它们将彼此相邻的事实。看看第3场。如果找到重复的行,请将其丢弃。
第5步。使用另一个sort
:
sort '-t#' -k1 -n
这次,排序使用行号的数值(第一个字段)。
第6步。从每行的开头删除行号。
第7步。转动每个#&#39;#&#39;字符回到换行符。完成工作。
虽然这看起来像很多步骤,但除了步骤3和5之外的所有步骤都只涉及一次通过输入文件,所以它们会非常快。 N行的N步。排序步骤(3和5)也很快,因为sort
程序已经过大量优化并使用了良好的排序算法(N行最多N log N步)。
答案 2 :(得分:1)
fOutput = open('myfile','w')
infile = open('test.ttl', 'r')
all_line2 = {}
while True:
inFileLine1 = infile.readline()
if not inFileLine1:
break #EOF
inFileLine2 = infile.readline()
_ = infile.readline()
_ = infile.readline()
all_line2[inFileLine2] = False
infile.seek(0)
while True:
inFileLine1=infile.readline()
if not inFileLine1:
break #EOF
inFileLine2=infile.readline()
inFileLine3=infile.readline()
inFileLine4=infile.readline()
if not all_line2.get(inFileLine2):
fOutput.write(inFileLine1)
fOutput.write(inFileLine2)
fOutput.write(inFileLine3)
fOutput.write(inFileLine4)
all_line2[inFileLine2] = True
答案 3 :(得分:1)
在Java中查看java.util.concurrent.ConcurrentHashMap。它被设计为在被同时访问地图的多个线程使用时表现良好。
此外,通过Executor固定线程池使用Java NIO读取文件。
首先,您可以使用此代码
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
private static final ConcurrentHashMap map = new ConcurrentHashMap();
public static class Task implements Runnable {
private final String line;
public Task(String line) {
this.line = line;
}
@Override
public void run() {
// if (!map.containsKey(line)) // not needed
map.put(line, true);
}
}
public static void main(String[] args) throws IOException {
ExecutorService service = Executors.newFixedThreadPool(10);
String dir_path, file_name;
Files.lines(Paths.get(dir_path, file_name)).forEach(l -> service.execute(new Task(l)));
service.shutdown();
map.keySet().forEach(System.out::println);
}
}
答案 4 :(得分:0)
我更喜欢使用Java。鉴于文件的大小为60 GB,Java为此名为MappedByteBuffer.
的
使用文件通道加载文件并使用上述API映射通道,如下所示:
FileChannel fileChannel = new RandomAccessFile(new File(inputFile), "r").getChannel();
mappedBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size());
这会将整个文件加载到内存中。为获得最佳效率,请将其映射为块(加载50k字节)
mappedBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, 50000);
现在您可以迭代mappedBuffer并进行处理。让我们知道清楚。
答案 5 :(得分:0)
我想以顺序方式阅读文件。让我们考虑一些影响性能的因素和可能的解决方案:
语言:投票支持C / C ++。
IO :我们可以使用Windows和Linux上可用的内存映射,在Linux上它是mmap()
函数;基本上,这会将文件内容映射到指针,例如char* data
。如果您使用的是Windows并且需要代码,请告诉我。
搜索密钥:我建议使用二进制搜索树,每次我们采用新的几行=&gt;价值,关键;我们需要遍历树来找到密钥。如果找到,那么跳过这个和下一对。如果未找到,则将此对作为新节点插入树中,位于搜索的结束位置;并将这对夫妇写入输出文件。当然,搜索需要O(logN)。
节点的数据结构:
struct Node {
char* key;
unsigned short keyLen;
char* value;
unsigned short valueLen;
Node* leftNode;
Node* rightNode;
}
如果相关,您可以将unsigned short
更改为unsigned char
。指针key
和value
实际上指向由data
保持的内存块的某些位置,因此没有分配新内存来存储键和值。
使用Bloom Filter可以进一步改善搜索。如果过滤器回答NO(非常快),那么确定键在我们的树中不存在,不再需要遍历树。如果答案为是,则正常遍历树。 Bloom Filter在Redis和HBase中实现,如果需要,请查看这些开源数据库系统。