多线程 -之对Zip压缩文件的解析

需求背景
接到需求是要对商家提供的数据文件解析为对应我们业务需要的对象,由于商家给的文件是zip文件,大小在10-100M直接,如果用单个线程处理解析势必会影响处理速度,
以下是我们解析时候写的代码。大概思路是这个样子:

思路1:

  1. 解析zip文件:
    – 由于zip文件中有对应自己要解析的流文件,解读流中指定的文件,获得对应的文件流。
    – 单个线程测试读取流数据感觉还可以接受,处理时间大概在2000ms之内。
    – 重点考虑多个线程去消费,需要将处理后的数据放在某一个队列中,让消费者去解析
    2.生成对象列表:
    – 处理解析出来的数据,生成我们需要的对象数据列表。
    – 主要是消费文件解析出来的行数据。将数据处理为对应的对象。
    – 将数据放入对应的列表中,这样在获取列表的数据进行后续的操作。
    3.将对象持久化:
    – 顺序读取列表中的数据,将解析出来的对象做持久化操作。对列表中的数据进行入库。
    – 按照上边的分析,我们可能需要对几个地方可以进行多线程优化。按照这个思路就开干。
/**
 1. @author Janle
 2. @date 2018/1/18 10:42
 */
public class ZipFileParser<T> {
    private final ConcurrentLinkedQueue readLines;
    private ExecutorService executorService;
    private int threadSize;
    private final String zipFile;
    private final String readFileName;
    private CountDownLatch countDownLatch;
    private Class t;
    private List target;
    private int counter;
    private AtomicBoolean canRelease = new AtomicBoolean(false);

    public ZipFileParser(String zipFile, String readFileName, Class t) {
        //计算线程的数量
        this(zipFile, Runtime.getRuntime().availableProcessors() * 2, readFileName, t);
    }

    public ZipFileParser(String zipFile, int threadSize, String readFileName, Class t) {
        //计算线程的数量
        this.threadSize = threadSize > 0 ? threadSize : Runtime.getRuntime().availableProcessors() * 2;
        executorService = Executors.newFixedThreadPool(this.threadSize);
        this.readLines = Queues.newConcurrentLinkedQueue();
        this.zipFile = zipFile;
        this.readFileName = readFileName;
        this.t = t;
    }

    /**
     * 解析流文件
     */
    public void start() {
        new Thread(() -> {
            ZipFile zip = null;
            try {
                //TODO 校验拦截是否是zip文件
                zip = new ZipFile(zipFile);
                //获得zip文件中的entry值
                Enumeration entries = (Enumeration) zip.entries();
                ZipEntry ze;
                int line = 0;
                // 枚举zip文件内的文件/
                while (entries.hasMoreElements()) {
                    ze = entries.nextElement();
                    // 读取目标对象
                    if (ze.getName().equals(readFileName)) {
                        Scanner scanner = new Scanner(zip.getInputStream(ze));
                        while (scanner.hasNextLine()) {
                            //写入读取的行
                            readLines.add(scanner.nextLine());
                            line++;
                        }
                        scanner.close();
                    }
                    //计算最大的行数
                    counter = line;
                }
                zip.close();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                canRelease.set(true);
                try {
                    zip.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }


    /**
     * 使用多个线程读取线程中的文件
     *
     * @return
     */
    private void consumer() {
        //计算线程处理数据的大小,分发到各个线程处理
        countDownLatch = new CountDownLatch(this.threadSize);
        //汇总的目标list
        this.target = Lists.newArrayList();
        Constructor c = null;
        try {
            c = t.getConstructor(String.class);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        final Constructor constructor = c;

        //放入多个线程中去处理消费
        for (int i = 0; i < this.threadSize; i++) {
            executorService.submit(() -> {
                List list = Lists.newArrayList();
                //如果执行没有完成或没有消费完队列数据
                while (true) {
                    while (canRelease.get() && readLines.isEmpty()) {
                        //将解析后的数据放入目标队列,这里要注意使用同步操作
                        mergeList(list);
                        countDownLatch.countDown();
                        return;
                    }
                    String line = readLines.poll();
                    if (StringUtils.isEmpty(line)) {
                        continue;
                    }
                    T obj = null;
                    try {
                        obj = (T) constructor.newInstance(line);
                        list.add(obj);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }


    /**
     * 同步合并列表
     *
     * @param list
     */
    private synchronized void mergeList(List list) {
        this.target.addAll(list);
    }


    /**
     * 返回对应的数据处理类
     *
     * @return
     */
    public List getParsedList() {
        try {
            consumer();
            countDownLatch.await();
            executorService.shutdown();
            //TODO 接入数据处理和Db操作,这里返回处理的回调结果就好,不需要进行返回列表
            return this.target;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return Lists.newArrayList();
    }
}

以上有3个地方需要注意:
3. 读取zip中的数据时候没有想到好的解析方式解读zip中的文件内容。
4. 在合并的目标列表主要不要使用同步列表。最好是将列表分到各个多线程的处理块中。
4. mergeList(list);作用就像是将缓存中的数据合并为一个缓存列表。

思路2:
1.第二版解析处理代码
解析zip文件:
–由于zip文件中有对应自己要解析的流文件,解读流中指定的文件,获得对应的文件流。
–单个线程测试读取流数据感觉还可以接受,处理时间大概在2000ms之内。
–重点考虑多个线程去消费,需要将处理后的数据放在某一个队列中,让消费者去解析
2.解析处理入库:
–将队列中的数据接入到对应的多个线程消费处理直接入库。不会在列表对象中存放。

这个图片不好整理,直接在这里放URL了
http://note.youdao.com/noteshare?id=cbeee975a075fb40e212cd41f698f319

你可能感兴趣的:(多线程)