大文件多线程读写操作

问题描述

对于一个比较大的数据文件, 如:20G, 每一行为一个数据单元,在不进行文件切割的前提下, 想利用多线程处理文件中的每行数据。 如果依次读取文件中每一行并处理则处理速度较慢较慢; 全部读取再处理机器的内存不能满足要求。

解决思路

根据问题描述, 我们借助阻塞队列解决问题。思路如下:

  • 利用一个线程逐行读取文件中的数据, 根据指定大小组装一组数据,将数据按组加入队列
  • 利用多个线程从队列中获取数据进行处理。

具体实现

读数据线程

主要功能:

  • 逐行读取文件中的数据,按照指定数量组成一组,放入阻塞队列;
  • 读取结束,放入结束标识
  • 队列满则等待
package com.notepad.thinkingnote.ionote;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * Description: 读取文件进程实现
 * 

* 逐行读取文件中的数据,按照指定数量组成一组,放入阻塞队列 * 读取结束,放入结束标识 *

* Create: 2018/7/2 23:30 * * @author Yang Meng([email protected]) */
public class InnerReader implements Runnable { public InnerReader() {} public InnerReader(BlockingQueue> blockingQueue, String inputFileName, int maxQueueSize) { this.blockingQueue = blockingQueue; this.inputFileName = inputFileName; this.maxQueueSize = maxQueueSize; } @Override public void run() { File inputFile = new File(inputFileName); if (!inputFile.exists()) { LOG.error("cat not find such file: {}", inputFileName); return; } try { BufferedReader bufferedReader = new BufferedReader(new FileReader(inputFile)); String line; List dataGroup = new ArrayList<>(GROUP_LIMIT); while ((line = bufferedReader.readLine()) != null) { if (dataGroup.size() >= GROUP_LIMIT) { addDataGroup(dataGroup); // 数据重置 dataGroup = new ArrayList<>(GROUP_LIMIT); } dataGroup.add(line); } // 最后一批数据 addDataGroup(dataGroup); // 结束标识 addDataGroup(END); bufferedReader.close(); } catch (Exception e) { LOG.error(e.getMessage()); } } /** * 阻塞队列中加入数据: 队列满则等待 * * @param dataGroup 一组数据 * @throws Exception time.sleep */ private void addDataGroup(List dataGroup) throws Exception{ while (blockingQueue.size() >= maxQueueSize) { TimeUnit.SECONDS.sleep(3); } blockingQueue.add(dataGroup); } /** 阻塞队列 */ private BlockingQueue> blockingQueue; /** 输入文件名称 */ private String inputFileName; /** 队列最大长度 */ private int maxQueueSize; /** 一组数据大小 */ private static final int GROUP_LIMIT = 1024; /** 结束标识 */ private static final List END = new ArrayList<>(); private static final Logger LOG = LoggerFactory.getLogger(InnerReader.class); }

写数据线程

主要功能:

  • 从队列中获取一组数据进行处理, 处理之后写入到文件
  • 读取到结束标识, 重新将结束标识入队
  • 此处实现的处理比较简单: 对每行数据计数。对于比较复杂的处理可通过InnerWriter的函数将处理类实例传入, 以供后续的使用。
package com.notepad.thinkingnote.ionote;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;

/**
 * Description: 大文件写入操作
 * 

* 从队列中获取一组数据进行处理, 处理之后写入到文件 * 文件名根据输出文件名 + 进程id命名 *

* Create: 2018/7/2 23:46 * * @author Yang Meng([email protected]) */
public class InnerWriter implements Runnable { public InnerWriter() {} public InnerWriter(BlockingQueue> blockingQueue, String outputFileName) { this.blockingQueue = blockingQueue; this.outputFileName = outputFileName; } @Override public void run() { List dataGroup = blockingQueue.poll(); if (dataGroup == null) { return; } // 读取到结束标识, 重新入队 if (dataGroup.isEmpty()) { blockingQueue.add(END); return; } String output = String.format("%s.%s", outputFileName, Thread.currentThread().getId()); File outputFile = new File(output); try { FileWriter writer = new FileWriter(outputFile); for (String line : dataGroup) { writer.write(String.format("%s\t%s\n", line, line.length())); } writer.close(); } catch (Exception e) { LOG.error(e.getMessage()); } dataGroup.clear(); } /** 阻塞队列 */ private BlockingQueue> blockingQueue; /** 输出文件名 */ private String outputFileName; /** 结束标识 */ private static final List END = new ArrayList<>(); private static final Logger LOG = LoggerFactory.getLogger(InnerWriter.class); }

主函数

主要功能:

  • 利用多线程处理大文件的读写

  • 借助阻塞队列实现

package com.notepad.thinkingnote.ionote;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.concurrent.*;

/**
 * Description: 大文件读写多线程操作操作
 * 

* 1. 一个reader逐行读取文件中的数据,按照指定数量组成一组,放入阻塞队列 * 2. 多个writer从阻塞队列中读取数据进行处理 *

* Create: 2018/7/2 23:27 * * @author Yang Meng([email protected]) */
public class BigFileApplication { public static void main(String[] args) { if (args == null || args.length < 2) { LOG.error("请提供输入文件和输出文件名称"); return; } try { executor(args[0], args[1]); } catch (Exception e) { e.printStackTrace(); LOG.error("执行器错误. {}", e.getMessage()); } } private static void executor(String inputFile, String outputFile) throws Exception { BlockingQueue> blockingQueue = new ArrayBlockingQueue<>(DEFAULT_QUEUE_SIZE); ThreadPoolExecutor pools = getThreadPool(); pools.execute(new InnerReader(blockingQueue, inputFile, DEFAULT_QUEUE_SIZE)); while (true) { if (blockingQueue.isEmpty()) { TimeUnit.SECONDS.sleep(1); } List dataGroup = blockingQueue.peek(); if (dataGroup != null && dataGroup.isEmpty()) { LOG.info("process the big file done."); break; } pools.execute(new InnerWriter(blockingQueue, outputFile)); } pools.shutdown(); } /** * 定义线程池 * TODO: 相关配置加入配置文件 * * @return 线程池 */ private static ThreadPoolExecutor getThreadPool() { return new ThreadPoolExecutor(5, 10, 5, TimeUnit.SECONDS, new LinkedBlockingDeque<>(10), new ThreadPoolExecutor.CallerRunsPolicy()); } private static final int DEFAULT_QUEUE_SIZE = 30; private static final Logger LOG = LoggerFactory.getLogger(BigFileApplication.class); }

你可能感兴趣的:(Java开发笔记)