Java根据列日期对csv文件进行排序

时间:2015-01-14 03:27:47

标签: java arrays sorting csv

需要根据日期列对csv文件进行排序。这就是masterRecords数组列表的样子

GBEP-1-2-4,FRAG,PMTypeEthernet,NEND,TDTN,15-MIN,Dec 15 2014  - 07:15:00 AM MYT,+0,COMPL
GBEP-1-2-1,FRAG,PMTypeEthernet,NEND,TDTN,15-MIN,Dec 15 2014  - 07:00:00 AM MYT,+0,COMPL
GBEP-2-2-1,FRAG,PMTypeEthernet,NEND,TDTN,15-MIN,Dec 15 2014  - 07:30:00 AM MYT,+0,COMPL

我需要根据日期07:15:00,07:30:00等进行排序。我创建了一个代码来对其进行排序:

// Date is fixed on per 15min interval
ArrayList<String> sortDate = new ArrayList<String>();
    sortDate.add(":00:");
    sortDate.add(":15:");
    sortDate.add(":30:");
    sortDate.add(":45:");

    BufferedWriter bw = new BufferedWriter(new FileWriter(tempPath + filename));

    for (int k = 0; k < sortDate.size(); k++) {
        String date = sortDate.get(k);
        for (int j = 0; j < masterRecords.size(); j++) {
            String[] splitLine = masterRecords.get(j).split(",", -1);
            if (splitLine[10].contains(date)) {
                bw.write(masterRecords.get(j) + System.getProperty("line.separator").replaceAll(String.valueOf((char) 0x0D), ""));
                masterRecords.remove(j);
            }
        }
    }
bw.close();

你可以从上面看到它将循环通过第一个数组(sortDate)并在第二个数组(即masterRecord)上再次循环并将其写入新文件。它似乎正在工作,因为新文件已整理,但我注意到我的masterRecord有10000条记录,但在创建一个新文件后,记录缩小到5000,我假设我如何从主列表中删除记录。谁知道为什么?

2 个答案:

答案 0 :(得分:3)

删除循环内的项目是不安全的。 您必须通过Iterator迭代数组,例如:

List<String> names = ....
Iterator<String> i = names.iterator();
while (i.hasNext()) {
   String s = i.next(); // must be called before you can call i.remove()
   // Do something
   i.remove();
}

文档说:

  

此类的迭代器和listIterator方法返回的迭代器是快速失败的:如果在创建迭代器之后的任何时候对列表进行结构修改,除了通过迭代器自己的remove或add方法之外,迭代器将抛出一个ConcurrentModificationException。因此,面对并发修改,迭代器会快速而干净地失败,而不是在未来不确定的时间冒着任意的,非确定性行为的风险。

答案 1 :(得分:1)

accepted answer Lautaro Cozzani是正确的。

现在完全不同

这里的乐趣是一种完全不同的方法。

我使用了两个库:

Apache Commons CSV

Commons CSV库处理各种版本的CSV的解析。它可以返回文件中的行列表,每行由其CSVRecord对象表示。您可以向第一个字段,第二个字段等请求该对象。

约达时间

Joda-Time负责解析日期时间字符串。

避免使用3个字母的时区代码

注意:Joda-Time拒绝尝试解析三个字母的时区代码MYT。有充分理由:这些3或4个字母代码仅仅是惯例,既不标准也不唯一。我的示例代码假设您的所有数据都使用MYT。我的代码指定了正确的时区名称xxx。我建议您启动创建输入数据的人,以了解proper time zone namesISO 8601字符串格式。

Java 8

我的示例代码需要Java 8,使用新的Lambda语法和“streams”。

示例代码

此示例执行双层排序。首先,行按小时(00,15,30,45)排序。在每个组中,行按日期时间值排序(按年,月,日期和时间排序)。

首先,我们打开.csv文本文件,并将其内容解析为CSVRecord个对象。

String filePathString = "/Users/brainydeveloper/input.csv";
try {
    Reader in = new FileReader( filePathString ); // Get the input file.
    List<CSVRecord> recs = CSVFormat.DEFAULT.parse( in ).getRecords(); // Parse the input file.

接下来,我们将这些CSVRecord对象分别包含在一个更智能的类中,该类提取我们关心的两个值:首先是DateTime,其次是DateTime的小时。请进一步了解该类CsvRecWithDateTimeAndMinute的简单代码。

    List<CsvRecWithDateTimeAndMinute> smartRecs = new ArrayList<>( recs.size() ); // Collect transformed data.
    for ( CSVRecord rec : recs ) { // For each CSV record…
        CsvRecWithDateTimeAndMinute smartRec = new CsvRecWithDateTimeAndMinute( rec ); // …transform CSV rec into one of our objects with DateTime and minute-of-hour.
        smartRecs.add( smartRec );
    }

接下来,我们将我们更智能的包装对象列表,并将该列表分成多个列表。每个新列表包含特定分钟(00,15,30和45)的CSV行数据。我们将它们存储在地图中。

如果我们的输入数据仅出现这四个值,则生成的地图将只有四个键。实际上,您可以通过查找四个以上的键来进行健全性检查。额外的密钥意味着在解析时出现了严重错误,或者有一些数据带有意外的分钟值。

每个键(这些数字的整数)都会导致我们的智能包装器对象列表。这是一些奇特的新Lambda语法。

    Map<Integer , List<CsvRecWithDateTimeAndMinute>> byMinuteOfHour = smartRecs.stream().collect( Collectors.groupingBy( CsvRecWithDateTimeAndMinute::getMinuteOfHour ) );

地图不会向我们提供我们的子列表,其中我们的键(小时的整数)已排序。在获得15群组之前,我们可能会返回00群组。因此,提取密钥,并对它们进行排序。

    // Access the map by the minuteOfHour value in order. We want ":00:" first, then ":15", then ":30:", and ":45:" last.
    List<Integer> minutes = new ArrayList<Integer>( byMinuteOfHour.keySet() ); // Fetch the keys of the map.
    Collections.sort( minutes ); // Sort that List of keys.

按照有序键列表,向地图询问每个键的列表。需要对该数据列表进行排序以获得我们的二级排序(按日期 - 时间)。

    List<CSVRecord> outputList = new ArrayList<>( recs.size() ); // Make an empty List in which to put our CSVRecords in double-sorted order.
    for ( Integer minute : minutes ) {
        List<CsvRecWithDateTimeAndMinute> list = byMinuteOfHour.get( minute );
        // Secondary sort. For each group of records with ":00:" (for example), sort them by their full date-time value.
        // Sort the List by defining an anonymous Comparator using new Lambda syntax in Java 8.
        Collections.sort( list , ( CsvRecWithDateTimeAndMinute r1 , CsvRecWithDateTimeAndMinute r2 ) -> {
            return r1.getDateTime().compareTo( r2.getDateTime() );
        } );
        for ( CsvRecWithDateTimeAndMinute smartRec : list ) {
            outputList.add( smartRec.getCSVRecord() );
        }
    }

我们完成了操纵数据。现在是时候导出回CSV格式的文本文件了。

    // Now we have complete List of CSVRecord objects in double-sorted order (first by minute-of-hour, then by date-time).
    // Now let's dump those back to a text file in CSV format.
    try ( PrintWriter out = new PrintWriter( new BufferedWriter( new FileWriter( "/Users/brainydeveloper/output.csv" ) ) ) ) {
        final CSVPrinter printer = CSVFormat.DEFAULT.print( out );
        printer.printRecords( outputList );
    }

} catch ( FileNotFoundException ex ) {
    System.out.println( "ERROR - Exception needs to be handled." );
} catch ( IOException ex ) {
    System.out.println( "ERROR - Exception needs to be handled." );
}

上面的代码一次将整个CSV数据集加载到内存中。如果希望节省内存,请使用parse方法而不是getRecords方法。至少这就是文档似乎在说的内容。我没有尝试过,因为我的用例到目前为止都很容易融入内存。

这是用于包装每个CSVRecord对象的智能类:

package com.example.jodatimeexperiment;

import org.apache.commons.csv.CSVRecord;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

/**
 *
 * @author Basil Bourque
 */
public class CsvRecWithDateTimeAndMinute
{

    // Statics
    static public final DateTimeFormatter FORMATTER = DateTimeFormat.forPattern( "MMM dd yyyy'  - 'hh:mm:ss aa 'MYT'" ).withZone( DateTimeZone.forID( "Asia/Kuala_Lumpur" ) );

    // Member vars.
    private final CSVRecord rec;
    private final DateTime dateTime;
    private final Integer minuteOfHour;

    public CsvRecWithDateTimeAndMinute( CSVRecord recordArg )
    {
        this.rec = recordArg;
        // Parse record to extract DateTime.
        // Expect value such as: Dec 15 2014  - 07:15:00 AM MYT
        String input = this.rec.get( 7 - 1 );  // Index (zero-based counting). So field # 7 = index # 6.
        this.dateTime = CsvRecWithDateTimeAndMinute.FORMATTER.parseDateTime( input );
        // From DateTime extract minute of hour
        this.minuteOfHour = this.dateTime.getMinuteOfHour();
    }

    public DateTime getDateTime()
    {
        return this.dateTime;
    }

    public Integer getMinuteOfHour()
    {
        return this.minuteOfHour;
    }

    public CSVRecord getCSVRecord()
    {
        return this.rec;
    }

    @Override
    public String toString()
    {
        return "CsvRecWithDateTimeAndMinute{ " + " minuteOfHour=" + minuteOfHour + " | dateTime=" + dateTime + " | rec=" + rec + " }";
    }

}

有了这个输入......

GBEP-1-2-4,FRAG,PMTypeEthernet,NEND,TDTN,15-MIN,2014年12月15日 - 07:15:00 AM MYT,+ 0,COMPL GBEP-1-2-1,FRAG,PMTypeEthernet,NEND,TDTN,15-MIN,2014年12月15日 - 上午07:00:00 MYT,+ 0,COMPL GBEP-2-2-1,FRAG,PMTypeEthernet,NEND,TDTN,15-MIN,2014年12月15日 - 07:30:00 MYT,+ 0,COMPL GBEP-1-2-4,FRAG,PMTypeEthernet,NEND,TDTN,15-MIN,2014年12月14日-07:15:00 MYT,+ 0,COMPL GBEP-1-2-1,FRAG,PMTypeEthernet,NEND,TDTN,15-MIN,2014年12月14日 - 07:00:00 MYT,+ 0,COMPL GBEP-2-2-1,FRAG,PMTypeEthernet,NEND,TDTN,15-MIN,2014年12月14日 - 07:30:00 MYT,+ 0,COMPL GBEP-1-2-4,FRAG,PMTypeEthernet,NEND,TDTN,15-MIN,2014年1月22日 - 07:15:00 AM MYT,+ 0,COMPL GBEP-1-2-1,FRAG,PMTypeEthernet,NEND,TDTN,15-MIN,2014年1月22日 - 上午07:00:00 MYT,+ 0,COMPL GBEP-2-2-1,FRAG,PMTypeEthernet,NEND,TDTN,15-MIN,2014年1月22日 - 07:30:00 MYT,+ 0,COMPL

...你会得到这个输出......

GBEP-1-2-1,FRAG,PMTypeEthernet,NEND,TDTN,15-MIN,2014年1月22日 - 上午07:00:00 MYT,+ 0,COMPL GBEP-1-2-1,FRAG,PMTypeEthernet,NEND,TDTN,15-MIN,2014年12月14日 - 07:00:00 MYT,+ 0,COMPL GBEP-1-2-1,FRAG,PMTypeEthernet,NEND,TDTN,15-MIN,2014年12月15日 - 上午07:00:00 MYT,+ 0,COMPL GBEP-1-2-4,FRAG,PMTypeEthernet,NEND,TDTN,15-MIN,2014年1月22日 - 07:15:00 AM MYT,+ 0,COMPL GBEP-1-2-4,FRAG,PMTypeEthernet,NEND,TDTN,15-MIN,2014年12月14日-07:15:00 MYT,+ 0,COMPL GBEP-1-2-4,FRAG,PMTypeEthernet,NEND,TDTN,15-MIN,2014年12月15日 - 07:15:00 AM MYT,+ 0,COMPL GBEP-2-2-1,FRAG,PMTypeEthernet,NEND,TDTN,15-MIN,2014年1月22日 - 07:30:00 MYT,+ 0,COMPL GBEP-2-2-1,FRAG,PMTypeEthernet,NEND,TDTN,15-MIN,2014年12月14日 - 07:30:00 MYT,+ 0,COMPL GBEP-2-2-1,FRAG,PMTypeEthernet,NEND,TDTN,15-MIN,2014年12月15日 - 07:30:00 MYT,+ 0,COMPL

相关问题