Java 中使用 OpenCSV 读取/写入CSV 文件

x33g5p2x  于2021-10-17 转载在 Java  
字(9.4k)|赞(0)|评价(0)|浏览(3451)

在之前的一篇文章中,我写了如何使用 Apache Commons CSV 在 Java 中读取和写入 CSV 文件。

在本文中,我将带您了解另一个名为 OpenCSV 的开源库,用于在 Java 中读写 CSV 文件。

添加 OpenCSV 依赖项

首先,您需要在您的项目中添加 OpenCSV 依赖项。如果您是 Maven 用户,请将以下依赖项添加到您的 pom.xml 文件中。

<dependency>
    <groupId>com.opencsv</groupId>
    <artifactId>opencsv</artifactId>
    <version>4.0</version>
</dependency>

这是 Gradle 的依赖项 -

compile "com.opencsv:opencsv:4.0"

CSV 文件示例

以下是我们将在本文提供的示例中读取和解析的两个示例 CSV 文件。

没有标题的 CSV 文件 - users.csv

Rajeev Kumar Singh ♥,rajeevs@example.com,+91-9999999999,India
Sachin Tendulkar,sachin@example.com,+91-9999999998,India
Barak Obama,barak.obama@example.com,+1-1111111111,United States
Donald Trump,donald.trump@example.com,+1-2222222222,United States

带有标题的 CSV 文件 - users-with-header.csv

name,email,phone,country
Rajeev Kumar Singh ♥,rajeevs@example.com,+91-9999999999,India
Sachin Tendulkar,sachin@example.com,+91-9999999998,India
Barak Obama,barak.obama@example.com,+1-1111111111,United States
Donald Trump,donald.trump@example.com,+1-2222222222,United States

读取 CSV 文件(以字符串数组的形式检索每条记录)

下面的示例展示了如何使用 OpenCSV 库读取和解析 CSV 文件。它将 CSV 记录一一读入字符串数组中 -

import com.opencsv.CSVReader;
import java.io.IOException;
import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.Paths;

public class OpenCSVReader {
    private static final String SAMPLE_CSV_FILE_PATH = "./users.csv";

    public static void main(String[] args) throws IOException {
        try (
            Reader reader = Files.newBufferedReader(Paths.get(SAMPLE_CSV_FILE_PATH));
            CSVReader csvReader = new CSVReader(reader);
        ) {
            // Reading Records One by One in a String array
            String[] nextRecord;
            while ((nextRecord = csvReader.readNext()) != null) {
                System.out.println("Name : " + nextRecord[0]);
                System.out.println("Email : " + nextRecord[1]);
                System.out.println("Phone : " + nextRecord[2]);
                System.out.println("Country : " + nextRecord[3]);
                System.out.println("==========================");
            }
        }
    }
}

#####一次读取所有记录

在上面的例子中,我们使用 readNext() 方法一一读取 CSV 记录。 CSVReader 还提供了一个名为 readAll() 的方法,可以一次性将所有记录读入 List<String[]>

// Reading All Records at once into a List<String[]>
List<String[]> records = csvReader.readAll();
for (String[] record : records) {
    System.out.println("Name : " + record[0]);
    System.out.println("Email : " + record[1]);
    System.out.println("Phone : " + record[2]);
    System.out.println("Country : " + record[3]);
    System.out.println("---------------------------");
}

请注意,上述方法将整个 CSV 内容加载到内存中,因此不适用于大型 CSV 文件。

跳过标题行

如果您尝试读取包含标题的示例 CSV 文件,则标题记录也将打印在输出中。如果要跳过标题行,则可以使用 CSVReaderBuilder 类构造一个 CSVReader 并跳过指定的行数。

import com.opencsv.CSVReaderBuilder;

CSVReader csvReader = new CSVReaderBuilder(reader).withSkipLines(1).build();

读取 CSV 文件并将记录解析为 Java 对象

OpenCSV 库的真正优势在于您可以直接将 CSV 记录解析为 Java 对象。有两种方法可以做到——第一种方法使用注释,第二种方法使用映射策略。

OpenCSV 中有两种类型的注释 - @CsvBindByName@CsvBindByPosition。您可以使用这些注释来指定应将哪个 CSV 列绑定到 Java 对象的哪个成员字段。

如果 CSV 文件包含标题,则可以使用 @CsvBindByName 批注指定 CSV 列和成员字段之间的映射。

@CsvBindByName 注释接受三个参数 - columnrequiredlocalerequiredlocale 参数是可选的,如果 CSV 文件中的标题名称与成员字段名称相同,您也可以省略 column 参数。

这是一个使用 @CsvBindByName 注释的 POJO 类的示例 -

import com.opencsv.bean.CsvBindByName;

public class CSVUser {
    @CsvBindByName
    private String name;

    @CsvBindByName(column = "email", required = true)
    private String email;

    @CsvBindByName(column = "phone")
    private String phoneNo;

    @CsvBindByName
    private String country;

	// Getters and Setters (Omitted for brevity) 
}

下面的示例显示了如何将 CSV 记录直接读取和解析为 Java 对象 -

import com.opencsv.bean.CsvToBean;
import com.opencsv.bean.CsvToBeanBuilder;
import java.io.IOException;
import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Iterator;
import java.util.List;

public class OpenCSVReadAndParseToBean {
    private static final String SAMPLE_CSV_FILE_PATH = "./users-with-header.csv";

    public static void main(String[] args) throws IOException {
        try (
            Reader reader = Files.newBufferedReader(Paths.get(SAMPLE_CSV_FILE_PATH));
        ) {
            CsvToBean<CSVUser> csvToBean = new CsvToBeanBuilder(reader)
                    .withType(CSVUser.class)
                    .withIgnoreLeadingWhiteSpace(true)
                    .build();

            Iterator<CSVUser> csvUserIterator = csvToBean.iterator();

            while (csvUserIterator.hasNext()) {
                CSVUser csvUser = csvUserIterator.next();
                System.out.println("Name : " + csvUser.getName());
                System.out.println("Email : " + csvUser.getEmail());
                System.out.println("PhoneNo : " + csvUser.getPhoneNo());
                System.out.println("Country : " + csvUser.getCountry());
                System.out.println("==========================");
            }
        }
    }
}

在上面的例子中,我们从 csvToBean 对象中获取了一个 Iterator,然后循环遍历这个迭代器,将每个对象一一检索。

CsvToBean 类还提供了一个 parse() 方法,该方法解析整个 CSV 文件并将所有对象一次加载到内存中。你可以像这样使用它 -

// Reads all CSV contents into memory (Not suitable for large CSV files)
List<CSVUser> csvUsers = csvToBean.parse();

for(CSVUser csvUser: csvUsers) {
    System.out.println("Name : " + csvUser.getName());
    System.out.println("Email : " + csvUser.getEmail());
    System.out.println("PhoneNo : " + csvUser.getPhoneNo());
    System.out.println("Country : " + csvUser.getCountry());
    System.out.println("==========================");
}

显然,上述方法不适合非常大的 CSV 文件,因为它将整个 CSV 文件内容加载到内存中。

使用@CsvBindByPosition 注释

如果您的 CSV 文件不包含标题,那么您可以使用 @CsvBindByPosition 注释来指定这样的映射 -

import com.opencsv.bean.CsvBindByPosition;

public class CSVUser {
    @CsvBindByPosition(position = 0)
    private String name;

    @CsvBindByPosition(position = 1)
    private String email;

    @CsvBindByPosition(position = 2)
    private String phoneNo;

    @CsvBindByPosition(position = 3)
    private String country;

    // Getters and Setters (Omitted for brevity) 
}

读取 CSV 文件并将记录解析为 Java 对象而不使用注释

如果您不想用 OpenCSV 注释使 POJO 类混乱,那么您可以使用 Mapping 策略来指定 CSV 列和对象成员字段之间的映射。

考虑以下 MyUser 类。

public class MyUser {
    private String name;
    private String email;
    private String phoneNo;
    private String country;

    public MyUser() {

    }

    public MyUser(String name, String email, String phoneNo, String country) {
        this.name = name;
        this.email = email;
        this.phoneNo = phoneNo;
        this.country = country;
    }
	
	// Getters and Setters (Omitted for brevity)
}

以下是如何使用 ColumnPositionMappingStrategy 指定 CSV 列和 Java 对象的成员字段之间的映射,并将 CSV 记录解析为 Java 对象。

import com.opencsv.bean.ColumnPositionMappingStrategy;
import com.opencsv.bean.CsvToBean;
import com.opencsv.bean.CsvToBeanBuilder;

import java.io.IOException;
import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Iterator;
import java.util.List;

public class OpenCSVParseToBeanWithoutAnnotation {
    private static final String SAMPLE_CSV_FILE_PATH = "./users-with-header.csv";

    public static void main(String[] args) throws IOException {
        try (
            Reader reader = Files.newBufferedReader(Paths.get(SAMPLE_CSV_FILE_PATH));
        ) {
            ColumnPositionMappingStrategy strategy = new ColumnPositionMappingStrategy();
            strategy.setType(MyUser.class);
            String[] memberFieldsToBindTo = {"name", "email", "phoneNo", "country"};
            strategy.setColumnMapping(memberFieldsToBindTo);

            CsvToBean<MyUser> csvToBean = new CsvToBeanBuilder(reader)
                    .withMappingStrategy(strategy)
                    .withSkipLines(1)
                    .withIgnoreLeadingWhiteSpace(true)
                    .build();

            Iterator<MyUser> myUserIterator = csvToBean.iterator();

            while (myUserIterator.hasNext()) {
                MyUser myUser = myUserIterator.next();
                System.out.println("Name : " + myUser.getName());
                System.out.println("Email : " + myUser.getEmail());
                System.out.println("PhoneNo : " + myUser.getPhoneNo());
                System.out.println("Country : " + myUser.getCountry());
                System.out.println("---------------------------");
            }
        }
    }
}

ColumnPositionMappingStrategy 用于声明基于位置的映射。在上面的例子中,我们将第一列绑定到 name 字段,第二列绑定到 email 字段等等......

生成 CSV 文件

您可以从字符串数组或对象列表生成 CSV 文件。

从字符串数组生成 CSV 文件

下面的示例显示了如何通过将字符串数组写入 CSV 文件的每一行来生成 CSV 文件。

import com.opencsv.CSVWriter;

import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.io.IOException;

public class OpenCSVWriter {
    private static final String STRING_ARRAY_SAMPLE = "./string-array-sample.csv";

    public static void main(String[] args) throws IOException {
        try (
            Writer writer = Files.newBufferedWriter(Paths.get(STRING_ARRAY_SAMPLE));

            CSVWriter csvWriter = new CSVWriter(writer,
                    CSVWriter.DEFAULT_SEPARATOR,
                    CSVWriter.NO_QUOTE_CHARACTER,
                    CSVWriter.DEFAULT_ESCAPE_CHARACTER,
                    CSVWriter.DEFAULT_LINE_END);
        ) {
            String[] headerRecord = {"Name", "Email", "Phone", "Country"};
            csvWriter.writeNext(headerRecord);

            csvWriter.writeNext(new String[]{"Sundar Pichai ♥", "sundar.pichai@gmail.com", "+1-1111111111", "India"});
            csvWriter.writeNext(new String[]{"Satya Nadella", "satya.nadella@outlook.com", "+1-1111111112", "India"});
        }
    }
}
从对象列表生成 CSV 文件

最后,以下示例展示了如何从对象列表生成 CSV 文件。该示例使用上一节中定义的 MyUser 类 -

import com.opencsv.CSVWriter;
import com.opencsv.bean.StatefulBeanToCsv;
import com.opencsv.bean.StatefulBeanToCsvBuilder;
import com.opencsv.exceptions.CsvDataTypeMismatchException;
import com.opencsv.exceptions.CsvRequiredFieldEmptyException;

import java.io.IOException;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

public class OpenCSVWriter {
    private static final String OBJECT_LIST_SAMPLE = "./object-list-sample.csv";

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

        try (
            Writer writer = Files.newBufferedWriter(Paths.get(STRING_ARRAY_SAMPLE));
        ) {
            StatefulBeanToCsv<MyUser> beanToCsv = new StatefulBeanToCsvBuilder(writer)
                    .withQuotechar(CSVWriter.NO_QUOTE_CHARACTER)
                    .build();

            List<MyUser> myUsers = new ArrayList<>();
            myUsers.add(new MyUser("Sundar Pichai ♥", "sundar.pichai@gmail.com", "+1-1111111111", "India"));
            myUsers.add(new MyUser("Satya Nadella", "satya.nadella@outlook.com", "+1-1111111112", "India"));

            beanToCsv.write(myUsers);
        }
    }
}

结论

这就是所有的人!在本文中,我们研究了使用 OpenCSV 库在 Java 中读取和写入 CSV 文件的不同方法。

您可以在 my github repository 中找到本文中提供的所有代码示例。

相关文章