Прочитайте идентификатор пользователя 30Million один за другим из большого файла

Я пытаюсь прочитать очень большой файл с помощью Java. Этот большой файл будет иметь такие данные, то есть каждая строка будет иметь идентификатор пользователя.

149905320
1165665384
66969324
886633368
1145241312
286585320
1008665352

И в этом большом файле будет около 30 миллионов идентификаторов пользователя. Теперь я пытаюсь прочитать каждый идентификатор пользователя один за другим из этого большого файла только один раз. Значение каждого идентификатора пользователя должно быть выбрано только один раз из этого большого файла. Например, если у меня есть идентификатор пользователя 30 миллионов, тогда он должен печатать 30 миллионов идентификаторов пользователя только один раз с использованием кода многопоточности.

Ниже приведен код, который является многопоточным кодом, работающим с 10 потоками, но с приведенной ниже программой, я не могу убедиться, что каждый пользовательский идентификатор выбран только один раз.

public class ReadingFile {


    public static void main(String[] args) {

        // create thread pool with given size
        ExecutorService service = Executors.newFixedThreadPool(10);

        for (int i = 0; i < 10; i++) {
            service.submit(new FileTask());
        }
    }
}

class FileTask implements Runnable {

    @Override
    public void run() {

        BufferedReader br = null;
        try {
            br = new BufferedReader(new FileReader("D:/abc.txt"));
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
                //do things with line
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                br.close();
            } catch (IOException e) {

                e.printStackTrace();
            }
        }
    }
}

Может кто-нибудь мне помочь? Что я плохо делаю? И какой самый быстрый способ сделать это?

Ответ 1

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

Изменить: это способ распараллеливать обработку строк, все еще используя последовательный ввод-вывод для чтения строк. Он использует BlockingQueue для связи между потоками; FileTask добавляет строки в очередь, а CPUTask считывает их и обрабатывает их. Это потокобезопасная структура данных, поэтому нет необходимости добавлять к ней какую-либо синхронизацию. Вы используете put(E e) для добавления строк в очередь, поэтому, если очередь заполнена (она может содержать до 200 строк, как определено в объявлении в ReadingFile), блоки FileTask до тех пор, пока пространство не будет освобождено; Аналогично, вы используете take() для удаления элементов из очереди, поэтому CPUTask будет блокироваться до тех пор, пока элемент не будет доступен.

public class ReadingFile {
    public static void main(String[] args) {

        final int threadCount = 10;

        // BlockingQueue with a capacity of 200
        BlockingQueue<String> queue = new ArrayBlockingQueue<>(200);

        // create thread pool with given size
        ExecutorService service = Executors.newFixedThreadPool(threadCount);

        for (int i = 0; i < (threadCount - 1); i++) {
            service.submit(new CPUTask(queue));
        }

        // Wait til FileTask completes
        service.submit(new FileTask(queue)).get();

        service.shutdownNow();  // interrupt CPUTasks

        // Wait til CPUTasks terminate
        service.awaitTermination(365, TimeUnit.DAYS);

    }
}

class FileTask implements Runnable {

    private final BlockingQueue<String> queue;

    public FileTask(BlockingQueue<String> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        BufferedReader br = null;
        try {
            br = new BufferedReader(new FileReader("D:/abc.txt"));
            String line;
            while ((line = br.readLine()) != null) {
                // block if the queue is full
                queue.put(line);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                br.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

class CPUTask implements Runnable {

    private final BlockingQueue<String> queue;

    public CPUTask(BlockingQueue<String> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        String line;
        while(true) {
            try {
                // block if the queue is empty
                line = queue.take(); 
                // do things with line
            } catch (InterruptedException ex) {
                break; // FileTask has completed
            }
        }
        // poll() returns null if the queue is empty
        while((line = queue.poll()) != null) {
            // do things with line;
        }
    }
}

Ответ 2

Мы говорим о среднем файле 315 МБ с линиями, разделенными новой строкой. Я предполагаю, что это легко вписывается в память. Подразумевается, что в именах пользователей нет определенного порядка, который должен быть сохранен. Поэтому я бы рекомендовал следующий алгоритм:

  • Получить длину файла
  • Скопируйте каждый десятый файл в буфер байта (двоичная копия должна быть быстрой)
  • Запустите поток для обработки каждого из этих буферов.
  • Каждый поток обрабатывает все строки в своей области, кроме первой и последней.
  • Каждый поток должен возвращать первую и последнюю частичную строку в своих данных, когда это делается,
  • "последний" каждого потока должен быть рекомбинирован с "первым" одним из тех, кто работает над следующим файловым блоком, потому что вы можете прорезать строку. Затем эти жетоны затем обрабатываются.

Ответ 3

API-интерфейс Fork Join, представленный в версии 1.7, отлично подходит для этого варианта использования. Проверьте http://docs.oracle.com/javase/tutorial/essential/concurrency/forkjoin.html. Если вы ищете, вы найдете множество примеров.