在没有 headers 的情况下附加到 CSV 文件

Appending to CSV file without headers

我正在使用 opencsv 将 Java bean 写入具有 headers 的 CSV 文件。文件名包含当前日期。如果用户在同一天第二次运行它,它会附加到文件,但会添加另一行 header。

如何追加到文件但没有列 headers.

    public class CSVExport {

final File USERHOME = new File(System.getProperty("user.home"));
final List<saleExport> listSaleExport = new ArrayList<>();
final ObjectMapper mapper = new ObjectMapper();

public void createCsvFile(String Region, Map<String, String> currentSale) throws IOException {

    mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
    // use column position mapping strategy for no headers?
    LocalDate today = LocalDate.now();

                final SaleExport saleExport = mapper.convertValue(currentSale, SaleExport.class);
                listSaleExport.add(saleExport);
                writeToFile(today +" LA.csv", listSaleExport);
}

public void writeToFile(String filename, List<listSaleExport> listSaleExport) throws IOException {

    File file = new File(PROCESSED_DIR +"\", "filename");

    if (!file.exists()) {
        try {
            Writer writer = new FileWriter(PROCESSED_DIR +"\" +filename, true);
            StatefulBeanToCsvBuilder<listSaleExport> beanToCsv = new StatefulBeanToCsvBuilder<>(writer);
            StatefulBeanToCsv<listSaleExport> beanWriter = beanToCsv.build();
            beanWriter.write(listSaleExport);
            writer.close();
        } catch (Exception ex) {
            System.out.println("Error : " + ex.getMessage());
        }
    } else {
        try {
            Writer writer = new FileWriter(PROCESSED_DIR +"\" +"filename");
            StatefulBeanToCsvBuilder<listSaleExport> beanToCsv = new StatefulBeanToCsvBuilder<>(writer);
            StatefulBeanToCsv<listSaleExport> beanWriter = beanToCsv.build();
            beanWriter.write(listSaleExport);
            writer.close();
        } catch (Exception ex) {
            System.out.println("Error : " + ex.getMessage());
        }
    }
  }
}

不错。当我们在 opencsv 中写入时,附加是我们没有考虑太多的事情,因为它有潜在的风险(出现问题你可能会破坏一个好的文件)所以 write 更受欢迎。

那是说在 sourceforge 中提出错误或功能请求,如果有足够的兴趣,我们将尝试在 4.3 版本中获得它(4.2 已预订)。

如果您想解决这个问题,请创建您自己的 MappingStrategy class that extends the HeaderColumnNameMappingStrategy and all you need there is a override on the generateHeader method to return an empty String array. You can look at the code in ColumnPositionMappingStrategy 看看我在说什么。这将防止 header 被写入。在这种情况下,您只需要在 else 部分使用它。

希望对您有所帮助。

:)

我不知道该功能是否最终添加到 OpenCSV 中。现在最新版本是 5.1,我还没有读到任何关于它的信息...

正如我在 another question 中发布的那样,我在更新到 5.1 后开始工作:

需要具有特定字段 headers (@CsvBindByName) 和位置 (@CsvBindByPosition) 的 CSV,所以我已经必须制作自己的 MappingStrategy。

public class CustomMappingStrategy<T> extends ColumnPositionMappingStrategy<T> {
    private boolean useHeader=true;
    
    public CustomMappingStrategy(){
    }
    
    public CustomMappingStrategy(boolean useHeader) {
        this.useHeader = useHeader;
    }
    
    @Override
    public String[] generateHeader(T bean) throws CsvRequiredFieldEmptyException {
        final int numColumns = FieldUtils.getAllFields(bean.getClass()).length;
        super.setColumnMapping(new String[numColumns]);
        
        if (numColumns == -1) {
            return super.generateHeader(bean);
        }
        
        String[] header = new String[numColumns];
        
        if(!useHeader){
            return ArrayUtils.EMPTY_STRING_ARRAY;
        }
        BeanField<T, Integer> beanField;
        for (int i = 0; i < numColumns; i++){
            beanField = findField(i);
            String columnHeaderName = extractHeaderName(beanField);
            header[i] = columnHeaderName;
        }
        
        return header;
    }
    
    private String extractHeaderName(final BeanField<T, Integer> beanField){
        if (beanField == null || beanField.getField() == null || beanField.getField().getDeclaredAnnotationsByType(CsvBindByName.class).length == 0){
            return StringUtils.EMPTY;
        }
        
        //return value of CsvBindByName annotation
        final CsvBindByName bindByNameAnnotation = beanField.getField().getDeclaredAnnotationsByType(CsvBindByName.class)[0];
        return bindByNameAnnotation.column();
    }

}

我还制作了一个通用导出器 class,以简化 CSV 生成:

public class ExportGenericCSV<T> {
    private static final Logger logger = LoggerFactory.getLogger(ExportGenericCSV.class);
    private final char DEFAULT_QUOTE_CHARACTER = CSVWriter.NO_QUOTE_CHARACTER;
    private final char CSV_SEPARATOR = CSVWriter.DEFAULT_SEPARATOR;
    
    public void writeToFile(String fileName, List<T> content){
        writeList(fileName, content, true);
    }
    
    public void appendToFile(String fileName, List<T> content){
        writeList(fileName, content, false, StandardOpenOption.APPEND);
    }

    @SuppressWarnings("unchecked")
    private void writeList(String fileName, List<T> exports, boolean addHeader, OpenOption...openOptions){
        if(exports == null  || exports.isEmpty()) return;
        try (Writer writer = Files.newBufferedWriter(Paths.get(fileName), openOptions)){

            CustomMappingStrategy<T> mapping = new CustomMappingStrategy<T>(addHeader);
            mapping.setType((Class<? extends T>) exports.get(0).getClass());
            
            StatefulBeanToCsv<T> beanToCsv = new StatefulBeanToCsvBuilder<T>(writer)
                    .withQuotechar(DEFAULT_QUOTE_CHARACTER)
                    .withSeparator(CSV_SEPARATOR)
                    .withMappingStrategy(mapping)
                    .build();

            beanToCsv.write(exports);
            
            writer.flush();
            writer.close();
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
        } catch (CsvDataTypeMismatchException e) {
            logger.error(e.getMessage(), e);
        } catch (CsvRequiredFieldEmptyException e) {
            logger.error(e.getMessage(), e);
        }
    }
}

这样,当您需要导出项目列表时,您可以使用:

ExportGenericCSV<ExportedClass> exporter = new ExportGenericCSV<ExportedClass>();

然后使用exporter.writeToFile(...)exporter.appendToFile(...)