当前位置: 首页 > 面试题库 >

使用CsvBeanReader读取具有可变列数的CSV文件

刁茂才
2023-03-14
问题内容

所以我正在解析.csv文件。我接受了StackOverflow上另一个线程的建议,并下载了SuperCSV。我终于使几乎所有的东西都能正常工作,但是现在我遇到了一个似乎很难修复的错误。

发生此问题的原因是可能填充了最后两列数据,也可能未填充。这是一个.csv文件的示例,第一行缺少最后一列,第二行完全完成:

2012:07:25,11:48:20,922,“ uLog.exe”,“”,按键,1246,341,-1.00,-1.00,1.00,Shift
2012:07:25,11:48:21,094,“ uLog.exe“,”“,按键,1246,341,-1.00,-1.00,1.00,b,Shift

根据我对Super CSV
Javadoc的
理解,如果列数可变,则无法用CsvBeanReader填充Java
Bean 。这似乎真的很愚蠢,因为我觉得在初始化Bean时应允许这些缺少的列为null或其他一些默认值。

供参考,这是解析器的完整代码:

public class ULogParser {

String uLogFileLocation;
String screenRecorderFileLocation;

private static final CellProcessor[] cellProcessor = new CellProcessor[] {
    new ParseDate("yyyy:MM:dd"),
    new ParseDate("HH:mm:ss"),
    new ParseDate("SSS"),
    new StrMinMax(0, 100),
    new StrMinMax(0, 100),
    new StrMinMax(0, 100),
    new ParseInt(),
    new ParseInt(),
    new ParseDouble(),
    new ParseDouble(),
    new ParseDouble(),
    new StrMinMax(0, 100),
    new StrMinMax(0, 100),
};

public String[] header = {"Date", "Time", "Msec", "Application", "Window", "Message", "X", "Y", "RelDist", "TotalDist", "Rate", "Extra1", "Extra2"};

public ULogParser(String uLogFileLocation, String screenRecorderFileLocation)
{
    this.uLogFileLocation = uLogFileLocation;
    this.screenRecorderFileLocation = screenRecorderFileLocation;
}

public void parse()
{
    try {
        ICsvBeanReader reader = new CsvBeanReader(new BufferedReader(new FileReader(uLogFileLocation)), CsvPreference.STANDARD_PREFERENCE);
        reader.getCSVHeader(false); //parse past the header
        Entry entry;
        entry = reader.read(Entry.class, header, cellProcessor);
        System.out.println(entry.Application);
    } catch (FileNotFoundException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

public void sendToDB()
{
    Query query = new Query();
}
}

以及Entry类的代码:

public class Entry
{
private Date Date;
private Date Time;
private Date Msec;
private String Application;
private String Window;
private String Message;
private int X;
private int Y;
private double RelDist;
private double TotalDist;
private double Rate;
private String Extra1;
private String Extra2;

public Date getDate() { return Date; }
public Date getTime() { return Time; }
public Date getMsec() { return Msec; }
public String getApplication() { return Application; }
public String getWindow() { return Window; }
public String getMessage() { return Message; }
public int getX() { return X; }
public int getY() { return Y; }
public double getRelDist() { return RelDist; }
public double getTotalDist() { return TotalDist; }
public double getRate() { return Rate; }
public String getExtra1() { return Extra1; }
public String getExtra2() { return Extra2; }

public void setDate(Date Date) { this.Date = Date; }
public void setTime(Date Time) { this.Time = Time; }
public void setMsec(Date Msec) { this.Msec = Msec; }
public void setApplication(String Application) { this.Application = Application; }
public void setWindow(String Window) { this.Window = Window; }
public void setMessage(String Message) { this.Message = Message; }
public void setX(int X) { this.X = X; }
public void setY(int Y) { this.Y = Y; }
public void setRelDist(double RelDist) { this.RelDist = RelDist; }
public void setTotalDist(double TotalDist) { this.TotalDist = TotalDist; }
public void setRate(double Rate) { this.Rate = Rate; }
public void setExtra1(String Extra1) { this.Extra1 = Extra1; }
public void setExtra2(String Extra2) { this.Extra2 = Extra2; }

public Entry(){}
}

还有我收到的异常(请注意,这与上面的示例不同,缺少最后两列):

线程“ main”中的异常值数组(大小12)必须与处理器数组(大小13)匹配:您可能正在读取的CSV行的列数与指定的单元处理器的上下文数不同:行:2列:0原始行:
[2012:07:25,11:48:05,740,uLog.exe,,,记录开始,-1,-1,-1.00,-1.00,-1.00,]
 违规处理器:null
    在org.supercsv.util.Util.processStringList(未知来源)
    在org.supercsv.io.CsvBeanReader.read(未知来源)
    在处理时.ULogParser.parse(ULogParser.java:59)
    在ui.ParseImplicitData.main(ParseImplicitData.java:15)

是的,写所有这些getter和setter实在是太难了。另外,我很抱歉,我对SuperCSV的使用可能没有完善的约定(例如,如果只希望使用未修改的String,则要使用什么CellProcessor),但是您明白了。另外,此代码显然不完整。现在,我只是试图成功检索一行数据。

在这一点上,我想知道是否可以出于我的目的使用CsvBeanReader。如果没有,我会有些失望,因为CsvListReader(我会发布超链接,但StackOverflow也不允许我,也很愚蠢)与根本不使用API​​以及仅使用Scanner.next一样容易。
()。

任何帮助,将不胜感激。提前致谢!


问题答案:

编辑: 更新超级CSV
2.0.0-β-1

请注意,API已在Super CSV
2.0.0-beta-1中更改(代码示例基于1.52)。现在getCSVHeader(),所有读者都getHeader()可以使用该方法(与writeHeader作者一致)。

此外,SuperCSVException已重命名为SuperCsvException

编辑: Super CSV 2.1.0更新

从2.1.0版开始,可以使用新方法 读取一行CSV 之后
执行单元处理器executeProcessors()。有关更多信息,请参见项目网站上的此示例。请注意,这仅与有关CsvListReader,因为它是唯一允许可变列长的读取器。

您是正确的- CsvBeanReader不支持列数可变的CSV文件。根据大多数CSV规范(包括RFC
4180),每行的列数必须相同。

因此,(作为超级CSV开发人员)我不愿意将此功能添加到超级CSV中。如果您想出一种优雅的添加方式,请随时在项目的SourceForge网站上提出建议。这可能意味着要扩展一个新的阅读器CsvBeanReader:它将不得不将读取和映射/处理分为两个单独的方法(除非您知道有多少列,否则您无法对bean的字段进行任何处理或映射)

简单的解决方案

解决此问题的简单方法(如果您可以控制要使用的CSV文件)是在编写CSV文件时简单地添加一个空白列(示例中的第一行的末尾带有逗号-
表示最后一列为空)。这样,您的CSV文件将是有效的(每行具有相同的列数),并且您可以CsvBeanReader按照自己的方式使用。

如果那不可能,那么 一切就不会丢失

花式的解决方案

您可能已经意识到,CsvBeanReader使用名称映射将CSV文件中的每一列与bean中的一个字段相关联,并使用CellProcessor数组来处理每一列。换句话说,如果要使用它,您必须知道有多少列(及其代表什么)。

CsvListReader另一方面,它非常原始,可以读取不同长度的行(因为它不需要处理或映射它们)。

因此,您可以通过同时使用两个阅读器读取文件来组合CsvBeanReaderwith的所有功能CsvListReader(如以下示例中所述):CsvListReader用于找出有多少列,并CsvBeanReader进行处理/映射。

请注意,这是假设仅可能不存在birthDate列(即,如果您无法确定缺少哪一列,它将不起作用)。

package example;

import java.io.StringReader;
import java.util.Date;

import org.supercsv.cellprocessor.ParseDate;
import org.supercsv.cellprocessor.ift.CellProcessor;
import org.supercsv.exception.SuperCSVException;
import org.supercsv.io.CsvBeanReader;
import org.supercsv.io.CsvListReader;
import org.supercsv.io.ICsvBeanReader;
import org.supercsv.io.ICsvListReader;
import org.supercsv.prefs.CsvPreference;

public class VariableColumns {

    private static final String INPUT = "name,birthDate,city\n"
        + "John,New York\n" 
        + "Sally,22/03/1974,London\n" 
        + "Jim,Sydney";

    // cell processors
    private static final CellProcessor[] NORMAL_PROCESSORS = 
    new CellProcessor[] {null, new ParseDate("dd/MM/yyyy"), null };
    private static final CellProcessor[] NO_BIRTHDATE_PROCESSORS = 
    new CellProcessor[] {null, null };

    // name mappings
    private static final String[] NORMAL_HEADER = 
    new String[] { "name", "birthDate", "city" };
    private static final String[] NO_BIRTHDATE_HEADER = 
    new String[] { "name", "city" };

    public static void main(String[] args) {

        // using bean reader and list reader together (to read the same file)
        final ICsvBeanReader beanReader = new CsvBeanReader(new StringReader(
                INPUT), CsvPreference.STANDARD_PREFERENCE);
        final ICsvListReader listReader = new CsvListReader(new StringReader(
                INPUT), CsvPreference.STANDARD_PREFERENCE);

        try {
            // skip over header
            beanReader.getCSVHeader(true);
            listReader.getCSVHeader(true);

            while (listReader.read() != null) {

                final String[] nameMapping;
                final CellProcessor[] processors;

                if (listReader.length() == NORMAL_HEADER.length) {
                    // all columns present - use normal header/processors
                    nameMapping = NORMAL_HEADER;
                    processors = NORMAL_PROCESSORS;

                } else if (listReader.length() == NO_BIRTHDATE_HEADER.length) {
                    // one less column - birth date must be missing
                    nameMapping = NO_BIRTHDATE_HEADER;
                    processors = NO_BIRTHDATE_PROCESSORS;

                } else {
                    throw new SuperCSVException(
                            "unexpected number of columns: "
                                    + listReader.length());
                }

                // can now use CsvBeanReader safely 
                // (we know how many columns there are)
                Person person = beanReader.read(Person.class, nameMapping,
                        processors);

                System.out.println(String.format(
                        "Person: name=%s, birthDate=%s, city=%s",
                        person.getName(), person.getBirthDate(),
                        person.getCity()));

            }
        } catch (Exception e) {
            // handle exceptions here
            e.printStackTrace();
        } finally {
            // close readers here
        }
    }

    public static class Person {

        private String name;
        private Date birthDate;
        private String city;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public Date getBirthDate() {
            return birthDate;
        }

        public void setBirthDate(Date birthDate) {
            this.birthDate = birthDate;
        }

        public String getCity() {
            return city;
        }

        public void setCity(String city) {
            this.city = city;
        }
    }

}

我希望这有帮助。

哦,您的Entry类中的字段为何不遵循正常的命名约定(camelCase)有什么理由吗?如果将header阵列更新为使用camelcase,则字段也可以是camelcase。



 类似资料:
  • 我已经使用读取和解析csv文件,列由分号分隔。一些文件在某些列之后有(出于未知原因)一个序列,这使得将它们拆分为不同的行。我想转义这些字符并将“第二行”附加到“第一行”,否则之后解析会变得困难。 我可以识别这些行,因为它们后面是数字,而正确的第一列包含时间,如00:00:00。熊猫有可能做到这一点吗。是否读取csv? 实例 如果文件正确,我的代码如下所示: 输出: 问题 如果文件已损坏,则如下所示

  • 我想从多列csv文件中读取特定列,并使用Java在其他csv文件中打印这些列。需要帮忙吗?下面是我逐行打印每个令牌的代码。。但我希望只打印多列csv中的几列。

  • 示例CSV: 我试图只捕获特定的列,例如、、和。 我看到的代码使我相信我可以通过相应的编号调用特定的列,因此:将对应于,使用迭代每一行将产生第2列中的所有项。只是它没有。

  • 我如何完成这个任务?

  • 我正在尝试读取Mac上pig shell上的csv文件。我所做的只是文件到变量中,然后变量。我是这样做的: 我使用的数据是从这里提供的github下载的 此文件在我的Mac上的本地安装的hdfs中可用。当我执行时,我得到一个错误: org.apache.pig.impl.logicallayer.FrontendException:错误1066:无法打开别名影片的迭代器 在org.apache.p

  • 问题内容: 进行时: 与此文件: (第一个列是UNIX时间戳,即自1970年1月1日起经过的秒数),当我每15秒对数据进行一次重采样时出现以下错误: 就像“ datetime”信息尚未解析: 如何使用熊猫模块导入带有存储为时间戳的日期的.CSV? 然后,一旦我能够导入CSV, 如何访问日期 > 2015-12-02 12:02:18的行? 问题答案: 我的解决方案类似于Mike的解决方案: