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

使用Java删除文件中的重复行,java,file,text,file-io,duplicates,Java,File,Text,File Io,Duplicates,作为我正在处理的项目的一部分,我想清理一个由重复行条目生成的文件。然而,这些重复通常不会发生在彼此附近。我提出了一种在Java中实现这一点的方法(它基本上复制了文件,然后使用嵌套的while语句将一个文件中的每一行与另一个文件中的其余行进行比较)。问题是,我生成的文件非常大,文本很重(大约225k行文本,大约40兆)。我估计我目前的过程需要63小时!这是绝对不能接受的 然而,我需要一个集成的解决方案。最好用Java。有什么想法吗?谢谢 嗯。。。40兆似乎足够小,你可以建立一个集合行,然后将它们全

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


然而,我需要一个集成的解决方案。最好用Java。有什么想法吗?谢谢

嗯。。。40兆似乎足够小,你可以建立一个
集合
行,然后将它们全部打印出来。这将比O(n2)I/O工作快得多

可能是这样的(忽略异常):

public void stripDuplicatesFromFile(字符串文件名){
BufferedReader reader=新的BufferedReader(新文件读取器(文件名));
Set line=newhashset(10000);//可能应该更大
弦线;
而((line=reader.readLine())!=null){
行。添加(行);
}
reader.close();
BufferedWriter writer=新的BufferedWriter(新的FileWriter(文件名));
用于(字符串唯一:行){
writer.write(独特);
writer.newLine();
}
writer.close();
}
如果顺序很重要,可以使用
LinkedHashSet
而不是
HashSet
。由于元素是通过引用存储的,因此与实际数据量相比,额外链表的开销应该是微不足道的


编辑:正如Workshop Alex指出的那样,如果您不介意创建一个临时文件,您只需在阅读时将行打印出来即可。这允许您使用简单的
HashSet
而不是
LinkedHashSet
。但我怀疑您是否注意到像这样的I/O绑定操作的区别。

您可以在读取文件时使用集合库中的Set来存储唯一的、可见的值

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);
}
Set uniqueStrings=new HashSet();
//读取文件,在换行符上循环,将每一行放入变量“thisLine”
唯一字符串。添加(此行);
//读完
用于(字符串唯一字符串:唯一字符串){
//对每个唯一字符串进行处理
//即系统输出打印项次(唯一字符串);
}

尝试使用一个简单的哈希集来存储您已经阅读的行。 然后迭代该文件。
如果您遇到重复项,它们将被忽略(因为一个集合只能包含每个元素一次)。

类似的内容,可能是:

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);
BufferedReader in=。。。;
设置行=新建LinkedHashSet();
for(字符串行;(line=in.readLine())!=null;)
行。添加(行);//如果已添加副本,则不执行任何操作
PrintWriter输出=。。。;
用于(字符串行:行)
out.println(行);

LinkedHashSet
保留插入顺序,而
HashSet
将重新排列所有行的顺序(虽然查找/插入速度稍快)。

哈希集方法可以,但您可以调整它,使其不必在内存中存储所有字符串,但是一个指向文件中位置的逻辑指针,这样只有在需要时才能返回读取实际值


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

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

for(i = line 0 to end)
{
    sed 's/\$i//2g' ; deletes all repeats
}
这将遍历整个文件,并且每次sed调用只传递一次唯一的事件。这样一来,你就不用再做以前做过的大量搜索了。

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

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

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

合并本身几乎不需要内存,只需比较每个文件中的当前行即可,因为下一行肯定会更大

稳定的解决方案稍微复杂一些。首先,像以前一样将文件分块排序,但在每一行中指出原始行号。然后,在“合并”过程中,不要麻烦存储 结果,只有要删除的行号


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

如果顺序不重要,则:

outfile

行的顺序有关系吗?你指望看到多少重复的行


如果不是,而且如果你指望大量重复(即读比写多得多),我也会考虑并行化哈希集解决方案,将哈希集作为共享资源

好吧,大多数答案都有点愚蠢和缓慢,因为它涉及到向某个哈希集或任何东西添加行,然后再将其从该集移回。让我展示伪代码中的最佳解决方案:

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.
伙计们,请不要让它变得比需要的更难。:-)甚至不必为排序而烦恼,您也不需要这样做。

类似的方法

public void stripDuplicatesFromFile(String filename) {
    IOUtils.writeLines(
        new LinkedHashSet<String>(IOUtils.readLines(new FileInputStream(filename)),
        "\n", new FileOutputStream(filename + ".uniq"));
}
public void stripDuplicatesFromFile(字符串文件名){
书写线(
新LinkedHashS
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.
public void stripDuplicatesFromFile(String filename) {
    IOUtils.writeLines(
        new LinkedHashSet<String>(IOUtils.readLines(new FileInputStream(filename)),
        "\n", new FileOutputStream(filename + ".uniq"));
}
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");
}
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);