Java сортирует 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 ответа

Решение

Не безопасно удалить элемент внутри цикла. Вы должны перебрать массив через 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, работают без сбоев: если список структурно изменяется в любое время после создания итератора, любым способом, кроме использования собственных методов удаления или добавления итератора, итератор создает исключение ConcurrentModificationException. Таким образом, перед одновременной модификацией итератор быстро и чисто дает сбой, вместо того, чтобы рисковать произвольным недетерминированным поведением в неопределенное время в будущем.

Принятый ответ Lautaro Cozzani правильный.

А сейчас нечто соверешнно другое

Для развлечения здесь совершенно другой подход.

Я использовал две библиотеки:

Apache Commons CSV

Библиотека Commons CSV выполняет разбор различных разновидностей CSV. Он может вернуть список строк из файла, каждая строка представлена ​​их CSVRecord объект. Вы можете запросить этот объект для первого поля, второго поля и так далее.

Joda времени

Joda-Time выполняет работу по разбору строк даты и времени.

Избегайте трехбуквенных кодов часовых поясов

Осторожно: Joda-Time отказывается пытаться разобрать трехбуквенный код часового пояса MYT, По уважительной причине: эти 3 или 4 буквенные коды являются простыми соглашениями, ни стандартизированными, ни уникальными. Мой пример кода ниже предполагает, что все ваши данные используют MYT, Мой код назначает правильное имя часового пояса xxx, Я предлагаю вам просветить любого, кто создает ваши входные данные, чтобы узнать о правильных именах часовых поясов и о форматах строк ISO 8601.

Java 8

Мой пример кода требует Java 8, используя новый синтаксис Lambda и "потоки".

Пример кода

Этот пример выполняет двухслойную сортировку. Сначала строки сортируются по минутам часа (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 );
    }

Затем мы берем этот список наших более умных обернутых объектов и разбиваем этот список на несколько списков. Каждый новый список содержит данные строки CSV для определенной минуты часа (00, 15, 30 и 45). Мы храним их на карте.

Если наши входные данные имеют только вхождения этих четырех значений, полученная карта будет иметь только четыре ключа. В самом деле, вы можете проверить работоспособность, найдя более четырех ключей. Дополнительные ключи могут означать, что при синтаксическом анализе что-то пошло не так, или есть данные с неожиданными значениями минут.

Каждый ключ (целое число из этих чисел) ведет к списку наших умных объектов-оболочек. Вот некоторые из этого необычного синтаксиса 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, 15 декабря 2014 г. - 07:15:00 MYT,+0,COMPL GBEP-1-2-1,FRAG,PMTypeEthernet,NEND,TDTN,15-MIN, 15 декабря 2014 г. - 07:00:00 MYT,+0,COMPL GBEP-2-2-1,FRAG,PMTypeEthernet,NEND,TDTN,15-MIN, 15 декабря 2014 г. - 07:30:00:00 MYT, + 0, КОМПЛЕКТ GBEP-1-2-4, FRAG, PMTypeEthernet, NEND, TDTN, 15-МИН, 14 декабря 2014 г. - 07:15:00 MYT, + 0, КОМПЛЕКТ GBEP-1-2- 1, FRAG, PMTypeEthernet, NEND, TDTN, 15-MIN, 14 декабря 2014 г. - 07:00:00 MYT,+0,COMPL GBEP-2-2-1,FRAG,PMTypeEthernet,NEND,TDTN,15-MIN, 14 декабря 2014 г. - 07:30:00 MYT,+0,COMPL GBEP-1-2-4,FRAG,PMTypeEthernet,NEND,TDTN,15-MIN, 22 января 2014 г. - 07:15:00 MYT,+0,COMPL GBEP-1-2-1,FRAG,PMTypeEthernet,NEND,TDTN,15-MIN, 22 января 2014 г. - 07:00:00 MYT,+0,COMPL GBEP-2-2-1,FRAG,PMTypeEthernet,NEND,TDTN,15-MIN, 22 января 2014 г. - 07:30:00 MYT, + 0, COMPL

... вы получите этот вывод...

GBEP-1-2-1, FRAG, PMTypeEthernet, NEND, TDTN, 15-MIN, 22 января 2014 г. - 07:00:00 MYT,+0,COMPL GBEP-1-2-1,FRAG,PMTypeEthernet,NEND,TDTN,15-MIN, 14 декабря 2014 г. - 07:00:00 MYT,+0,COMPL GBEP-1-2-1,FRAG,PMTypeEthernet,NEND,TDTN,15-MIN, 15 декабря 2014 г. - 07:00: 00:00 MYT,+0,COMPL GBEP-1-2-4,FRAG,PMTypeEthernet,NEND,TDTN,15-MIN, 22 января 2014 г. - 07:15:00 MYT, + 0, COMPL GBEP-1-2- 4, FRAG, PMTypeEthernet, NEND, TDTN, 15-MIN, 14 декабря 2014 г. - 07:15:00 MYT,+0,COMPL GBEP-1-2-4,FRAG,PMTypeEthernet,NEND,TDTN,15-MIN, 15 декабря 2014 г. - 07:15:00 MYT,+0,COMPL GBEP-2-2-1,FRAG,PMTypeEthernet,NEND,TDTN,15-MIN, 22 января 2014 г. - 07:30:00 MYT,+0,COMPL GBEP-2-2-1,FRAG,PMTypeEthernet,NEND,TDTN,15-MIN, 14 декабря 2014 г. - 07:30:00 MYT,+0,COMPL GBEP-2-2-1,FRAG,PMTypeEthernet,NEND,TDTN,15-MIN, 15 декабря 2014 г. - 07:30:00 MYT, + 0, COMPL

Другие вопросы по тегам