如何用OpenCSV按CSV记录类型过滤并分别解析为不同Java对象
可以使用OpenCSV原生的过滤器或自定义映射策略实现需求,不需要手动逐行处理,性能和单类型解析基本一致。
前置修正:调整实体类字段位置
你当前的实体类@CsvBindByPosition配置存在错误,CSV第二列(索引为1)是类型标识位,业务字段索引需要顺移1位:
public class Student { @CsvBindByPosition(position = 0) private String name; @CsvBindByPosition(position = 2) private String mathsScore; @CsvBindByPosition(position = 3) private String scienceScore; @CsvBindByPosition(position = 4) private String englishScore; // 省略getter、setter } public class Teacher { @CsvBindByPosition(position = 0) private String name; @CsvBindByPosition(position = 2) private String salary; @CsvBindByPosition(position = 3) private String yearOfExp; // 省略getter、setter }
方案1:过滤器分两次解析(实现简单)
通过OpenCSV自带的withFilter方法在绑定对象前过滤对应类型的行,不符合条件的行直接跳过,不会产生额外的映射开销:
import com.opencsv.CSVReader; import com.opencsv.CSVReaderBuilder; import com.opencsv.CSVParserBuilder; import com.opencsv.bean.CsvToBeanBuilder; import java.io.FileReader; import java.util.List; // 1. 解析学生列表 CSVReader studentReader = new CSVReaderBuilder(new FileReader(csvFile)) .withCSVParser(new CSVParserBuilder().withSeparator('|').build()) .build(); List<Student> students = new CsvToBeanBuilder<Student>(studentReader) .withType(Student.class) // 只保留第二列为S的有效行 .withFilter(line -> line.length >= 2 && "S".equals(line[1])) .build() .parse(); // 2. 解析教师列表,需重新构造Reader重置文件指针 CSVReader teacherReader = new CSVReaderBuilder(new FileReader(csvFile)) .withCSVParser(new CSVParserBuilder().withSeparator('|').build()) .build(); List<Teacher> teachers = new CsvToBeanBuilder<Teacher>(teacherReader) .withType(Teacher.class) // 只保留第二列为T的有效行 .withFilter(line -> line.length >= 2 && "T".equals(line[1])) .build() .parse();
方案2:自定义映射策略一次解析(性能更优,适合超大文件)
如果不想两次读取文件,可以自定义映射策略,一次读取直接生成两类对象再分组,性能更高:
- 先定义标记接口让两类实体实现:
public interface Person {} public class Student implements Person { // 原有实体逻辑不变 } public class Teacher implements Person { // 原有实体逻辑不变 }
- 自定义字段映射策略:
import com.opencsv.bean.HeaderColumnNameMappingStrategy; import com.opencsv.exceptions.*; public class PersonMappingStrategy<T> extends HeaderColumnNameMappingStrategy<T> { @Override public T populateNewBean(String[] line) throws CsvBeanIntrospectionException, CsvRequiredFieldEmptyException, CsvDataTypeMismatchException, CsvConstraintViolationException { if (line.length < 2) return null; String type = line[1]; if ("S".equals(type)) { setType((Class<T>) Student.class); } else if ("T".equals(type)) { setType((Class<T>) Teacher.class); } else { return null; } return super.populateNewBean(line); } }
- 一次解析后分组:
CSVReader reader = new CSVReaderBuilder(new FileReader(csvFile)) .withCSVParser(new CSVParserBuilder().withSeparator('|').build()) .build(); PersonMappingStrategy<Person> strategy = new PersonMappingStrategy<>(); List<Person> allPersons = new CsvToBeanBuilder<Person>(reader) .withMappingStrategy(strategy) .build() .parse(); // 按类型拆分 List<Student> students = allPersons.stream() .filter(p -> p instanceof Student) .map(p -> (Student) p) .toList(); List<Teacher> teachers = allPersons.stream() .filter(p -> p instanceof Teacher) .map(p -> (Teacher) p) .toList();
注意事项
- 以上代码省略了异常捕获和流关闭逻辑,实际使用时建议用try-with-resources管理文件流,避免资源泄漏。
- OpenCSV底层本身是高度优化的逐行读取实现,上述两种方案均不需要手动处理行拆分和字段映射,性能完全可以满足大文件场景。
内容的提问来源于stack exchange,提问作者krishna
相关产品推荐
相关产品推荐

