如何轻松地将 CSV 文件处理为 List<MyClass>

Posted

技术标签:

【中文标题】如何轻松地将 CSV 文件处理为 List<MyClass>【英文标题】:How to easily process CSV file to List<MyClass> 【发布时间】:2014-04-24 11:15:17 【问题描述】:

在我的应用程序中,我使用了很多 CSV 文件,我必须阅读这些文件并根据它们构建一个列表。我想找到一种简单的方法来做到这一点。你知道有什么简单的框架可以在不使用配置文件等的情况下完成吗?

例如,我有一个类 Person:

public class Person 
    String name;
    String surname;

    double shoeSize;
    boolean sex; // true: male, false:female

    public Person() 
    

    public String getName() 
            return name;
    

    public void setName(String name) 
            this.name = name;
    

    public String getSurname() 
            return surname;
    

    public void setSurname(String surname) 
            this.surname = surname;
    

    public double getShoeSize() 
            return shoeSize;
    

    public void setShoeSize(double shoeSize) 
            this.shoeSize = shoeSize;
    

    public boolean isSe) 
            return sex;
    

    public void setSeboolean sex) 
            this.sex = sex;
    

对于这堂课,我准备了 CSV 文件:

name,surname,shoesize,sex
Tom,Tommy,32,true
Anna,Anny,27,false

我怎样才能轻松做到这一点?

【问题讨论】:

是的,使用third party library。 我不认为这个问题不合适或质量低下。相反,它可能是duplicate。 【参考方案1】:

我最近通过使用Immutables 和Jackson 解决了这个问题,如果您愿意使用这些库,我认为这是一个很好的方法。

Immutables 和 Jackson 集成得非常好。以 OP 为例,您所要做的就是像这样指定 Immutables 类(符合 sn-p 显式性的注释):

@org.immutables.value.Value.Immutable
@com.fasterxml.jackson.databind.annotation.JsonDeserialize(as = ImmutablePerson.class)
public interface Person 
    String getName();
    String getSurname();
    double getShoeSize();
    boolean getSex();

然后,使用Jackson CSV module,您可以轻松地将 CSV 的每一行反序列化为 Immutables 为您生成的类:

List<Person> loadPeople(File personsCsvFile) throws IOException 
    CsvSchema schema = CsvSchema.emptySchema().withHeader();
    MappingIterator<Person> personsIterator = new CsvMapper()
            .readerFor(Person.class)
            .with(schema)
            .readValues(personsCsvFile);
    return personsIterator.readAll();

【讨论】:

【参考方案2】:

opencsv 是一个很好且简单的解决方案。这是一个小而强大的库。可以从opencsv website下载(direct download from sourceforge,使用deploy目录下的jar)或者使用maven。

java bean 映射功能使它变得非常简单,因为您的 CSV 列名称与您的类的属性名称匹配(它忽略了不同的大小写)。

使用方法:

Reader reader = // ... reader for the input file

// let it map the csv column headers to properties
CsvToBean<Person> csvPersons = new CsvToBean<Person>();
HeaderColumnNameMappingStrategy<Person> strategy = new HeaderColumnNameMappingStrategy<Person>();
strategy.setType(Person.class);

// parse the file and get a list of persons
List<Person> persons = csvPersons.parse(strategy, reader);

就是这样。

【讨论】:

【参考方案3】:

有很多用 Java 编写的优秀框架来解析 CSV 文件并形成对象列表。 OpenCSV、JSefa 和 jCSV 仅举几例。

根据您的要求,我相信jCSV 最适合。以下是 jCSV 的示例代码,您可以轻松使用。

Reader reader = new FileReader("persons.csv");

CSVReader<Person> csvPersonReader = ...;

// read all entries at once
List<Person> persons = csvPersonReader.readAll();

// read each entry individually
Iterator<Person> it = csvPersonReader.iterator();
while (it.hasNext()) 
  Person p = it.next();
  // ...

而且,解析 CSV 文件并将其转换为 List 并不是什么大问题,无需使用任何框架即可实现,如下所示。

br = new BufferedReader(new FileReader(csvFileToRead));  
List<Person> personList = new ArrayList<>();
while ((line = br.readLine()) != null)   
       // split on comma(',')  
       String[] personCsv = line.split(splitBy);  

       // create car object to store values  
       Person personObj = new Person();  

       // add values from csv to car object  
       personObj.setName(personCsv[0]);  
       personObj.setSurname(personCsv[1]);  
       personObj.setShoeSize(personCsv[2]);  
       personObj.setGender(personCsv[3]); 

       // adding car objects to a list  
       personList.add(personObj);         
 

如果 CSV 列到 bean 对象的映射在实际情况下很复杂、重复或很大,那么使用DozerBeanMapper 可以轻松完成。

希望这会对你有所帮助。

希希尔

【讨论】:

+1 用于 JSefa。这是我发现的从 Bean 生成 CSV 内容的更好方法 完全没问题:)【参考方案4】:

读取和序列化数据的最简单方法之一是使用 Jackson 库。 它还有一个 CSV 扩展,你可以找到 wiki here

假设你有一个像这样的 Pojo:

@JsonPropertyOrder( "name", "surname", "shoesize", "gender" )
public class Person 

    public String name;
    public String surname;
    public int shoesize;
    public String gender;


还有这样的 CSV:

Tom,Tommy,32,m
Anna,Anny,27,f

然后读取它是这样完成的:

MappingIterator<Person> personIter = new CsvMapper().readerWithTypedSchemaFor(Person.class).readValues(csvFile);
List<Person> people = personIter.readAll();

这对我来说很简单,基本上你需要做的就是使用@JsonPropertyOrder注释在你的CSV文件中添加列顺序,然后使用上面的2行读取文件。

【讨论】:

【参考方案5】:

不确定您是否需要使用外部库(并承担通常隐含的性能损失)。这是一个非常简单的实现。如果不出意外,了解这样一个库中幕后发生的事情总是有帮助的:

public List<Person> readFile(String fileName) throws IOException 
    List<Person> result = new ArrayList<Person>();
    BufferedReader br = new BufferedReader(new FileReader(new File(fileName)));
    try 
        // Read first line
        String line = br.readLine();
        // Make sure file has correct headers
        if (line==null) throw new IllegalArgumentException("File is empty");
        if (!line.equals("name,surname,shoesize,sex"))
            throw new IllegalArgumentException("File has wrong columns: "+line);
        // Run through following lines
        while ((line = br.readLine()) != null) 
            // Break line into entries using comma
            String[] items = line.split(",");
            try 
                // If there are too many entries, throw a dummy exception, if
                // there are too few, the same exception will be thrown later
                if (items.length>4) throw new ArrayIndexOutOfBoundsException(); 
                // Convert data to person record
                Person person = new Person();
                person.setName    (                     items[0] );
                person.setSurname (                     items[1] );
                person.setShoeSize(Double .parseDouble (items[2]));
                person.setSex     (Boolean.parseBoolean(items[3]));
                result.add(person);
             catch (ArrayIndexOutOfBoundsException|NumberFormatException|NullPointerException e) 
                // Caught errors indicate a problem with data format -> Print warning and continue
                System.out.println("Invalid line: "+ line);
            
        
        return result;
     finally 
        br.close();
    

请注意,catch 语句使用 Java 7 多捕获。对于较旧的 Java 版本,要么将其拆分为 3 个 catch 块,要么将 ArrayIndexOutOfBoundsException|NumberFormatException|NullPointerException 替换为 Exception。后者通常不鼓励使用,因为它也会屏蔽并忽略所有其他异常,但在像这样的简单示例中,风险可能不会太高。

不幸的是,这个答案是针对您的问题的,但鉴于它非常简单,应该也很容易适应其他情况......

您可以做的另一件事是使用正则表达式匹配 while 循环内的 line,而不是简单地根据逗号将其拆分。这样一来,您还可以一次性实现数据验证(例如,仅匹配鞋码的合理数字)。

请注意,如果您的名称包含逗号,然后用引号括起来(例如“Jackson, Jr.”作为姓氏),则上述实现不起作用。如果您使用如上所述的正则表达式,或者通过检查姓氏的第一个字母并且如果它是引号,则可以“轻松”覆盖这种情况,将 item[1] 与 item[2] 组合并使用 item[3 ] 和 item[4] 代替鞋码和性别。此处建议的大多数外部库可能会涵盖这种特殊情况,因此,如果您不担心任何依赖关系、许可问题和性能损失,那么这些可能是更简单的出路...

【讨论】:

【参考方案6】:

使用OpenCSV

这是一个读取条目并将它们添加到列表的完整示例:

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.List;

import au.com.bytecode.opencsv.CSVReader;

public class CSVReaderImplementor 
  private String fileName;
  private CSVReader reader;
  private List<String[]> entries;

  public CSVReaderImplementor(String fileName) throws IOException, FileNotFoundException 
    this.fileName = fileName;
    reader = new CSVReader(new FileReader(this.fileName));

    entries = reader.readAll();

  

  public List getEntries() 
    return entries;
  

  public static void main(String[] args) throws FileNotFoundException, IOException 
    CSVReaderImplementor cri = new CSVReaderImplementor("yourfile.csv");

    for(int i = 0; i < 50; i++) 
      System.out.println(cri.getEntries().get(i).toString());
    
  

返回 String[] 类型的 List。您可以遍历列表中每个条目的 String 数组,并使用每个索引处的值来填充您的 Bean 构造函数。

【讨论】:

【参考方案7】:

我认为 SuperCSV + Dozer 易于使用并且对于 java bean CSV 序列化非常健壮

http://supercsv.sourceforge.net/dozer.html

【讨论】:

据我所知,SuperCSV 的 CSVBeanReader 类应该足够了

以上是关于如何轻松地将 CSV 文件处理为 List<MyClass>的主要内容,如果未能解决你的问题,请参考以下文章

如何正确地将 DataReader 转换为 DTO/List<DTO>? [复制]

使用 C# 将 csv 文件转换为 json

如何有效地将 Postgres 数据从 Query 传输到 S3

如何轻松地将json文件的某些方面加载到rdd

如何正确地将数据从 *.csv 文件导入 mysql 数据库中的给定表

如何有效且快速地将大型 (6 Gb) .csv 文件导入 R,而不会导致 R REPL 崩溃?