【问题标题】:Parsing different types of data format from a CSV file从 CSV 文件解析不同类型的数据格式
【发布时间】:2018-11-25 17:55:30
【问题描述】:

我仍然是 Java 编程的初学者,所以如果我的问题过于复杂,我提前道歉。

我的程序是什么? 我正在构建一个基于 GUI 的程序。该程序的目标是加载 CSV、XML 或 JSON 文件,然后程序将数据存储到一个数组中。然后数据将显示在文本框中。最终,该程序将能够将数据绘制到图表中。

界面细节:

  • 3 单选按钮 - 允许用户选择 CSV、XML 或 JSON
  • 加载文件按钮
  • 显示按钮 - 将数据显示到 textArea 中
  • 显示图表按钮
  • 文本区域

问题:我无法将数据存储到数组中。我相信这是因为数据的格式。例如,这是 CSV 文件的前 3 行:

millis,stamp,datetime,light,temp,vcc
1000, 1273010254, 2010/5/4 21:57:34, 333, 78.32, 3.54
2000, 1273010255, 2010/5/4 21:57:35, 333, 78.32, 3.92
3000, 1273010256, 2010/5/4 21:57:36, 344, 78.32, 3.95

(注意 - CSV/XML/JSON 文件中有 52789000 行数据)

CSV-Reader 类包含读取数据、将其存储到数组中然后将其存储到 dataList 的方法。

从上面的例子可以看出,一些数据类型有很大的不同。我在拆分/解析时间和日期变量时遇到了特别的麻烦。

这是我的 CSV-Reader 类代码目前的样子(再次,我为菜鸟代码道歉)。

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

public class CSVReader {

//create a class that will hold arraylist which will have objects representing all lines of the file

private List<Data> dataList = new ArrayList<Data>();
private String path;

public List<Data> getDataList() {
    return dataList;
}

public String getPath() {
    return path;
}
public void setPath(String path) {
    this.path = path;
}

//Create a method to read through the csv stored in the path
//Create the list of data and store in the dataList

public void readCSV() throws IOException{

    //i will create connection with the file, in the path
    BufferedReader in  = new BufferedReader(new FileReader(path));  

    String line = null;
    line = in.readLine();

    while((line = in.readLine())!=null){

        //I need to split and store in the temporary variable and create an object


        String[] splits = line.split("\\s*(=>|,|\\s)\\s*");

        long millis = Long.parseLong(splits[0].trim());
        long stamp = Long.parseLong(splits[1].trim());
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/M/d HH:mm:ss");
        System.out.println(splits[2].trim());
        LocalDateTime dateTime = LocalDateTime.parse(splits[2].trim(), formatter);
        LocalDate dateTime = dateTime.toLocalDate();
        LocalTime time = dateTime.toLocalTime();
        int light = Integer.parseInt(splits[3].trim());
        double temp = Double.parseDouble(splits[4].trim());
        double vcc = Double.parseDouble(splits[5].trim());

        Data d = new Data(millis,stamp,datetime,light,temp,vcc);//uses constructor


        //final job is to add this object 'd' onto the dataList
        dataList.add(d);

    }//end of while loop

}

任何帮助将不胜感激!

编辑 1 - 我认为日期和时间是单独的 CSV 标题。它们不是。因此,时间变量已从程序中删除。它已被替换为 datetime 变量。

编辑 2 - 我的程序现在正在读取 CSV 文件,直到 csv 的第 15 行

27000、1273010280、2010/5/4 21:58:0、288、77.74、3.88

控制台错误

Exception in thread "AWT-EventQueue-0" 
java.time.format.DateTimeParseException: Text **'2010/5/4 21:58:0'** could not 
be parsed at index 15
at java.time.format.DateTimeFormatter.parseResolved0(Unknown Source)
at java.time.format.DateTimeFormatter.parse(Unknown Source)
at java.time.LocalDateTime.parse(Unknown Source)
at CSVReader.readCSV(CSVReader.java:55)
at GUI$2.actionPerformed(GUI.java:85)
at javax.swing.AbstractButton.fireActionPerformed(Unknown Source)
at javax.swing.AbstractButton$Handler.actionPerformed(Unknown Source)
at javax.swing.DefaultButtonModel.fireActionPerformed(Unknown Source)
at javax.swing.DefaultButtonModel.setPressed(Unknown Source)
at javax.swing.plaf.basic.BasicButtonListener.mouseReleased(Unknown Source)
at java.awt.Component.processMouseEvent(Unknown Source)
at javax.swing.JComponent.processMouseEvent(Unknown Source)
at java.awt.Component.processEvent(Unknown Source)
at java.awt.Container.processEvent(Unknown Source)
at java.awt.Component.dispatchEventImpl(Unknown Source)
at java.awt.Container.dispatchEventImpl(Unknown Source)
at java.awt.Component.dispatchEvent(Unknown Source)
at java.awt.LightweightDispatcher.retargetMouseEvent(Unknown Source)
at java.awt.LightweightDispatcher.processMouseEvent(Unknown Source)
at java.awt.LightweightDispatcher.dispatchEvent(Unknown Source)
at java.awt.Container.dispatchEventImpl(Unknown Source)
at java.awt.Window.dispatchEventImpl(Unknown Source)
at java.awt.Component.dispatchEvent(Unknown Source)
at java.awt.EventQueue.dispatchEventImpl(Unknown Source)
at java.awt.EventQueue.access$500(Unknown Source)
at java.awt.EventQueue$3.run(Unknown Source)
at java.awt.EventQueue$3.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
at java.awt.EventQueue$4.run(Unknown Source)
at java.awt.EventQueue$4.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
at java.awt.EventQueue.dispatchEvent(Unknown Source)
at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.run(Unknown Source)

【问题讨论】:

  • 你为什么试图将日期解析为双精度?
  • 因为我觉得那是最合适的……应该是什么?
  • 您可能希望探索java.time 包以获取处理日期和时间的适当类型。

标签: java arrays csv parsing


【解决方案1】:

由于您的date 列是一个字符串,您需要将其解析为一个日期对象。这取决于您的 Java 版本;如果您使用的是 Java 8,则可以使用新的日期类。也许this answer 可以帮助你。

看起来您在 CSV 标题中有两个单独的列,datetime,但这些行在一列中包含日期和时间。这可能是您问题的一部分。您需要确定它们是两列还是一列,但无论如何都可以使用 Java 库对其进行解析。

另外,使用像Apache Commons CSV 这样久经考验的 CSV 库可能是个好主意,而不是滚动您自己的 CSV 解析器。在有限的情况下,您可能会使用自己的方法,但 CSV 并不像最初看起来那么简单。

【讨论】:

  • 感谢您指出 >date 和 >time 标题问题。我通过删除时间变量并创建一个日期时间来更新我的代码。我尝试过使用 CSV 库,例如 OPEN CSV。但是,这项任务已分配给我,我的讲师希望我在没有 CSV 库的情况下完成。
【解决方案2】:

ISO 8601

已解决因此由于我的 CSV 未遵循正确的日期和时间格式(请阅读下面的 cmets),程序崩溃了。

将日期时间值作为文本进行交换时,请使用标准的ISO 8601 格式,而不是自己发明。它们经过明智的设计,易于机器解析,并且易于跨文化的人类阅读。所以,2010-05-04T21:57:34,而不是2010/5/4 21:57:34

java.time 类在解析/生成字符串时默认使用 ISO 8601 格式。

数据类型

日期时间

您的数据馈送的第 2 列和第 3 列代表相同的事物:带有时间的日期。第一个是自纪元参考日期 1970-01-01T00:00Z 以来的整秒计数(Z 表示 UTC)。

因此将两者都包括在内是愚蠢的。如上所述,第 3 列的格式选择不当。在我看来,使用从 epoch 开始计数的第 2 列方法也是一个糟糕的选择,因为它并不明显,没有人可以破译它的含义,因此它会犯不明显的错误,从而使调试和日志记录变得困难。

为了处理我们所拥有的,可以将 seconds-from-epoch 解析为 Instant。此类代表 UTC 中的一个时刻。

Instant instant = Instant.ofEpochMilli( 1_273_010_254L ) ;

您的第 3 列给出了日期和时间,但省略了时区或与 UTC 的偏移量的指示符。由于它在 UTC 中从 1970 年第一刻起解析为秒时与第二列匹配,因此我们知道它的值适用于 UTC。省略此类信息是不好的做法,例如没有货币指标的货币金额。

理想情况下,两列都应替换为 ISO 8601 格式的字符串,例如 2010-05-04T21:57:34Z,其中包括表示 UTC 的 Z

如果我们不得不在不知道它是针对 UTC 的情况下解析第 3 列,我们将解析为 LocalDateTime,这是一个带有时间但缺少时区或偏移量的日期。我们需要定义一个格式化模式来匹配您的输入。

DateTimeFormatter f = DateTimeFormatter.ofPattern( "uuuu/M/d HH:mm:ss" );
LocalDateTime localDateTime = LocalDateTime.parse( "2010/5/4 21:57:34" , f );

BigDecimal

为了准确起见,您的小数位数应表示为 BigDecimal 对象。切勿在您关心准确性的地方使用double/Doublefloat/Float。这些类型使用floating-point 技术,trades away accuracy 用于提高执行速度。相比之下,BigDecimal 速度慢但准确。

从字符串中解析BigDecimal

new BigDecimal ( "78.32" ) 

Apache Commons CSV

当已经存在经过良好测试的代码时,不要编写代码。已经编写了一些库来读取 CSV/Tab-delimited 文件。

我使用Apache Commons CSV 进行此类工作。这些格式有多种变体,均由该库处理。

这里是示例代码。首先定义一个类来保存你的数据,这里命名为Reading

Reading.java

package com.basilbourque.example;

import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDateTime;

public class Reading {
    private Integer millis;
    private Instant instant;
    private LocalDateTime localDateTime;
    private Integer light;
    private BigDecimal temp;
    private BigDecimal vcc;

    public Reading ( Integer millis , Instant instant , LocalDateTime localDateTime , Integer light , BigDecimal temp , BigDecimal vcc ) {
        // TODO: Add checks for null arguments: Objects.requireNonNull( … ).
        this.millis = millis;
        this.instant = instant;
        this.localDateTime = localDateTime;
        this.light = light;
        this.temp = temp;
        this.vcc = vcc;
    }

    @Override
    public String toString ( ) {
        return "com.basilbourque.example.Reading{" +
                "millis=" + millis +
                ", instant=" + instant +
                ", localDateTime=" + localDateTime +
                ", light=" + light +
                ", temp=" + temp +
                ", vcc=" + vcc +
                '}';
    }
}

示例数据文件:

millis,stamp,datetime,light,temp,vcc
1000, 1273010254, 2010/5/4 21:57:34, 333, 78.32, 3.54
2000, 1273010255, 2010/5/4 21:57:35, 333, 78.32, 3.92
3000, 1273010256, 2010/5/4 21:57:36, 344, 78.32, 3.95

现在调用 Commons CSV 来解析该数据,实例化 Reading 对象并收集它们。

DateTimeFormatter f = DateTimeFormatter.ofPattern( "uuuu/M/d HH:mm:ss" );

List < Reading > readings = new ArrayList <>( 3 );
Reader reader = null;
try {
    reader = new FileReader( "/Users/basilbourque/data.csv" );
    Iterable < CSVRecord > records = CSVFormat.RFC4180.withIgnoreSurroundingSpaces( true ).withHeader().parse( reader );
    for ( CSVRecord record : records ) {
        // Grab inputs
        String millisInput = record.get( "millis" );
        String stampInput = record.get( "stamp" );
        String datetimeInput = record.get( "datetime" );
        String lightInput = record.get( "light" );
        String tempInput = record.get( "temp" );
        String vccInput = record.get( "vcc" );

        // Parse inputs
        Integer millis = Integer.valueOf( millisInput );
        Instant instant = Instant.ofEpochSecond( Integer.valueOf( stampInput ) );
        LocalDateTime localDateTime = LocalDateTime.parse( datetimeInput , f );
        Integer light = Integer.valueOf( lightInput );
        BigDecimal temp = new BigDecimal( tempInput );
        BigDecimal vcc = new BigDecimal( vccInput );

        // Construct object
        Reading r = new Reading( millis , instant , localDateTime , light , temp , vcc );

        // Collect object
        readings.add( r );
    }
} catch ( FileNotFoundException e ) {
    e.printStackTrace();
} catch ( IOException e ) {
    e.printStackTrace();
}

System.out.println( readings );

[com.basilbourque.example.Reading{millis=1000, Instant=2010-05-04T21:57:34Z, localDateTime=2010-05-04T21:57:34, light=333, temp=78.32, vcc= 3.54}, com.basilbourque.example.Reading{millis=2000, Instant=2010-05-04T21:57:35Z, localDateTime=2010-05-04T21:57:35, light=333, temp=78.32, vcc=3.92 }, com.basilbourque.example.Reading{millis=3000, Instant=2010-05-04T21:57:36Z, localDateTime=2010-05-04T21:57:36, light=344, temp=78.32, vcc=3.95} ]

关于您的提及:

将数据存储到数组中

您在代码中使用ArrayList,而不是数组。请参阅 Oracle 教程 for listsfor arrays 以了解区别。一般最好使用Java Collections framework。如果大小和速度真的很重要,我们可以选择一个数组。


关于java.time

java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.DateCalendarSimpleDateFormat

Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。

要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310

您可以直接与您的数据库交换 java.time 对象。使用符合JDBC 4.2 或更高版本的JDBC driver。不需要字符串,不需要java.sql.* 类。

从哪里获得 java.time 类?

ThreeTen-Extra 项目通过附加类扩展了 java.time。该项目是未来可能添加到 java.time 的试验场。您可以在这里找到一些有用的类,例如IntervalYearWeekYearQuartermore

【讨论】:

    【解决方案3】:
    1. 您在一行中似乎有 5 个标记(不是 6 个),

    2. 您正试图将日期字符串解析为双精度,

    对您的代码进行一些修改,以下一项对您有用:

    String[] splits = line.split(",");// line.split("\\s*(=>|,|\\s)\\s*");
    
    long millis = Long.parseLong(splits[0].trim());
    long stamp = Long.parseLong(splits[1].trim());
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/M/d HH:mm:ss");
    LocalDateTime dateTime = LocalDateTime.parse(splits[2].trim(), formatter);
    LocalDate date = dateTime.toLocalDate();
    LocalTime time = dateTime.toLocalTime();
    int light = Integer.parseInt(splits[3].trim());
    double temp = Double.parseDouble(splits[4].trim());
    double vcc = Double.parseDouble(splits[5].trim());
    

    【讨论】:

    • 非常感谢您的评论!我已经合并了您的一些代码,但我仍在为错误而苦苦挣扎。如果您不介意再看一遍,我已经更新了我原来的帖子。
    【解决方案4】:

    您正在尝试将date 解析为数字,这是一个字符串字段,也许您可​​以使用它

    Date date=new SimpleDateFormat("yyyy/M/D HH:mm:ss").parse(splits[2]);
    

    现在有了日期,你可以随意变换

    【讨论】:

    • 非常感谢,我已将此方法合并到我的代码中,但我仍然遇到问题。如果您不介意再看一遍,我已经更新了我的原始帖子,谢谢。
    • 您可以尝试将yyyy/M/D HH:mm:ss 更改为yyyy/M/D HH:mm:s 吗?
    • @rfl 您还需要检查 csv 中的小时和分钟是否填充了两个数字,如果我们是 01,您需要输入 HH,但如果它只有 1,您需要输入H 分秒也一样
    • CSV 包含一位数和两位数的日期/时间!
    • >DateTimeFormatter 格式化程序 = DateTimeFormatter.ofPattern("yyyy/M/d H:m:s");已修复我的代码!!! :D 谢谢你的帮助
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-25
    • 2021-09-15
    • 1970-01-01
    • 1970-01-01
    • 2014-02-07
    相关资源
    最近更新 更多