Быстрый анализ CSV - PullRequest
       5

Быстрый анализ CSV

15 голосов
/ 28 июля 2011

У меня есть приложение на сервере Java, которое загружает файл CSV и анализирует его.Разбор может занять от 5 до 45 минут и происходит каждый час. Этот метод является узким местом приложения, поэтому он не является преждевременной оптимизацией.Код на данный момент:

        client.executeMethod(method);
        InputStream in = method.getResponseBodyAsStream(); // this is http stream

        String line;
        String[] record;

        reader = new BufferedReader(new InputStreamReader(in), 65536);

        try {
            // read the header line
            line = reader.readLine();
            // some code
            while ((line = reader.readLine()) != null) {
                 // more code

                 line = line.replaceAll("\"\"", "\"NULL\"");

                 // Now remove all of the quotes
                 line = line.replaceAll("\"", "");     


                 if (!line.startsWith("ERROR"){
                   //bla bla 
                    continue;
                 }

                 record = line.split(",");
                 //more error handling
                 // build the object and put it in HashMap
         }
         //exceptions handling, closing connection and reader

Существует ли какая-либо библиотека, которая помогла бы мне ускорить процесс?Могу ли я улучшить существующий код?

Ответы [ 8 ]

18 голосов
/ 28 июля 2011

Apache Commons CSV

Вы видели Apache Commons CSV ?

Предостережение при использовании split

Имейте в виду, что split возвращает только представление данных, что означает, что исходный объект line не подходит для сборки мусора, хотя есть ссылка на любое из его представлений.Возможно, создание защитной копии поможет?( Отчет об ошибках Java )

Также не надежно группировать экранированные столбцы CSV, содержащие запятые

13 голосов
/ 28 июля 2011

opencsv

Взгляните на opencsv .

Это сообщение в блоге, opencsv - простой анализатор CSV , имеет пример использования.

5 голосов
/ 21 октября 2014

Проблема вашего кода в том, что он использует replaceAll и split, что является очень дорогостоящей операцией.Вам определенно следует рассмотреть возможность использования синтаксического анализатора / читателя csv, который будет выполнять однопроходный анализ.

Существует тест на github

https://github.com/uniVocity/csv-parsers-comparison

, который, к сожалению, запускается подJava 6. Число немного отличается в Java 7 и 8. Я пытаюсь получить более подробные данные для файла разного размера, но он в процессе

см. https://github.com/arnaudroger/csv-parsers-comparison

5 голосов
/ 28 июля 2011

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

Ниже приведен краткий анализ и предлагаемое решение

  1. Из кода кажется, что вы читаете данные по сети (чаще всего apache-common-httpclient lib).
  2. Вы должны убедиться, что указанное узкое место не связано с передачей данных по сети.
  3. Один из способов увидеть это просто сбросить данные в некоторый файл (без разбора) и посмотреть, сколько это займет.Это даст вам представление о том, сколько времени фактически потрачено на разбор (по сравнению с текущим наблюдением).
  4. Теперь посмотрим, как используется пакет java.util.concurrent.Вот некоторые ссылки, которые вы можете использовать: ( 1 , 2 )
  5. То, что вы можете сделать, - это задачи, которые вы выполняете в цикле for, которые можно выполнить вТема.
  6. Использование пула потоков и параллелизма значительно улучшит вашу производительность.

Хотя решение требует определенных усилий, но в конце концов оно вам поможет.

2 голосов
/ 28 июля 2011

opencsv

Вы должны взглянуть на OpenCSV . Я ожидаю, что у них есть оптимизация производительности.

1 голос
/ 28 ноября 2018

Немного поздно здесь, теперь есть несколько проектов бенчмаркинга для парсеров CSV. Ваш выбор будет зависеть от точного варианта использования (то есть необработанные данные против привязки данных и т. Д.).

0 голосов
/ 06 июня 2019

Apache Commons CSV ➙ 12 секунд для миллионов строк

Есть ли какая-нибудь библиотека, которая бы помогла мне ускорить процесс?

Да, проект Apache Commons CSV очень хорошо работает по моему опыту.

Вот пример приложения, которое использует библиотеку Apache Commons CSV для записи и чтения строк из 24 столбцов: целочисленное последовательное число, Instant, а остальные являются случайными UUID объектов.

Для 10000 строк запись и чтение занимают примерно полсекунды. Чтение включает восстановление объектов Integer, Instant и UUID.

Мой пример кода позволяет включить или выключить восстановление объектов. Я бегал с миллионами строк. Это создает файл 850 мегабайт. Я использую Java 12 на MacBook Pro (Retina, 15-дюймовый, конец 2013 г.), 2,3 ГГц Intel Core i7, 16 ГБ 1600 МГц DDR3, встроенный твердотельный накопитель Apple.

Для миллиона строк: десять секунд для чтения и две секунды для разбора:

  • Запись: PT25.994816S
  • Только чтение: PT10.353912S
  • Чтение и разбор: PT12.219364S

Исходный код представляет собой один .java файл. Имеет метод записи и метод read. Оба метода вызываются из main метода.

Я открыл BufferedReader, позвонив Files.newBufferedReader.

package work.basil.example;

import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVPrinter;
import org.apache.commons.csv.CSVRecord;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.time.Instant;
import java.util.UUID;

public class CsvReadingWritingDemo
{
    public static void main ( String[] args )
    {
        CsvReadingWritingDemo app = new CsvReadingWritingDemo();
        app.write();
        app.read();
    }

    private void write ()
    {
        Instant start = Instant.now();
        int limit = 1_000_000; // 10_000  100_000  1_000_000
        Path path = Paths.get( "/Users/basilbourque/IdeaProjects/Demo/csv.txt" );
        try (
                Writer writer = Files.newBufferedWriter( path, StandardCharsets.UTF_8 );
                CSVPrinter printer = new CSVPrinter( writer , CSVFormat.RFC4180 );
        )
        {
            printer.printRecord( "id" , "instant" , "uuid_01" , "uuid_02" , "uuid_03" , "uuid_04" , "uuid_05" , "uuid_06" , "uuid_07" , "uuid_08" , "uuid_09" , "uuid_10" , "uuid_11" , "uuid_12" , "uuid_13" , "uuid_14" , "uuid_15" , "uuid_16" , "uuid_17" , "uuid_18" , "uuid_19" , "uuid_20" , "uuid_21" , "uuid_22" );
            for ( int i = 1 ; i <= limit ; i++ )
            {
                printer.printRecord( i , Instant.now() , UUID.randomUUID() , UUID.randomUUID() , UUID.randomUUID() , UUID.randomUUID() , UUID.randomUUID() , UUID.randomUUID() , UUID.randomUUID() , UUID.randomUUID() , UUID.randomUUID() , UUID.randomUUID() , UUID.randomUUID() , UUID.randomUUID() , UUID.randomUUID() , UUID.randomUUID() , UUID.randomUUID() , UUID.randomUUID() , UUID.randomUUID() , UUID.randomUUID() , UUID.randomUUID() , UUID.randomUUID() , UUID.randomUUID() , UUID.randomUUID() );
            }
        } catch ( IOException ex )
        {
            ex.printStackTrace();
        }
        Instant stop = Instant.now();
        Duration d = Duration.between( start , stop );
        System.out.println( "Wrote CSV for limit: " + limit );
        System.out.println( "Elapsed: " + d );
    }

    private void read ()
    {
        Instant start = Instant.now();

        int count = 0;
        Path path = Paths.get( "/Users/basilbourque/IdeaProjects/Demo/csv.txt" );
        try (
                Reader reader = Files.newBufferedReader( path , StandardCharsets.UTF_8) ;
        )
        {
            CSVFormat format = CSVFormat.RFC4180.withFirstRecordAsHeader();
            CSVParser parser = CSVParser.parse( reader , format );
            for ( CSVRecord csvRecord : parser )
            {
                if ( true ) // Toggle parsing of the string data into objects. Turn off (`false`) to see strictly the time taken by Apache Commons CSV to read & parse the lines. Turn on (`true`) to get a feel for real-world load.
                {
                    Integer id = Integer.valueOf( csvRecord.get( 0 ) ); // Annoying zero-based index counting.
                    Instant instant = Instant.parse( csvRecord.get( 1 ) );
                    for ( int i = 3 - 1 ; i <= 22 - 1 ; i++ ) // Subtract one for annoying zero-based index counting.
                    {
                        UUID uuid = UUID.fromString( csvRecord.get( i ) );
                    }
                }
                count++;
                if ( count % 1_000 == 0 )  // Every so often, report progress.
                {
                    //System.out.println( "# " + count );
                }
            }
        } catch ( IOException e )
        {
            e.printStackTrace();
        }

        Instant stop = Instant.now();
        Duration d = Duration.between( start , stop );
        System.out.println( "Read CSV for count: " + count );
        System.out.println( "Elapsed: " + d );
    }
}
0 голосов
/ 09 ноября 2018

Для скорости вы не хотите использовать replaceAll, и вы также не хотите использовать регулярные выражения. То, что вы в основном всегда хотите делать в критических случаях, таких как создание символа конечного автомата за анализатором символов. Я сделал это, свернув все это в функцию Iterable. Он также принимает поток и анализирует его, не сохраняя и не кэшируя. Так что, если вы можете сделать аборт на ранней стадии, это, скорее всего, тоже пойдет хорошо. Он также должен быть достаточно коротким и хорошо закодированным, чтобы было понятно, как он работает.

public static Iterable<String[]> parseCSV(final InputStream stream) throws IOException {
    return new Iterable<String[]>() {
        @Override
        public Iterator<String[]> iterator() {
            return new Iterator<String[]>() {
                static final int UNCALCULATED = 0;
                static final int READY = 1;
                static final int FINISHED = 2;
                int state = UNCALCULATED;
                ArrayList<String> value_list = new ArrayList<>();
                StringBuilder sb = new StringBuilder();
                String[] return_value;

                public void end() {
                    end_part();
                    return_value = new String[value_list.size()];
                    value_list.toArray(return_value);
                    value_list.clear();
                }

                public void end_part() {
                    value_list.add(sb.toString());
                    sb.setLength(0);
                }

                public void append(int ch) {
                    sb.append((char) ch);
                }

                public void calculate() throws IOException {
                    boolean inquote = false;
                    while (true) {
                        int ch = stream.read();
                        switch (ch) {
                            default: //regular character.
                                append(ch);
                                break;
                            case -1: //read has reached the end.
                                if ((sb.length() == 0) && (value_list.isEmpty())) {
                                    state = FINISHED;
                                } else {
                                    end();
                                    state = READY;
                                }
                                return;
                            case '\r':
                            case '\n': //end of line.
                                if (inquote) {
                                    append(ch);
                                } else {
                                    end();
                                    state = READY;
                                    return;
                                }
                                break;
                            case ',': //comma
                                if (inquote) {
                                    append(ch);
                                } else {
                                    end_part();
                                    break;
                                }
                                break;
                            case '"': //quote.
                                inquote = !inquote;
                                break;
                        }
                    }
                }

                @Override
                public boolean hasNext() {
                    if (state == UNCALCULATED) {
                        try {
                            calculate();
                        } catch (IOException ex) {
                        }
                    }
                    return state == READY;
                }

                @Override
                public String[] next() {
                    if (state == UNCALCULATED) {
                        try {
                            calculate();
                        } catch (IOException ex) {
                        }
                    }
                    state = UNCALCULATED;
                    return return_value;
                }
            };
        }
    };
}

Обычно вы обрабатываете это очень усложненно, как:

for (String[] csv : parseCSV(stream)) {
    //<deal with parsed csv data>
}

Красота этого API стоит довольно загадочно выглядящей функции.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...