Расщепление огромного CSV с помощью пользовательского фильтра?

1

У меня огромный (> 5 ГБ) CSV файл в формате: имя пользователя, транзакция

Я хочу иметь в качестве отдельного файла CSV для каждого пользователя только с его транзакциями в том же формате. У меня мало идей, но я хочу услышать другие идеи для эффективной (быстрой и эффективной памяти).

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

public class BatchFileReader {


private ICsvBeanReader beanReader;
private double total;
private String[] header;
private CellProcessor[] processors;
private DataTransformer<HashMap<String, List<LoginDto>>> processor;
private boolean hasMoreRecords = true;

public BatchFileReader(String file, DataTransformer<HashMap<String, List<LoginDto>>> processor) {
    try {
        this.processor = processor;
        this.beanReader = new CsvBeanReader(new FileReader(file), CsvPreference.STANDARD_PREFERENCE);
        header = CSVUtils.getHeader(beanReader.getHeader(true));
        processors = CSVUtils.getProcessors();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

public void read() {
    try {
        readFile();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (beanReader != null) {
            try {
                beanReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}

private void readFile() throws IOException {
    while (hasMoreRecords) {

        long start = System.currentTimeMillis();

        HashMap<String, List<LoginDto>> usersBatch = readBatch();

        long end = System.currentTimeMillis();
        System.out.println("Reading batch for " + ((end - start) / 1000f) + " seconds.");
        total +=((end - start)/ 1000f);
        if (processor != null && !usersBatch.isEmpty()) {
            processor.transform(usersBatch);
        }
    }
    System.out.println("total = " + total);
}

private HashMap<String, List<LoginDto>> readBatch() throws IOException {
    HashMap<String, List<LoginDto>> users = new HashMap<String, List<LoginDto>>();
    int readLoginCount = 0;
    while (readLoginCount < CONFIG.READ_BATCH_SIZE) {
        LoginDto login = beanReader.read(LoginDto.class, header, processors);
        if (login != null) {
            if (!users.containsKey(login.getUsername())) {
                List<LoginDto> logins = new LinkedList<LoginDto>();
                users.put(login.getUsername(), logins);
            }
            users.get(login.getUsername()).add(login);
            readLoginCount++;
        } else {
            hasMoreRecords = false;
            break;
        }
    }   
    return users;
}

}

public class BatchFileWriter {

private final String file;

private final List<T> processedData;

public BatchFileWriter(final String file,  List<T> processedData) {
    this.file = file;
    this.processedData = processedData;
}

public void write() {
    try {
        writeFile(file, processedData);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
    }
}

private void writeFile(final String file, final List<T> processedData) throws IOException {
    System.out.println("START WRITE " + "  " + file);
    FileWriter writer = new FileWriter(file, true);

    long start = System.currentTimeMillis();

    for (T record : processedData) {
        writer.write(record.toString());
        writer.write("\n");
    }
    writer.flush();
    writer.close();

    long end = System.currentTimeMillis();
    System.out.println("Writing in file " + file + " complete for " + ((end - start) / 1000f) + " seconds.");

}

}

открытый класс LoginsTest {

private static final ExecutorService executor = Executors.newSingleThreadExecutor();
private static final ExecutorService procExec = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() + 1);

@Test
public void testSingleThreadCSVtoCSVSplit() throws InterruptedException, ExecutionException {
    long start = System.currentTimeMillis();

    DataTransformer<HashMap<String, List<LoginDto>>> simpleSplitProcessor =  new DataTransformer<HashMap<String, List<LoginDto>>>() {
        @Override
        public void transform(HashMap<String, List<LoginDto>> data) {
            for (String field : data.keySet()) {
                new BatchFileWriter<LoginDto>(field + ".csv", data.get(field)).write();
            }
        }

    };

    BatchFileReader reader = new BatchFileReader("loadData.csv", simpleSplitProcessor);
    reader.read();
    long end = System.currentTimeMillis();
    System.out.println("TOTAL " + ((end - start)/ 1000f) + " seconds.");
}

@Test
public void testMultiThreadCSVtoCSVSplit() throws InterruptedException, ExecutionException {

    long start = System.currentTimeMillis();
    System.out.println(start);

    final DataTransformer<HashMap<String, List<LoginDto>>> simpleSplitProcessor =  new DataTransformer<HashMap<String, List<LoginDto>>>() {
        @Override
        public void transform(HashMap<String, List<LoginDto>> data) {
            System.out.println("transform");
            processAsync(data);
        }
    };
    final CountDownLatch readLatch = new CountDownLatch(1);
    executor.execute(new Runnable() {
    @Override
    public void run() {
        BatchFileReader reader = new BatchFileReader("loadData.csv", simpleSplitProcessor);
        reader.read();
        System.out.println("read latch count down");
        readLatch.countDown();
    }});
    System.out.println("read latch before await");
    readLatch.await();
    System.out.println("read latch after await");
    procExec.shutdown();
    executor.shutdown();
    long end = System.currentTimeMillis();
    System.out.println("TOTAL " + ((end - start)/ 1000f) + " seconds.");

}


private void processAsync(final HashMap<String, List<LoginDto>> data) {
    procExec.execute(new Runnable() {
        @Override
        public void run() {
            for (String field : data.keySet()) {
                writeASync(field, data.get(field));
            }
        }

    });     
}

private void writeASync(final String field, final List<LoginDto> data) {
    procExec.execute(new Runnable() {
        @Override
        public void run() {

            new BatchFileWriter<LoginDto>(field + ".csv", data).write();    
        }
    });
}

}

  • 0
    Что вы пробовали и с чем у вас проблемы? Почему бы не прочитать запись файла в файл для каждого пользователя? Чего мне не хватает?
  • 0
    Кстати, многие машины в наши дни имеют более 5 ГБ и могут загружать все это в память. Вы можете обнаружить, что вам вообще не нужно разбивать его. Почему бы не обработать файл как есть?
Показать ещё 7 комментариев
Теги:
multithreading
batch-file
file-io
parsing

3 ответа

1

Не было бы лучше использовать команды unix для сортировки, а затем разделить исходный файл?

Что-то вроде: cat txn.csv | sort> txn-sorted.csv

Оттуда получите список уникальных имен пользователей через grep, а затем grep отсортированный файл для каждого имени пользователя

  • 0
    Я должен написать это в Java, поэтому в основном мне нужно найти лучший алгоритм решения. Кроме того, я не уверен, что сортировка будет быстрой и эффективной.
  • 0
    Вам разрешено использовать фреймворки для этого? Это рабочее задание или задание?
Показать ещё 2 комментария
1

Если вы уже знаете Camel, я бы написал простой маршрут Camel, чтобы: Читать строку из файла. Разбирать строку. Напишите в правильный выходной файл.

Это очень простой маршрут, но если вы хотите его как можно быстрее, тогда это просто тривиально легко сделать его многопоточным

например, ваш маршрут будет выглядеть примерно так:

from("file:/myfile.csv")
.beanRef("lineParser")
.to("seda:internal-queue");

from("seda:internal-queue")
.concurrentConsumers(5)
.to("fileWriter");

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

Многопоточность будет использовать больше памяти, поэтому вам нужно будет сбалансировать эффективность памяти против производительности.

  • 0
    Хорошо, вот что я сделал: 1. запустил поток чтения, который читает пакетами 2. каждый пакет обрабатывается этим потоком в HashMap <String, List <String>, где ключ = имя пользователя, список <String = логины для этого имени пользователя. 3. для каждого ключа я вызываю службу исполнения Java, которая имеет фиксированный размер, а запись в файл выполняется параллельно для всех имен пользователей. 4. когда все записи для текущей партии завершены, новая партия считывается и все повторяется.
  • 0
    Надо сказать, что на обычном ПК производительность не так уж и лучше, благодаря операциям ввода-вывода. Файл объемом 2 ГБ с 1000 тыс. Строк разделяется на 100 секунд в одном потоке, а при многопоточном использовании он равен 60 секундам.
Показать ещё 2 комментария
0

Я бы открыл/добавил новый выходной файл для каждого пользователя. Если вы хотите свести к минимуму использование памяти и навлечь больше накладных расходов ввода-вывода, вы можете сделать что-то вроде следующего, хотя вы, вероятно, захотите использовать настоящий синтаксический анализатор CSV, например Super CSV (http://supercsv.sourceforge.net/index.html):

Scanner s = new Scanner(new File("/my/dir/users-and-transactions.txt"));
while (s.hasNextLine()) {
    String line = s.nextLine();
    String[] tokens = line.split(",");
    String user = tokens[0];
    String transaction = tokens[1];
    PrintStream out = new PrintStream(new FileOutputStream("/my/dir/" + user, true));
    out.println(transaction);
    out.close();
}
s.close();

Если у вас разумный объем памяти, вы можете создать карту имени пользователя для OutputStream. Каждый раз, когда вы видите пользовательскую строку, вы можете получить существующий OutputStream для этого имени пользователя или создать новый, если он не существует.

  • 0
    Это не очень эффективно, потому что для каждого чтения вы открываете / пишете / закрываете.
  • 0
    Да, это достаточно справедливо. Я думал о минимизации использования памяти. Я отредактировал свой ответ соответственно.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню