使用Java删除文件中的重复行

时间:2009-06-15 13:14:40

标签: java file text file-io duplicates

作为我正在处理的项目的一部分,我想清理一个生成重复行条目的文件。然而,这些重复通常不会彼此靠近。我想出了一种在Java中这样做的方法(它基本上制作了文件的副本,然后使用嵌套的while语句将一个文件中的每一行与另一个文件中的其余部分进行比较)。问题是,我生成的文件非常大而且文本很重(大约225k行文本,大约40兆)。我估计我目前的流程需要63个小时!这绝对是不可接受的。

但是,我需要一个集成的解决方案。最好是Java。有任何想法吗?谢谢!

15 个答案:

答案 0 :(得分:37)

嗯... 40 megs似乎足够小,你可以建立Set行,然后将它们全部打印出来。这比进行O(n 2 )I / O工作要快得多。

这将是这样的(忽略例外):

public void stripDuplicatesFromFile(String filename) {
    BufferedReader reader = new BufferedReader(new FileReader(filename));
    Set<String> lines = new HashSet<String>(10000); // maybe should be bigger
    String line;
    while ((line = reader.readLine()) != null) {
        lines.add(line);
    }
    reader.close();
    BufferedWriter writer = new BufferedWriter(new FileWriter(filename));
    for (String unique : lines) {
        writer.write(unique);
        writer.newLine();
    }
    writer.close();
}

如果订单很重要,您可以使用LinkedHashSet代替HashSet。由于元素是通过引用存储的,因此与实际数据量相比,额外链接列表的开销应该是无关紧要的。

编辑:正如Workshop Alex所指出的,如果你不介意制作一个临时文件,你可以在阅读时简单地打印出来。这允许您使用简单的HashSet代替LinkedHashSet。但我怀疑你是否注意到像这样的I / O绑定操作的区别。

答案 1 :(得分:15)

好的,大多数答案都有点愚蠢和缓慢,因为它涉及到一些hashset或其他任何东西添加行,然后再从那个集合中移回它。让我展示伪代码中最优的解决方案:

Create a hashset for just strings.
Open the input file.
Open the output file.
while not EOF(input)
  Read Line.
  If not(Line in hashSet)
    Add Line to hashset.
    Write Line to output.
  End If.
End While.
Free hashset.
Close input.
Close output.

请大家,不要让它变得比它需要的更困难。 :-)甚至不打扰排序,你不需要。

答案 2 :(得分:10)

类似的方法

public void stripDuplicatesFromFile(String filename) {
    IOUtils.writeLines(
        new LinkedHashSet<String>(IOUtils.readLines(new FileInputStream(filename)),
        "\n", new FileOutputStream(filename + ".uniq"));
}

答案 3 :(得分:4)

这样的事情,也许是:

BufferedReader in = ...;
Set<String> lines = new LinkedHashSet();
for (String line; (line = in.readLine()) != null;)
    lines.add(line); // does nothing if duplicate is already added
PrintWriter out = ...;
for (String line : lines)
    out.println(line);

LinkedHashSet保留了插入顺序,而不是HashSet(对于查找/插入稍快一些)会重新排序所有行。

答案 4 :(得分:3)

您可以使用“集合”库中的“设置”在读取文件时存储唯一的,可见的值。

Set<String> uniqueStrings = new HashSet<String>();

// read your file, looping on newline, putting each line into variable 'thisLine'

    uniqueStrings.add(thisLine);

// finish read

for (String uniqueString:uniqueStrings) {
  // do your processing for each unique String
  // i.e. System.out.println(uniqueString);
}

答案 5 :(得分:3)

如果订单无关紧要,请simplest way is shell scripting

<infile sort | uniq > outfile

答案 6 :(得分:2)

尝试使用一个简单的HashSet来存储您已阅读过的行。 然后迭代文件。 如果遇到重复项,则会被忽略(因为Set只能包含每个元素一次)。

答案 7 :(得分:2)

  • 读入文件,存储行号和行:O(n)
  • 按字母顺序排序:O(n log n)
  • 删除重复项:O(n)
  • 将其分类为原始行号顺序:O(n log n)

答案 8 :(得分:1)

Hash Set方法没问题,但您可以调整它,不必将所有字符串存储在内存中,而是将逻辑指针存储到文件中的位置,这样您就可以返回读取实际值,以防万一需要它。

另一种创造性的方法是在每行附加行号,然后对所有行进行排序,删除重复项(忽略应该是数字的最后一个标记),然后再按最后一个标记对文件进行排序,在输出中将其删除。

答案 9 :(得分:0)

如果您可以使用UNIX shell命令,则可以执行以下操作:

for(i = line 0 to end)
{
    sed 's/\$i//2g' ; deletes all repeats
}

这将迭代整个文件,并且每次调用只传递一次唯一的事件。这样你就没有做过一堆你以前做过的搜索。

答案 10 :(得分:0)

有两种可扩展的解决方案,其中可扩展的意思是磁盘而不是基于内存,取决于程序是否应该稳定,其中稳定的意思是删除重复后的顺序是相同的。如果可伸缩性不是问题,那么只需将内存用于同一种方法。

对于非稳定解决方案,首先对磁盘上的文件进行排序。这是通过将文件拆分为较小的文件,对内存中较小的块进行排序,然后按排序顺序合并文件来完成的,其中合并忽略重复项。

通过比较每个文件中的当前行,几乎没有内存可以完成合并本身,因为保证下一行更大。

稳定的解决方案稍微复杂一些。首先,像以前一样对文件进行排序,但在每行中指出原始行号。然后,在“合并”期间不要打扰存储 结果,只是要删除的行号。

然后逐行复制原始文件,忽略上面存储的行号。

答案 11 :(得分:0)

线条的顺序和重复数量是否重要?

如果没有,如果你指望很多欺骗(即阅读比写作多得多)我还会考虑并行化散列集解决方案,将hashset作为共享资源。

答案 12 :(得分:0)

我对这个有效的解决方案做了两个假设:

  1. 有一个Blob等效的行,或者我们可以将其作为二进制文件处理
  2. 我们可以保存偏移量或指向每行开头的指针。
  3. 基于这些假设,解决方案是: 1.读取一行,将hashmap中的长度保存为键,这样我们就可以得到更轻的hashmap。将列表保存为散列映射中的条目,用于具有密钥中提到的长度的所有行。构建此哈希映射是O(n)。 在映射散列映射中每一行的偏移量时,将行blob与该行密钥长度的行(偏移量)中的所有现有条目进行比较,除了条目-1为偏移量。如果找到重复,则删除两行并保存偏移量 - 列表中的那些地方有1个。

    因此,请考虑复杂性和内存使用情况:

    Hashmap内存,空间复杂度= O(n)其中n是行数

    时间复杂度 - 如果没有重复但是考虑到每条线的长度= m的所有相等长度线,则考虑线数= n然后那将是O(n)。由于我们假设我们可以比较blob,因此m无关紧要。 那是最糟糕的情况。

    在其他情况下,我们节省了比较,尽管我们在hashmap中只需要很少的额外空间。

    此外,我们可以在服务器端使用mapreduce来拆分集合并稍后合并结果。并使用长度或行的开头作为映射器密钥。

答案 13 :(得分:0)

void deleteDuplicates(File filename) throws IOException{
    @SuppressWarnings("resource")
    BufferedReader reader = new BufferedReader(new FileReader(filename));
    Set<String> lines = new LinkedHashSet<String>();
    String line;
    String delims = " ";
    System.out.println("Read the duplicate contents now and writing to file");
    while((line=reader.readLine())!=null){
        line = line.trim(); 
        StringTokenizer str = new StringTokenizer(line, delims);
        while (str.hasMoreElements()) {
            line = (String) str.nextElement();
            lines.add(line);
            BufferedWriter writer = new BufferedWriter(new FileWriter(filename));
            for(String unique: lines){
                writer.write(unique+" ");               
            }
            writer.close();
        }
    }
    System.out.println(lines);
    System.out.println("Duplicate removal successful");
}

答案 14 :(得分:0)

这些答案都依赖于文件足够小以存储在内存中。

如果可以对文件进行排序,这是一种可以用于任何大小文件的算法。

您需要这个库:https://github.com/lemire/externalsortinginjava

我假设你从一个文件 fileDumpCsvFileUnsorted 开始,你最终会得到一个新文件 fileDumpCsvFileSorted,它已经排序并且没有重复。

ExternalSort.sort(fileDumpCsvFileUnsorted, fileDumpCsvFileSorted);
int numDupes = 0;
File dupesRemoved = new File(fileDumpCsvFileSorted.getAbsolutePath() + ".nodupes");
String previousLine = null;
try (FileWriter fw = new FileWriter(dupesRemoved);
     BufferedWriter bw = new BufferedWriter(fw);
     FileReader fr = new FileReader(fileDumpCsvFileSorted);
     LineIterator lineIterator = new LineIterator(fr)
) {
  while (lineIterator.hasNext()) {
    String nextLine = lineIterator.nextLine();
    if (StringUtils.equals(nextLine, previousLine)) {
      ++numDupes;
      continue;
    }
    bw.write(String.format("%s%n", nextLine));
    previousLine = nextLine;
  }
}
logger.info("Removed {} dupes from {}", numDupes, fileDumpCsvFileSorted.getAbsolutePath());
FileUtils.deleteQuietly(fileDumpCsvFileSorted);
FileUtils.moveFile(dupesRemoved, fileDumpCsvFileSorted);

现在创建的文件 fileDumpCsvFileSorted 已排序,没有重复。