读大文件多线程

时间:2017-06-24 08:29:21

标签: java multithreading future callable mappedbytebuffer

我正在实现一个应该接收大文本文件的类。我希望将它分成块,每个块由一个不同的线程保存,该线程将计算此块中每个字符的频率。我希望通过启动更多线程来获得更好的性能,但事实证明性能越来越差。这是我的代码:

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;
    }
}

2 个答案:

答案 0 :(得分:2)

正如评论中所建议的那样,当(多次)从多个线程读取时,您(通常)不会获得更好的性能。相反,您应该处理您在多个线程上读取的块。通常处理会执行一些阻塞,I / O操作(保存到另一个文件?保存到数据库?HTTP调用?),如果您在多个线程上进行处理,性能会更好。

对于处理,您可能有ExecutorService(具有合理的线程数)。使用java.util.concurrent.Executors获取java.util.concurrent.ExecutorService

的实例

拥有ExecutorService个实例,您可以submit进行处理。提交块不会阻止。 ExecutorService将开始在单独的线程处理每个块(详细信息取决于ExecutorService的配置)。您可以提交RunnableCallable的实例。

最后,在提交所有项目后,您应该在ExecutorService上调用awaitTermination。它将等待所有提交的项目的处理完成。在awaitTermination返回之后,你应该调用shutdownNow()来中止处理(否则它可能会无限期地挂起,处理一些流氓任务)。

答案 1 :(得分:1)

您的程序几乎肯定受到从磁盘读取速度的限制。使用多个线程对此没有帮助,因为限制是对从磁盘传输信息的速度的硬件限制。

此外,使用RandomAccessFile和后续缓冲区可能会导致小幅减速,因为您在读取数据后但在处理之前将数据移动到内存中,而不是仅仅处理它。最好不要使用中间缓冲区。

通过将文件直接读入最终缓冲区并调度这些缓冲区以便线程在填充时进行处理,您可能会获得轻微的加速,而不是在处理之前等待读取整个文件。但是,大多数时间仍然会被磁盘读取使用,因此任何加速都可能是最小的。