创建通用 CsvReader

问题描述 投票:0回答:5

我正在尝试创建一个简单的类来读取 csv 文件并将内容存储在

ArrayList<ArrayList<T>>.  

我正在创建一个通用类 CsvReader,以便我可以处理不同类型的数据:int、double、String。比如说,如果我有一个双打的 csv 文件,我想我会像这样使用我的类:

//possible method 1
CsvReader<Double> reader = new CsvReader<Double>();
ArrayList<ArrayList<Double>> contents = reader.getContents();

//possible method 2
CsvReader reader = new CsvReader(Double.class);
ArrayList<ArrayList<Double>> contents = reader.getContents();

但是方法 1 不起作用,因为类型擦除会阻止您编写类似的代码

rowArrayList.add(new T(columnStringValue)); 

但我什至无法使 Double.class 解决方案中的传递起作用。问题是,真正发生的事情是我需要我的类“参数化”(在这个词的一般意义上,而不是技术性的java泛型意义上)具有以下属性的类型:它有一个接受单个字符串参数的构造函数。也就是说,要在 Double csv 文件上创建行 ArrayLists,我需要编写:

StringTokenizer st = new StringTokenizer(line,",");
ArrayList<Double> curRow = new ArrayList<Double>();
while (st.hasMoreTokens()) {
 curRow.add(new Double(st.nextToken());
}

传入 Double.class 后,我可以使用

获取其 String ctor
  Constructor ctor = c.getConstructor(new Class[] {String.class});

但这有两个问题。最重要的是,这是一个通用构造函数,它将返回一个 Object 类型,然后我无法将其向下转换为 Double。其次,我会缺少“类型”检查,因为我要求传入的类具有 String arg 构造函数。

我的问题是:如何正确实现这个通用的 CsvReader?

谢谢, 乔纳

java
5个回答
7
投票

我不确定通用 CSV 阅读器是否会如此简单地使用(顺便说一下,创建)。

我想到的第一个问题是:如果 CSV 包含三列:首先是整数,然后是字符串,最后是日期,该怎么办?您将如何使用通用 CSV 阅读器?

无论如何,假设您要创建一个 CSV 阅读器,其中所有列都具有相同类型。正如您所说,您无法在“接受

String
作为构造函数”的类型上参数化类。 Java 就是不允许这样做。使用反射的解决方案是一个好的开始。但是,如果您的类在其构造函数之一中不采用
String
作为参数怎么办?

在这里你可以有一个替代方案:一个解析器,它将接受你的字符串并返回正确类型的对象。创建一个通用接口,并为您要爬取的类型进行一些实现:

public interface Parser<T> {

    T parse(String value);

}

然后,实施:

public class StringParser implements Parser<String> {

    public String parse(String value) {
        return value;
    }

}

然后,您的 CSV 阅读器可以将

Parser
作为其参数之一。然后,它可以使用这个解析器将每个
String
转换为Java对象。

通过这个解决方案,您可以摆脱您所使用的不太漂亮的反射。你可以转换为任何类型,你只需要实现一个

Parser

您的阅读器将如下所示:

public CSVReader<T> {

    Parser<T> parser;

    List<T> getValues() {
        // ...
    }

}

现在,回到 CSV 文件可以有多种类型的问题,只需稍微改进一下您的阅读器即可。您所需要的只是一个解析器列表(每列一个),而不是解析所有列的解析器。

希望有帮助:-)


1
投票

创建一个正确的 CVS 阅读器可能比您想象的更困难。例如,在您的代码示例中,在以下情况下它将无法正常工作。

“微软公司”,1,2,3

您将得到的不是 4 个字段,而是基于 的 5 个字段

StringTokenizer st = new StringTokenizer(line,",");

我的建议是,使用第三方库实现。例如

http://opencsv.sourceforge.net/

我在我的一个应用程序中使用它,并且我的应用程序已经运行了 3 年。到目前为止一切顺利。


1
投票

如果您正在尝试做真正的工作,我建议您忘记这一点并使用扫描仪

如果你正在尝试:我会让 CsvReader 成为一个抽象类:

public abstract class  CsvReader<T> {
...
    // This is what you use in the rest of CsvReader
    // to create your objects from the strings in the CSV
    protected abstract T parse(String s);
...
}

它将被用作:

CsvReader<Double> = new CsvReader<Double>() {
    @Override protected Double parse(String s) {
        return Double.valueOf(s);
    }
};
...

不完美,但合理。


编辑:事实证明你可以按照自己的方式进行,尽管它看起来有点黑客。请参阅超级类型令牌。它基本上涉及包括 CsvReader 中的超级类型令牌链接中显示的逻辑,以获得与您的元素类相对应的类对象。


0
投票

我需要读取 CSV 文件单元格中存储的简单字符串列表,并开始寻找 Java 解决方案。我发现大多数开源 CSV 阅读器对于我的目的而言都过于复杂。 (请参阅 https://agiletribe.purplehillsbooks.com/2012/11/23/the-only-class-you-need-for-csv-files/ 进行全面审查)。 最后我发现MKYong的代码非常有效。我必须根据自己的目的对其进行调整,以读取整个 CSV 或 TSV 文件并将其作为列表列表返回。内部列表中的每个元素代表 CSV 的一个单元格。代码以及 MKYong 的致谢信息可以在以下位置找到: https://github.com/ramanraja/CsvReader


0
投票

这是读取 CSV 的通用解决方案。我说有点通用,因为类是参数化的,但仍然要求用户使用 lambda 来映射列。

csv_reader_TEST.csv:

hello,world,1
hello,world,2
hello,world,3

CsvReader 类:

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

/**
 * This class provides generic functions for loading CSV files to objects.
 */

public class CSVReader<T> {

    /**
     * Load the data from CSV into nested list
     */

    public List<List<String>> loadCSVData(String filename) throws IOException, FileNotFoundException {
        List<List<String>> rows = new ArrayList<>();
        String path = getClass().getClassLoader().getResource(filename).getPath();
        try (BufferedReader br = new BufferedReader(new FileReader(path))) {
            String line;
            while ((line = br.readLine()) != null) {
                String[] values = line.split(",");
                rows.add(Arrays.asList(values));
            }
        }
        return rows;
    }

    /**
     * Load the CSV, then return the rows converted using the conversionFunction.
     *
     * @param filename           String
     * @param conversionFunction Function<List<String>>, T>
     * @return List<T>
     */

    public List<T> getObjectsFromCSV(String filename, Function<List<String>, T> conversionFunction)
            throws IOException, FileNotFoundException {
        List<T> objects = new ArrayList<>();
        for (List<String> row : loadCSVData(filename)) {
            objects.add(conversionFunction.apply(row));
        }
        return objects;
    }
}

单元测试:

    @Test
    void testCSVReader() throws IOException {

        class CsvRow {
            String a;
            String b;
            Integer c;
        }

        CSVReader<CsvRow> reader = new CSVReader<>();
        String filename = "csv_reader_TEST.csv";

        List<CsvRow> csvRows = reader.getObjectsFromCSV(filename, row -> {
            CsvRow obj = new CsvRow();
            obj.a = row.get(0);
            obj.b = row.get(1);
            obj.c = Integer.valueOf(row.get(2));
            return obj;
        });

        for (CsvRow row : csvRows) {
            assertEquals(row.a, "hello");
            assertEquals(row.b, "world");
            assertInstanceOf(Integer.class, row.c);
        }
    }
© www.soinside.com 2019 - 2024. All rights reserved.