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
- Joda времени
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