簡體   English   中英

讀取大文件多線程

[英]Read large file multithreaded

我正在實現一個應該接收大文本文件的類。 我想將其拆分為多個塊,每個塊由另一個線程保持,該線程將計算該塊中每個字符的頻率。 我希望啟動更多的線程可以獲得更好的性能,但事實證明性能會越來越差。 這是我的代碼:

public class Main {

    public static void main(String[] args) 
    throws IOException, InterruptedException, ExecutionException, ParseException  
    {

        // save the current run's start time
        long startTime = System.currentTimeMillis();

        // create options 
        Options options = new Options();
        options.addOption("t", true, "number of threads to be start");

        // variables to hold options 
        int numberOfThreads = 1;

        // parse options
        CommandLineParser parser = new DefaultParser();
        CommandLine cmd;
        cmd = parser.parse(options, args);
        String threadsNumber = cmd.getOptionValue("t");
        numberOfThreads = Integer.parseInt(threadsNumber);

        // read file
        RandomAccessFile raf = new RandomAccessFile(args[0], "r");
        MappedByteBuffer mbb 
            = raf.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, raf.length());

        ExecutorService pool = Executors.newFixedThreadPool(numberOfThreads);
        Set<Future<int[]>> set = new HashSet<Future<int[]>>();

        long chunkSize = raf.length() / numberOfThreads;
        byte[] buffer = new byte[(int) chunkSize];

        while(mbb.hasRemaining())
        {
            int remaining = buffer.length;
            if(mbb.remaining() < remaining)
            {
                remaining = mbb.remaining();
            }
            mbb.get(buffer, 0, remaining);
            String content = new String(buffer, "ISO-8859-1");
            @SuppressWarnings("unchecked")
            Callable<int[]> callable = new FrequenciesCounter(content);
            Future<int[]> future = pool.submit(callable);
            set.add(future);

        }

        raf.close();

        // let`s assume we will use extended ASCII characters only
        int alphabet = 256;

        // hold how many times each character is contained in the input file
        int[] frequencies = new int[alphabet];

        // sum the frequencies from each thread
        for(Future<int[]> future: set)
        {
            for(int i = 0; i < alphabet; i++)
            {
                frequencies[i] += future.get()[i];
            }
        }
    }

}

//help class for multithreaded frequencies` counting
class FrequenciesCounter implements Callable
{
    private int[] frequencies = new int[256];
    private char[] content;

    public FrequenciesCounter(String input)
    {
        content = input.toCharArray();
    }

    public int[] call()
    {
        System.out.println("Thread " + Thread.currentThread().getName() + "start");

        for(int i = 0; i < content.length; i++)
        {
            frequencies[(int)content[i]]++;
        }

        System.out.println("Thread " + Thread.currentThread().getName() + "finished");

        return frequencies;
    }
}

如注釋中所建議,從多個線程進行讀取時,您通常不會獲得更好的性能。 相反,您應該處理在多個線程上讀取的塊。 通常,處理會執行一些阻塞的I / O操作(保存到另一個文件嗎?保存到數據庫嗎?HTTP調用?),並且如果您在多個線程上進行處理,則性能會更好。

為了進行處理,您可能具有ExecutorService(具有合理的線程數)。 使用java.util.concurrent.Executors獲得java.util.concurrent.ExecutorService實例

具有ExecutorService實例,您可以提交塊進行處理。 提交塊不會被阻止。 ExecutorService將開始在單獨的線程中處理每個塊(具體取決於ExecutorService的配置)。 您可以提交RunnableCallable實例。

最后,提交所有項目后,應在ExecutorService上調用awaitTermination 它將等待所有提交項目的處理完成。 在awaitTermination返回之后,您應該調用shutdownNow()來中止處理(否則它可能會無限期掛起,從而執行某些惡意任務)。

幾乎可以肯定,您的程序受磁盤讀取速度的限制。 使用多個線程無助於此,因為該限制是硬件可以限制從磁盤傳輸信息的速度的限制。

此外,同時使用RandomAccessFile和后續的緩沖區可能會導致速度稍有下降,因為在讀取數據之后但在處理之前,您將數據移動到內存中,而不僅僅是在原地進行處理。 您最好不要使用中間緩沖區。

通過將文件直接讀取到最終緩沖區中並在線程被填滿時分派要由線程處理的緩沖區,而不是等待處理之前讀取整個文件,可能會略微加快速度。 但是,大多數時間仍將由磁盤讀取使用,因此任何加速都可能是最小的。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM