读取 CSV 文件

JavaBeginner
立即练习

介绍

在本次实验中,你将学习如何在 Java 中读取 CSV(逗号分隔值)文件。CSV 是一种常见的文件格式,用于存储表格数据,如电子表格或数据库导出数据。CSV 文件中的每一行代表一行数据,各列之间用逗号分隔。

你将探索三种不同的方法来在 Java 中读取 CSV 文件:

  • 使用 java.io 包中的 BufferedReader
  • 使用 java.util 包中的 Scanner
  • 使用 OpenCSV 库,这是一个流行的用于 CSV 处理的第三方库

在本次实验结束时,你将能够根据具体需求,为你的 Java 应用程序选择最合适的 CSV 文件读取方法。

创建示例 CSV 文件和项目结构

在开始读取 CSV 文件之前,我们要确保项目已正确设置。在这一步中,我们将查看 CSV 文件的结构,并创建主 Java 类。

了解 CSV 文件

CSV(逗号分隔值)文件以纯文本形式存储表格数据。每行代表一行数据,各列之间用逗号分隔。由于其简单性以及与许多应用程序(如 Excel、Google Sheets 和数据库系统)的兼容性,CSV 文件被广泛用于数据交换。

查看示例 CSV 文件

我们的实验环境中已经有一个示例 CSV 文件,路径为 ~/project/sample.csv。我们先查看一下它的内容:

cat ~/project/sample.csv

你应该会看到以下输出:

name,age,city
John,25,New York
Alice,30,Los Angeles
Bob,28,Chicago
Eve,22,Boston

这个 CSV 文件包含四行数据(包括标题行),内容是关于人员、他们的年龄和所在城市的信息。

创建 Java 类

现在,让我们在 src 目录下创建一个名为 CSVReaderDemo.java 的新 Java 类,我们将在整个实验中使用它。

在 VSCode 中,点击侧边栏的资源管理器图标,导航到 ~/project/src 目录,右键点击该目录,然后选择“新建文件”。将文件命名为 CSVReaderDemo.java

在文件中添加以下基本结构:

public class CSVReaderDemo {
    public static void main(String[] args) {
        System.out.println("CSV Reader Demo");

        // 我们将在接下来的步骤中添加读取 CSV 的代码
    }
}

创建 Java 文件

让我们编译并运行这个 Java 类,以验证一切是否设置正确:

cd ~/project
javac -d . src/CSVReaderDemo.java
java CSVReaderDemo

你应该会看到以下输出:

CSV Reader Demo

太棒了!现在我们的项目结构已经准备好。在接下来的步骤中,我们将实现不同的方法来读取 CSV 文件。

使用 BufferedReader 读取 CSV 文件

在这一步中,我们将实现第一种读取 CSV 文件的方法,即使用 java.io 包中的 BufferedReader 类。这是 Java 中读取文本文件常用且直接的方法。

了解 BufferedReader

BufferedReader 类用于从字符输入流中读取文本,它会对字符进行缓冲,以高效地读取字符、数组和行。你可以指定缓冲区大小,也可以使用默认大小。

使用 BufferedReader 实现 CSV 读取

让我们更新 CSVReaderDemo.java 文件,使用 BufferedReader 来读取 CSV 文件。将文件的全部内容替换为以下代码:

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class CSVReaderDemo {
    public static void main(String[] args) {
        System.out.println("Reading CSV using BufferedReader");

        // Path to our CSV file
        String csvFile = "sample.csv";

        // Lists to store our data
        List<List<String>> data = new ArrayList<>();

        // Try-with-resources to ensure the reader gets closed automatically
        try (BufferedReader br = new BufferedReader(new FileReader(csvFile))) {
            String line;

            // Read each line from the file
            while ((line = br.readLine()) != null) {
                // Split the line by comma and convert to a List
                String[] values = line.split(",");
                List<String> lineData = Arrays.asList(values);

                // Add the line data to our main list
                data.add(lineData);
            }

            // Print the data we read
            System.out.println("\nData read from CSV file:");
            for (int i = 0; i < data.size(); i++) {
                List<String> row = data.get(i);
                System.out.println("Row " + i + ": " + String.join(", ", row));
            }

        } catch (IOException e) {
            System.err.println("Error reading the CSV file: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

更新 Java 文件

让我们编译并运行更新后的代码:

cd ~/project
javac -d . src/CSVReaderDemo.java
java CSVReaderDemo

你应该会看到类似以下的输出:

Reading CSV using BufferedReader

Data read from CSV file:
Row 0: name, age, city
Row 1: John, 25, New York
Row 2: Alice, 30, Los Angeles
Row 3: Bob, 28, Chicago
Row 4: Eve, 22, Boston

代码解释

  1. 我们导入了用于文件 I/O 操作和数据结构的必要 Java 类。
  2. 我们定义了 CSV 文件的路径 (sample.csv)。
  3. 我们创建了一个 List<List<String>> 来将 CSV 数据存储为二维列表。
  4. 我们使用 try-with-resources 语句块,以确保 BufferedReader 在使用后自动关闭。
  5. 我们使用 br.readLine() 逐行读取文件。
  6. 对于每一行,我们使用 line.split(",") 按逗号分割,并将其转换为 List
  7. 我们将每一行添加到主数据列表中。
  8. 最后,我们打印数据以验证是否正确读取。

BufferedReader 方法简单且高效,适用于读取包括 CSV 文件在内的文本文件。然而,在处理更复杂的 CSV 格式(如包含逗号或引号括起来的换行符的字段)时,它存在局限性。

在下一步中,我们将探索另一种使用 Scanner 类的方法。

使用 Scanner 读取 CSV 文件

在这一步中,我们将实现第二种读取 CSV 文件的方法,即使用 java.util 包中的 Scanner 类。Scanner 类提供了一种方便的方式来从各种源读取格式化输入。

了解 Scanner

Scanner 类使用分隔符模式将其输入拆分为标记(token),默认情况下,分隔符匹配空白字符。然后,可以使用各种 next 方法将生成的标记转换为不同类型的值。

使用 Scanner 实现 CSV 读取

让我们更新 CSVReaderDemo.java 文件,使用 Scanner 来读取 CSV 文件。将文件的全部内容替换为以下代码:

import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;

public class CSVReaderDemo {
    public static void main(String[] args) {
        System.out.println("Reading CSV using Scanner");

        // Path to our CSV file
        String csvFile = "sample.csv";

        // Lists to store our data
        List<List<String>> data = new ArrayList<>();

        try (Scanner scanner = new Scanner(new File(csvFile))) {
            // Read each line from the file
            while (scanner.hasNextLine()) {
                String line = scanner.nextLine();

                // Split the line by comma and convert to a List
                String[] values = line.split(",");
                List<String> lineData = Arrays.asList(values);

                // Add the line data to our main list
                data.add(lineData);
            }

            // Print the data we read
            System.out.println("\nData read from CSV file:");
            for (int i = 0; i < data.size(); i++) {
                List<String> row = data.get(i);
                System.out.println("Row " + i + ": " + String.join(", ", row));
            }

        } catch (FileNotFoundException e) {
            System.err.println("CSV file not found: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

让我们编译并运行更新后的代码:

cd ~/project
javac -d . src/CSVReaderDemo.java
java CSVReaderDemo

你应该会看到类似以下的输出:

Reading CSV using Scanner

Data read from CSV file:
Row 0: name, age, city
Row 1: John, 25, New York
Row 2: Alice, 30, Los Angeles
Row 3: Bob, 28, Chicago
Row 4: Eve, 22, Boston

代码解释

  1. 我们导入了用于文件操作、Scanner 和数据结构的必要 Java 类。
  2. 我们定义了 CSV 文件的路径 (sample.csv)。
  3. 我们创建了一个 List<List<String>> 来将 CSV 数据存储为二维列表。
  4. 我们使用 try-with-resources 语句块,以确保 Scanner 在使用后自动关闭。
  5. 只要 scanner.hasNextLine() 返回 true,我们就使用 scanner.nextLine() 逐行读取文件。
  6. 对于每一行,我们使用 line.split(",") 按逗号分割,并将其转换为 List
  7. 我们将每一行添加到主数据列表中。
  8. 最后,我们打印数据以验证是否正确读取。

Scanner 方法与 BufferedReader 方法类似,但它提供了更多方便的方法来解析不同类型的数据。然而,和 BufferedReader 一样,它在处理复杂的 CSV 格式时也存在局限性。

在下一步中,我们将探索一种更强大的方法,即使用 OpenCSV 库,它能更有效地处理复杂的 CSV 格式。

使用 OpenCSV 库读取 CSV 文件

在这一步中,我们将实现第三种读取 CSV 文件的方法,即使用 OpenCSV 库。OpenCSV 是一个第三方库,它提供了强大的 CSV 解析功能,能够处理复杂的场景,例如包含逗号或引号括起来的换行符的字段。

了解 OpenCSV

OpenCSV 是一个用于 Java 的 CSV 解析库,支持所有基本的 CSV 格式变体。与之前的方法不同,OpenCSV 能够正确处理包含逗号、换行符和其他特殊字符的带引号字段,而这些情况会导致简单的按逗号分割方法失效。

配置 OpenCSV

首先,让我们下载 OpenCSV 库及其依赖项:

cd ~/project
mkdir -p lib
curl -L -o lib/opencsv-5.7.1.jar https://repo1.maven.org/maven2/com/opencsv/opencsv/5.7.1/opencsv-5.7.1.jar
curl -L -o lib/commons-lang3-3.12.0.jar https://repo1.maven.org/maven2/org/apache/commons/commons-lang3/3.12.0/commons-lang3-3.12.0.jar
curl -L -o lib/commons-text-1.10.0.jar https://repo1.maven.org/maven2/org/apache/commons/commons-text/1.10.0/commons-text-1.10.0.jar
curl -L -o lib/commons-beanutils-1.9.4.jar https://repo1.maven.org/maven2/commons-beanutils/commons-beanutils/1.9.4/commons-beanutils-1.9.4.jar
curl -L -o lib/commons-collections-3.2.2.jar https://repo1.maven.org/maven2/commons-collections/commons-collections/3.2.2/commons-collections-3.2.2.jar
curl -L -o lib/commons-logging-1.2.jar https://repo1.maven.org/maven2/commons-logging/commons-logging/1.2/commons-logging-1.2.jar

创建更复杂的 CSV 文件

让我们创建一个更复杂的 CSV 文件,其中包含带引号的含逗号字段:

echo 'name,description,price
"Laptop","High-performance laptop, with SSD",999.99
"Smartphone","Latest model, with dual camera",499.99
"Headphones","Noise-canceling, wireless",149.99' > ~/project/products.csv

使用 OpenCSV 实现 CSV 读取

现在,让我们更新 CSVReaderDemo.java 文件,使用 OpenCSV 来读取 CSV 文件。将文件的全部内容替换为以下代码:

import com.opencsv.CSVReader;
import com.opencsv.exceptions.CsvValidationException;
import java.io.FileReader;
import java.io.IOException;

public class CSVReaderDemo {
    public static void main(String[] args) {
        System.out.println("Reading CSV using OpenCSV");

        // Path to our CSV file with complex data
        String csvFile = "products.csv";

        try (CSVReader reader = new CSVReader(new FileReader(csvFile))) {
            // Read and print the header
            String[] header = reader.readNext();
            if (header != null) {
                System.out.println("\nHeader: " + String.join(", ", header));
            }

            // Read and print each line
            String[] nextLine;
            int rowNumber = 1;

            System.out.println("\nData read from CSV file:");
            while ((nextLine = reader.readNext()) != null) {
                System.out.println("Row " + rowNumber + ":");
                for (int i = 0; i < nextLine.length; i++) {
                    System.out.println("  " + header[i] + ": " + nextLine[i]);
                }
                rowNumber++;
                System.out.println();
            }

        } catch (IOException | CsvValidationException e) {
            System.err.println("Error reading the CSV file: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

让我们编译并运行更新后的代码:

cd ~/project
javac -cp ".:lib/*" -d . src/CSVReaderDemo.java
java -cp ".:lib/*" CSVReaderDemo

你应该会看到类似以下的输出:

Reading CSV using OpenCSV

Header: name, description, price

Data read from CSV file:
Row 1:
  name: Laptop
  description: High-performance laptop, with SSD
  price: 999.99

Row 2:
  name: Smartphone
  description: Latest model, with dual camera
  price: 499.99

Row 3:
  name: Headphones
  description: Noise-canceling, wireless
  price: 149.99

代码解释

  1. 我们从 OpenCSV 库和 Java I/O 中导入必要的类。
  2. 我们定义了 CSV 文件的路径 (products.csv)。
  3. 我们创建了一个 CSVReader 对象来读取 CSV 文件。
  4. 我们使用 reader.readNext() 读取标题行并存储起来供后续使用。
  5. 然后,我们在循环中使用 reader.readNext() 读取每一行,直到没有更多行。
  6. 对于每一行,我们打印每个字段及其对应的标题。

OpenCSV 库会自动处理复杂的 CSV 格式,正确解析包含引号括起来的逗号的字段。这使得它非常适合处理可能包含复杂数据的真实世界 CSV 文件。

OpenCSV 的优势

与基本方法相比,OpenCSV 具有以下几个优势:

  1. 它能正确处理包含逗号、换行符和其他特殊字符的带引号字段。
  2. 它提供了将数据读取到 Java 对象(bean)的内置支持。
  3. 它支持自定义分隔符、引号字符和转义字符等高级功能。
  4. 它能高效处理大型 CSV 文件。

对于大多数处理 CSV 文件的实际应用程序,建议使用像 OpenCSV 这样的专用库。

总结

在这个实验中,我们探索了三种在 Java 中读取 CSV 文件的不同方法:

  1. 使用 BufferedReader:这是一种使用标准 Java I/O 库的简单方法。它适用于基本的 CSV 文件,但在处理复杂的 CSV 格式时存在局限性。
  2. 使用 Scanner:这是另一种使用标准 Java 实用工具库的方法。和 BufferedReader 一样,它适用于简单的 CSV 文件,但缺乏对复杂 CSV 格式的支持。
  3. 使用 OpenCSV:这是一种使用专门为 CSV 处理设计的第三方库的强大方法。它能处理复杂的 CSV 格式,包括包含逗号、换行符和其他特殊字符的带引号字段。

每种方法都有其优点和适用场景:

  • 当你想避免外部依赖时,BufferedReaderScanner 是处理简单 CSV 文件的不错选择。
  • 对于处理可能包含复杂数据的真实世界 CSV 文件的应用程序,OpenCSV 是最佳选择。

通过了解这些不同的方法,你可以根据具体需求和 CSV 数据的复杂程度选择最合适的方法。

CSV 文件广泛应用于数据处理、数据交换和数据集成场景。对于 Java 开发者来说,尤其是在专注于数据的应用程序以及与其他系统的集成中,读取和处理 CSV 文件的能力是一项宝贵的技能。