从文件

时间:2015-07-07 09:08:56

标签: java python c++ linux multithreading

我有以下数据:

 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

6 个答案:

答案 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

使用文件通道加载文件并使用上述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。指针keyvalue实际上指向由data保持的内存块的某些位置,因此没有分配新内存来存储键和值。

使用Bloom Filter可以进一步改善搜索。如果过滤器回答NO(非常快),那么确定键在我们的树中不存在,不再需要遍历树。如果答案为是,则正常遍历树。 Bloom Filter在Redis和HBase中实现,如果需要,请查看这些开源数据库系统。