我正在尝试创建一个简单的类来读取 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?
谢谢, 乔纳
我不确定通用 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 文件可以有多种类型的问题,只需稍微改进一下您的阅读器即可。您所需要的只是一个解析器列表(每列一个),而不是解析所有列的解析器。
希望有帮助:-)
创建一个正确的 CVS 阅读器可能比您想象的更困难。例如,在您的代码示例中,在以下情况下它将无法正常工作。
“微软公司”,1,2,3
您将得到的不是 4 个字段,而是基于 的 5 个字段
StringTokenizer st = new StringTokenizer(line,",");
我的建议是,使用第三方库实现。例如
http://opencsv.sourceforge.net/
我在我的一个应用程序中使用它,并且我的应用程序已经运行了 3 年。到目前为止一切顺利。
如果您正在尝试做真正的工作,我建议您忘记这一点并使用扫描仪。
如果你正在尝试:我会让 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 中的超级类型令牌链接中显示的逻辑,以获得与您的元素类相对应的类对象。
我需要读取 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
这是读取 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);
}
}