如何轻松地将 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>? [复制]
如何有效地将 Postgres 数据从 Query 传输到 S3