Вопрос:
В настоящее время у меня есть программа, которая читает файл (очень большой) в однопоточном режиме и создает индекс поиска, но он занимает слишком много времени для индексации в однопоточной среде.
Теперь я пытаюсь заставить его работать в многопоточном режиме, но не уверен, что это лучший способ.
Моя основная программа создает буферизованный читатель и передает экземпляр в поток, и поток использует буферный экземпляр считывателя для чтения файлов.
Я не думаю, что это работает так, как ожидалось, и каждый поток читает одну и ту же строку снова и снова.
Есть ли способ заставить потоки читать только строки, которые не читаются другим потоком? Нужно ли разбить файл? Есть ли способ реализовать это без разделения файла?
Пример Основной программы:
import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.FileReader; import java.util.ArrayList; public class TestMTFile { public static void main(String args[]) { BufferedReader reader = null; ArrayList<Thread> threads = new ArrayList<Thread>(); try { reader = new BufferedReader(new FileReader( «test.tsv»)); } catch (FileNotFoundException e1) { e1.printStackTrace(); } for (int i = 0; i <= 10; i++) { Runnable task = new ReadFileMT(reader); Thread worker = new Thread(task); // We can set the name of the thread worker.setName(String.valueOf(i)); // Start the thread, never call method run() direct worker.start(); // Remember the thread for later usage threads.add(worker); } int running = 0; int runner1 = 0; int runner2 = 0; do { running = 0; for (Thread thread : threads) { if (thread.isAlive()) { runner1 = running++; } } if (runner2 != runner1) { runner2 = runner1; System.out.println(«We have » + runner2 + » running threads. «); } } while (running > 0); if (running == 0) { System.out.println(«Ended»); } } }
Тема:
import java.io.BufferedReader; import java.io.IOException; public class ReadFileMT implements Runnable { BufferedReader bReader = null; ReadFileMT(BufferedReader reader) { this.bReader = reader; } public synchronized void run() { String line; try { while ((line = bReader.readLine()) != null) { try { System.out.println(line); } catch (Exception e) { } } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } Лучший ответ:
Ваше узкое место скорее всего является индексированием, а не чтением файла. предполагая, что ваша система индексирования поддерживает несколько потоков, вы, вероятно, хотите настроить производителя/потребителя с одним потоком, читающим файл, и нажатием каждой строки в BlockingQueue (продюсер) и несколькими потоками, вытягивающими линии из BlockingQueue и нажатиями их в индекс ( потребителей).
Ответ №1
См. этот поток – если ваши файлы находятся на одном диске, вы не можете сделать лучше, чем чтение их одним потоком, хотя это возможно для обработки файлов с несколькими потоками после их чтения в основной памяти.
Ответ №2
Если вы можете использовать Java 8, вы можете сделать это быстро и легко, используя API Streams. Прочитайте файл в MappedByteBuffer, который может быстро открыть файл до 2 ГБ, а затем прочитать строки из буфера (вам нужно убедиться, что ваша JVM имеет достаточно дополнительной памяти для хранения файла):
package com.objective.stream; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.nio.file.Path; import java.nio.file.Paths; import java.util.stream.Stream; public class StreamsFileProcessor { private MappedByteBuffer buffer; public static void main(String[] args){ if (args[0] != null){ Path myFile = Paths.get(args[0]); StreamsFileProcessor proc = new StreamsFileProcessor(); try { proc.process(myFile); } catch (IOException e) { e.printStackTrace(); } } } public void process(Path file) throws IOException { readFileIntoBuffer(file); getBufferStream().parallel() .forEach(this::doIndex); } private Stream<String> getBufferStream() throws IOException { try (BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(buffer.array())))){ return reader.lines(); } } private void readFileIntoBuffer(Path file) throws IOException{ try(FileInputStream fis = new FileInputStream(file.toFile())){ FileChannel channel = fis.getChannel(); buffer = channel.map(FileChannel.MapMode.PRIVATE, 0, channel.size()); } } private void doIndex(String s){ // Do whatever I need to do to index the line here } } Ответ №3
Во-первых, я согласен с @Zim-Zam в том, что это файл IO, а не индексирование, вероятно, это шаг определения скорости. (Поэтому я не согласен с @jtahlborn). Зависит от того, насколько сложна индексация.
Во-вторых, в вашем коде каждый поток имеет свой собственный, независимый BufferedReader. Поэтому они все прочитают весь файл. Одним из возможных исправлений является использование одного BufferedReader, который они разделяют. И тогда вам нужно синхронизировать метод BufferedReader.readLine() (я думаю), так как javadocs молчат о том, является ли BufferedReader потокобезопасным. И, поскольку я думаю, что я – ботленок, это станет узким местом, и я сомневаюсь, что многопоточность принесет вам много. Но попробуй, я иногда ошибался.: -)
p.s. Я согласен с @jtahlmorn в том, что образец производителя/потребителя лучше, чем моя доля, идея BufferedReader, но для вас это будет намного больше.