supercsv - 有关在 csv 中查找和报告重复行的建议

标签 supercsv

我有多个项目,其中包含不同数量的 csv 文件,我正在使用 SuperCSV CsvBeanReader 来执行映射和单元格验证。我已经为每个 csv 文件创建了一个 bean 并覆盖;每个 bean 的 equals、hashCode 和 toString。

我正在寻找有关执行 csv 行重复识别的最佳“所有项目”实现方法的建议。报告(不是删除)原始 csv 行号和行内容,以及找到的所有重复行的行号和行内容。有些文件可以达到数十万行,大小超过 GB,并且希望最大限度地减少每个文件的读取次数,并认为这可以在 CsvBeanReader 打开文件时完成。

提前谢谢您。

最佳答案

考虑到文件的大小以及您想要原始文件和副本的行内容这一事实,我认为您能做的最好的事情就是对文件进行两次传递。

如果您只想获取副本的最新行内容,则只需 1 次即可。在一次传递中跟踪原始行内容以及所有重复项意味着您必须存储每一行​​的内容 - 您可能会耗尽内存。

我的解决方案假设具有相同 hashCode() 的两个 bean 是重复的。如果您必须使用 equals() 那么它会变得更加复杂。

  • 第 1 步:识别重复项(记录每个重复项的行号) 重复哈希)

  • 第 2 步:报告重复项

第 1 遍:识别重复项

/**
 * Finds the row numbers with duplicate records (using the bean's hashCode()
 * method). The key of the returned map is the hashCode and the value is the
 * Set of duplicate row numbers for that hashcode.
 * 
 * @param reader
 *            the reader
 * @param preference
 *            the preferences
 * @param beanClass
 *            the bean class
 * @param processors
 *            the cell processors
 * @return the map of duplicate rows (by hashcode)
 * @throws IOException
 */
private static Map<Integer, Set<Integer>> findDuplicates(
    final Reader reader, final CsvPreference preference,
    final Class<?> beanClass, final CellProcessor[] processors)
    throws IOException {

  ICsvBeanReader beanReader = null;
  try {
    beanReader = new CsvBeanReader(reader, preference);

    final String[] header = beanReader.getHeader(true);

    // the hashes of any duplicates
    final Set<Integer> duplicateHashes = new HashSet<Integer>();

    // the hashes for each row
    final Map<Integer, Set<Integer>> rowNumbersByHash = 
      new HashMap<Integer, Set<Integer>>();

    Object o;
    while ((o = beanReader.read(beanClass, header, processors)) != null) {
      final Integer hashCode = o.hashCode();

      // get the row no's for the hash (create if required)
      Set<Integer> rowNumbers = rowNumbersByHash.get(hashCode);
      if (rowNumbers == null) {
        rowNumbers = new HashSet<Integer>();
        rowNumbersByHash.put(hashCode, rowNumbers);
      }

      // add the current row number to its hash
      final Integer rowNumber = beanReader.getRowNumber();
      rowNumbers.add(rowNumber);

      if (rowNumbers.size() == 2) {
        duplicateHashes.add(hashCode);
      }

    }

    // create a new map with just the duplicates
    final Map<Integer, Set<Integer>> duplicateRowNumbersByHash = 
      new HashMap<Integer, Set<Integer>>();
    for (Integer duplicateHash : duplicateHashes) {
      duplicateRowNumbersByHash.put(duplicateHash,
          rowNumbersByHash.get(duplicateHash));
    }

    return duplicateRowNumbersByHash;

  } finally {
    if (beanReader != null) {
      beanReader.close();
    }
  }
}

作为此方法的替代方法,您可以使用 CsvListReader 并利用 getUntokenizedRow().hashCode() - 这将根据原始数据计算哈希值CSV 字符串(它会快得多,但您的数据可能有细微的差异,这意味着它不起作用)。

第 2 轮:报告重复项

此方法获取上一个方法的输出,并使用它来快速识别重复记录及其重复的其他行。

  /**
   * Reports the details of duplicate records.
   * 
   * @param reader
   *            the reader
   * @param preference
   *            the preferences
   * @param beanClass
   *            the bean class
   * @param processors
   *            the cell processors
   * @param duplicateRowNumbersByHash
   *            the row numbers of duplicate records
   * @throws IOException
   */
  private static void reportDuplicates(final Reader reader,
      final CsvPreference preference, final Class<?> beanClass,
      final CellProcessor[] processors,
      final Map<Integer, Set<Integer>> duplicateRowNumbersByHash)
      throws IOException {

    ICsvBeanReader beanReader = null;
    try {
      beanReader = new CsvBeanReader(reader, preference);

      final String[] header = beanReader.getHeader(true);

      Object o;
      while ((o = beanReader.read(beanClass, header, processors)) != null) {
        final Set<Integer> duplicateRowNumbers = 
            duplicateRowNumbersByHash.get(o.hashCode());
        if (duplicateRowNumbers != null) {
          System.out.println(String.format(
            "row %d is a duplicate of rows %s, line content: %s",
            beanReader.getRowNumber(),
            duplicateRowNumbers,
            beanReader.getUntokenizedRow()));
        }

      }

    } finally {
      if (beanReader != null) {
        beanReader.close();
      }
    }
  }

示例

以下是如何使用这两种方法的示例。

  // rows (2,4,8) and (3,7) are duplicates
  private static final String CSV = "a,b,c\n" + "1,two,01/02/2013\n"
      + "2,two,01/02/2013\n" + "1,two,01/02/2013\n"
      + "3,three,01/02/2013\n" + "4,four,01/02/2013\n"
      + "2,two,01/02/2013\n" + "1,two,01/02/2013\n";

  private static final CellProcessor[] PROCESSORS = { new ParseInt(),
      new NotNull(), new ParseDate("dd/MM/yyyy") };

  public static void main(String[] args) throws IOException {

    final Map<Integer, Set<Integer>> duplicateRowNumbersByHash = findDuplicates(
        new StringReader(CSV), CsvPreference.STANDARD_PREFERENCE,
        Bean.class, PROCESSORS);

    reportDuplicates(new StringReader(CSV),
        CsvPreference.STANDARD_PREFERENCE, Bean.class, PROCESSORS,
        duplicateRowNumbersByHash);

  }

输出:

row 2 is a duplicate of rows [2, 4, 8], line content: 1,two,01/02/2013
row 3 is a duplicate of rows [3, 7], line content: 2,two,01/02/2013
row 4 is a duplicate of rows [2, 4, 8], line content: 1,two,01/02/2013
row 7 is a duplicate of rows [3, 7], line content: 2,two,01/02/2013
row 8 is a duplicate of rows [2, 4, 8], line content: 1,two,01/02/2013

关于supercsv - 有关在 csv 中查找和报告重复行的建议,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15186862/

相关文章:

java - 使用 SuperCSV ICsvBeanReader 解析枚举

java - 使用 SuperCSV 更改 header 值

java - 对于使用 apache POI 转换为 CSV 时的 xlsx 单元格数据

java - 使用 super-CSV 读取嵌套 POJO

java - 使用 CSVBeanReader 忽略不需要的列

java - 官方 supercsv 推土机示例不适用于 Play — Play 2.1.1,Java

java - 异常后继续 SuperCSV 读取

mapping - super CSV 和多 bean 映射

java - 如何使用 SuperCSV 获取具有自定义 header 名称而不是 bean 字段名称的 csv?

java - SuperCSV,Dozer : Writing to a csv file. 对于具有多行列表的对象