首页 > 解决方案 > 我需要获取一个 CSV 文件并根据列标题 [JAVA] 将其拆分为单独的文件

问题描述

我对Java相当陌生,并且正在努力阅读>排序>导出csv。我有一个带有 [X, Y, Z, Scalar 1, Scalar 2, Scalar 3, Scalar 4] 的 csv 作为需要分成 4 个 csv 的标题。实际文件长达数千行,如此简短的示例:

[X,Y,Z, Sc1, Sc2, Sc3, Sc4]
[1,0,0,   5,   7,   9,  10]
[0,1,1,   6,   8,   4,   0]
[0,0,1,   3,   3,   8,   2]

我需要将源 csv 拆分为 4 个单独的 csv,其中包含一个标量值和 x、y、z 数据。

File 1       | File 2       | File 3       | File 4
----------------------------------------------------------
[Sc1, X,Y,Z] | [Sc2, X,Y,Z] | [Sc3, X,Y,Z] | [Sc4,  X,Y,Z]
[5,   1,0,0] | [7,   1,0,0] | [9,   1,0,0] | [10,   1,0,0]
[6,   0,1,1] | [8,   0,1,1] | [4,   0,1,1] | [ 0,   0,1,1]
[3,   0,0,1] | [3,   0,0,1] | [8,   0,0,1] | [ 2,   0,0,1]

我目前正在使用 BufferedReader 读取数据,但我不确定一旦读取数据后如何组织数据,或者这是否是一个好方法。

 ArrayList<String> readFileFast (String expDir,String filename) {
        String path = expDir + filename;
        ArrayList<String> fileContents = new ArrayList<>();
        try {
            BufferedReader br = new BufferedReader(new FileReader(path));
            String line;
            while ((line = br.readLine()) != null) {
                fileContents.add(line);
            }
        } catch (Exception e) {
            SuperStackPrint(e);
        }
        return fileContents;
      }

println(readFileFast(expDir, "/DELETEME.csv"));

任何有关如何正确执行此操作的见解将不胜感激。

标签: javacsvsortingexport-to-csv

解决方案


您将受益于使用专门读取和写入 CSV 文件的库。有几个可供选择,但在这里我将使用OpenCSV

如果您最终没有使用这个库,它至少可以为您提供一些关于您自己的方法的想法。

此外,在使用库时,我建议使用 Maven 或 Gradle 等工具来帮助管理这一点,因为这些工具会为您处理“依赖项的依赖关系”——例如,OpenCSV 库本身需要访问它的其他库的地方用途。

对于 Maven,这是我的 POM 文件的 OpenCSV 依赖项:

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

该方法:

  1. 创建一个 Java 类(一个“bean”)来保存将从 CSV 源文件加载的数据。这将SplitBean在我的示例中调用。

  2. 使用此类创建对象集合,其中每个对象都包含 CSV 文件中一行的数据

  3. 遍历这个对象集合,将相关部分写入 4 个输出文件。

您可以选择在不使用 OpenCSV 或类似库的情况下遵循上述方法。但是您必须自己编写更多与基本 CSV 操作相关的代码。在你的情况下,数据并不复杂,所以这不是不合理的。

无论哪种方式,我建议创建一个类来表示一行输入数据,然后在写入输出文件时处理这些对象的列表。这将流程分成 2 个不同的步骤,并利用 Java 对象来简化流程。

这是SplitBean课程:

import com.opencsv.bean.CsvBindByName;
        
public class SplitBean {
    @CsvBindByName(column = "X")
    private int x;

    @CsvBindByName(column = "Y")
    private int y;

    @CsvBindByName(column = "Z")
    private int z;
    
    @CsvBindByName(column = "Sc1")
    private int  sc1;

    @CsvBindByName(column = "Sc2")
    private int  sc2;

    @CsvBindByName(column = "Sc3")
    private int  sc3;

    @CsvBindByName(column = "Sc4")
    private int  sc4;

    public static String[] getHeadingsOne() {
        String[] s = { "Sc1", "X", "Y", "Z" };
        return s;
    }
    
    public static String[] getHeadingsTwo() {
        String[] s = { "Sc2", "X", "Y", "Z" };
        return s;
    }
    
    public static String[] getHeadingsThree() {
        String[] s = { "Sc3", "X", "Y", "Z" };
        return s;
    }
    
    public static String[] getHeadingsFour() {
        String[] s = { "Sc4", "X", "Y", "Z" };
        return s;
    }
    
    public String[] getDataOne() {
        String[] i = { String.valueOf(sc1), String.valueOf(x), 
            String.valueOf(y), String.valueOf(z) };
        return i;
    }
    
    public String[] getDataTwo() {
        String[] i = { String.valueOf(sc2), String.valueOf(x), 
            String.valueOf(y), String.valueOf(z) };
        return i;
    }
    
    public String[] getDataThree() {
        String[] i = { String.valueOf(sc3), String.valueOf(x), 
            String.valueOf(y), String.valueOf(z) };
        return i;
    }
    
    public String[] getDataFour() {
        String[] i = { String.valueOf(sc4), String.valueOf(x), 
            String.valueOf(y), String.valueOf(z) };
        return i;
    }
    
    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    public int getZ() {
        return z;
    }

    public void setZ(int z) {
        this.z = z;
    }

    public int getSc1() {
        return sc1;
    }

    public void setSc1(int sc1) {
        this.sc1 = sc1;
    }

    public int getSc2() {
        return sc2;
    }

    public void setSc2(int sc2) {
        this.sc2 = sc2;
    }

    public int getSc3() {
        return sc3;
    }

    public void setSc3(int sc3) {
        this.sc3 = sc3;
    }

    public int getSc4() {
        return sc4;
    }

    public void setSc4(int sc4) {
        this.sc4 = sc4;
    }
    
}

此类使用@CsvBindByName注释将源 CSV 文件中的列标题名称映射到类本身中的字段名称。你不需要这样做,但它是 OpenCSV 提供的一个方便的功能。

该类还包含处理 4 个不同输出文件(输入文件数据的子集)的方法。

现在我们可以编写一个单独的doTheSplit()方法来使用这个类:

import com.opencsv.bean.CsvToBean;
import com.opencsv.bean.CsvToBeanBuilder;
import com.opencsv.bean.HeaderColumnNameMappingStrategy;
import com.opencsv.exceptions.CsvDataTypeMismatchException;
import com.opencsv.exceptions.CsvRequiredFieldEmptyException;
import com.opencsv.CSVWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.FileWriter;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;

public class SplitData {

    public void doTheSplit() throws URISyntaxException, IOException,
            CsvDataTypeMismatchException, CsvRequiredFieldEmptyException {
        HeaderColumnNameMappingStrategy msIn = new HeaderColumnNameMappingStrategy();
        msIn.setType(SplitBean.class);

        Path path = Paths.get("C:/tmp/csvsplit/input.csv");
        List<SplitBean> list;

        // read the data from the input CSV file into our SplitBean list:
        try ( Reader reader = Files.newBufferedReader(path)) {
            CsvToBean cb = new CsvToBeanBuilder(reader)
                    .withMappingStrategy(msIn)
                    .build();
            list = cb.parse();
            int i = 1;
        }

        // set up 4 file writers:
        try ( CSVWriter writer1 = new CSVWriter(new FileWriter("C:/tmp/csvsplit/output1.csv"));
                CSVWriter writer2 = new CSVWriter(new FileWriter("C:/tmp/csvsplit/output2.csv"));
                CSVWriter writer3 = new CSVWriter(new FileWriter("C:/tmp/csvsplit/output3.csv"));
                CSVWriter writer4 = new CSVWriter(new FileWriter("C:/tmp/csvsplit/output4.csv"))) {

            // first write the headers to each file (false = no quotes):
            writer1.writeNext(SplitBean.getHeadingsOne(), false);
            writer2.writeNext(SplitBean.getHeadingsTwo(), false);
            writer3.writeNext(SplitBean.getHeadingsThree(), false);
            writer4.writeNext(SplitBean.getHeadingsFour(), false);
            
            // then write each row of data (false = no quotes):
            for (SplitBean item : list) {
                writer1.writeNext(item.getDataOne(), false);
                writer2.writeNext(item.getDataTwo(), false);
                writer3.writeNext(item.getDataThree(), false);
                writer4.writeNext(item.getDataFour(), false);
            }
        }
    }

}

此代码的第一部分填充一个List<SplitBean> list. 输入电子表格中的每一行数据都有一个 splitBean 对象。OpenCSV 在幕后为您处理大部分工作。

然后,代码创建了 4 个使用 OpenCSVCSVWriter对象的文件编写器,以帮助将我们的数据格式化为有效的 CSV 行。

使用此代码,我们将列标题写入 4 个文件中的每一个。最后,我们遍历我们的SplitBean项目集合,并将相关数据子集写入每个文件。

因此,对于这样的 CSV 输入文件:

X,Y,Z,Sc1,Sc2,Sc3,Sc4
1,0,0,5,7,9,10
0,1,1,6,8,4,0
0,0,1,3,3,8,2

我们最终得到 4 个不同的输出文件。一个例子:

Sc1,X,Y,Z
5,1,0,0
6,0,1,1
3,0,0,1

附加说明:以这种方式使用类的一大优势SplitBean是,如果您决定需要执行更多数据转换 - 例如,过滤掉数据行或以不同方式对数据进行排序,您将拥有更大的灵活性。


推荐阅读