当前位置:首页 > 资讯 > 正文

java多文件导出成压缩包

java多文件导出成压缩包


前言:最近在工作中遇到了很多需要做导出的功能,像什么导出Excel、导出 word、导出压缩包等。可当数据量一上来后,响应给前端的速度就会明显变慢,所以在实际开发中便有了使用线程池来提升速度的想法,亲测有效,于是便整理了一些关于线程池、关于导出方面的一些小技巧,以此用 Demo 项目来分享给大家,内容仅供参考。

文章目录

一、线程池简介

1.1  线程池的创建

1.1.1    Executors.newFixedThreadPool(int nThreads)

1.1.2    Executors.newCachedThreadPool()

1.1.3    Executors.newSingleThreadExecutor()

1.1.4    Executors.newWorkStealingPool()

1.1.5    Executors.newScheduledThreadPool(int corePoolSize)

 1.1.6    ThreadPoolExecutor 类自定义线程池

1.2  线程池的使用

1.3  线程池的使用细节

1.3.1  submit()和 executor() 提交任务的异同:

 1.3.2  Runnable 接口 和 Callable 接口 实现任务的区别:

二、线程池的实际运用

2.1  框架结构搭建

2.2  线程池改造结构

三、 本地文件导出为 Zip 压缩包(包含文件夹)

四、 导出路径小技巧


        线程池也就是放线程的池子,它会帮我们管理线程资源,比如创建和回收,在程序中使用线程池可以极大的提升项目的运行速度,同时也可以减少一些多线程的并发问题,如SingleThreadExecutor  单核线程池 在内部实现中保证了只有一个线程在执行任务,所以不会出现并发访问共享资源的问题,因此不需要考虑线程安全性。而对于多核线程池,虽然可以并行的执行任务(如下载多个文件,并行执行时就可以同时下载),但也需要注意线程间的安全问题。

        常见的创建线程池的方式有 6 种,分别是用 Executors 工具类创建的 5 种线程池,以及使用 ThreadPoolExecutor 类创建的自定义线程池。使用 Executors 工具类创建的线程池方法如下:

1.1.1    Executors.newFixedThreadPool(int nThreads)

        此方法创建的是定长线程池,也就是线程池中的线程数量是固定的,且全是核心线程,每个线程都有无限的存活时间。

        常用于执行周期长的任务

1.1.2    Executors.newCachedThreadPool()

        此方法创建的是可缓存线程池,“如果当前线程池的长度超过了处理的需要时,它可以灵活的回收空闲的线程,当需要增加时, 它也可以灵活的添加新的线程,而不会对池的长度作任何限制”。也就是线程池大小不固定,有多少任务就可以创建多少线程(默认有 Integer.MAX_VALUE 个线程数,且都是非核心线程,此线程池没有核心线程)、线程池中的线程可以被复用,如果线程空闲60秒将收回并移出缓存。缺点是不保证任务的执行顺序、可能存在线程泄漏问题。不建议使用默认的线程数,建议使用线程池初始化参数对其进行设置,来尽可能的避免一些错误的发生。

        此线程池适用于处理大量短期异步的任务。

1.1.3    Executors.newSingleThreadExecutor()

        此方法创建的是单核线程池,只有一个线程处理任务,任务多了会排队执行,优点是不用考虑线程安全性,同时也具有无限的存活时间,但需要注意线程泄漏问题。

        适用于需要保证任务顺序执行的场景

1.1.4    Executors.newWorkStealingPool()

        此方法创建的是工作窃取线程池,在 JDK 1.8 时引入,有两种构造,一个无参,一个是可传入 Integer 类型的参数用来规定可以并行执行的线程数。所谓“可窃取”也就是说,此线程池不保证任务执行顺序,哪个线程抢到了就由哪个线程执行。

        适用于处理大量并发任务。

1.1.5    Executors.newScheduledThreadPool(int corePoolSize)

        此方法创建的是延时任务线程池,创建一个指定核心线程数的线程池,默认拥有 Integer.MAX_VALUE 个非核心线程池,默认即无限,每个线程拥有无限存活时间。特点是可以执行延时的或者周期性的任务,可使用其 schedule 方法实现延时任务,使用 scheduleAtFixedRate 方法实现周期任务,如:

使用 schedule 方法实现延时任务:

        上述代码使用  schedule 方法,实现延迟 1 秒后只打印一次 "hello" 及其时间,然后循环 5 次,同时主线程每次都睡了 1 秒,等待线程池中的任务执行完,效果如下:

java多文件导出成压缩包_线程池

         虽然使用 schedule 方法线程池中的任务只执行一次,但因为此线程池中的线程数具有无限存活时间,所以用完即关,养成一个好习惯:

使用  scheduleAtFixedRate 方法实现周期任务

         上述代码使用  scheduleAtFixedRate 方法,该方法接受四个参数:要执行的任务、初始延迟时间、周期和时间单位。在这个示例中,我们设置初始延迟为 0,周期为 1 秒,时间单位为秒。这样,任务就会每隔 1 秒钟执行一次,主线程睡眠3秒,所以会打印 3 次。scheduleAtFixedRate 方法中的周期任务会一直运行下去,所以用完还需要将其关闭。代码运行效果如下:

java多文件导出成压缩包_spring_02

        此线程池适用于需要控制任务执行时间的场景。

 1.1.6    ThreadPoolExecutor 类自定义线程池

         此线程池类可以根据自己的需求进行自定义线程池设置,参数如下:

java多文件导出成压缩包_spring_03

  1. :线程池的核心线程数。
  2. :线程池的最大线程数。这是线程池能够容纳的最大线程数,包括核心线程和非核心线程。
  3. :当线程数大于核心大小时,多余的空闲线程在终止之前等待新任务的最长时间,也就是空闲线程的最大存活时间。
  4. :参数的时间单位。
  5. :用于保存新提交任务的队列。通常有几种类型的队列可以选择,如、等。
  6. :用于创建新线程的工厂。你可以提供一个自定义的工厂来创建具有特定名称前缀或具有特定设置的线程。
  7. :当线程池和工作队列都被填满时,该策略将被用来拒绝新任务。通常,当任务队列满时,会抛出一个异常或返回一个特殊的值。

举个栗子:

        用单核线程池举例,一般流程就是创建线程池-->提交任务-->等待完成后关闭线程池,如:

        这里使用的是 Runnable 接口来实现线程任务,还有另一个接口是 Callable<Object>,该接口可以有返回值,返回值用 Future 来接收:

1.3.1  submit()和 executor() 提交任务的异同:

相同点:

        二者都可以开启线程执行池中的任务,但是实际上submit()方法中最终调用的还是execute()方法。


不同点:

        接收参数:execute()只能执行 Runnable 类型的任务。submit()可以执行 Runnable 和 Callable 类型的任务。

        返回值:submit()方法可以返回持有计算结果的 Future 对象,而execute()没有

        异常处理:submit()方便Exception处理

 1.3.2  Runnable 接口 和 Callable 接口 实现任务的区别:

        1. 返回值:Runnable 接口的 run() 方法没有返回值,而 Callable 接口的 call() 方法有返回值,其返回结果会被 Future 对象异步接收可使用 Future.get() 方法来获取任务的结果。这个方法会阻塞直到任务完成。如果任务抛出了异常,这个方法会抛出ExecutionException。

        2. 异常处理:Runnable.run() 方法不能抛出检查型异常,即异常只能在内部捕获,不能继续上抛。而 Callable.call() 方法可以,这使得在 Callable 任务中处理异常更为灵活。


        好,我们回归正题,从上面我们已经了解到了线程池的一般使用,现在我们就来实际操作一下,首先来搭一个简单的 web框架

controller:

service:

 serviceImpl:

 然后启动运行,用 Postman 测一下:

java多文件导出成压缩包_java多文件导出成压缩包_04

 响应乱码,说明没问题,点击保存,然后随便找个位置保存下来

java多文件导出成压缩包_开发语言_05

打开

java多文件导出成压缩包_java多文件导出成压缩包_06

java多文件导出成压缩包_java_07

 层级没问题,内容也没问题,现在就用线程池来改造一下,这里就用单核线程池来举个栗子

        首先我们先建一个 ThreadExportUtil 配置类,此类用于得到 单例 线程池。利用 SpringBean 的特点,让此线程池进行一个简单的单例化。

        然后在 serviceImpl 类中,将需要用线程池减轻业务量的代码放进线程池中,让繁重的业务在JVM 堆栈中另开空间,和主线程同时进行,所以这里实现的接口是 Callable接口。Callable 在这里的好处是:一、可以传参。因为每个线程都是互相独立的,若实现 Runnable 接口的话,则需要单独设一个共享变量,然后就又要考虑资源死锁问题,而 Callable 可以避免。二、因为每个线程都有自己的 pc(程序计数器),所以异步执行时可能导出还没结束,主线程就已经跑完返回结果了,所以使用 Callable 接口通过其返回的结果 Future 对象的 get() 方法,让主线程阻塞等待副线程,达到顺序执行的效果(主要是为了关流)。

 测试一下,结果一样。因为 Demo 项目数据量少,效果不明显,真实环境下效果就可以看出来了。需要注意的是文件目录需要一级一级的创建,不然会报错说没有这个文件或目录。

        附带一个将文件夹压缩成 Zip 包的工具类

        有时候业务需求说,需要把一个文件直接导出到前端,也需要将这个文件和其他文件夹压缩在一起然后形成一个 zip 包响应给前端,很明显这里就要两段重复的接口,而这两者的差异就仅仅是最后导出的路径不同而已,所以这里就有个小技巧可以优化这两个接口。那就是将 response 和 path 同时传给导出的接口,通过判断 path 是否为 null ,来判断最后是直接响应给客户端还是输出到服务器中。如导出项目根路径下的 test.xlsx 文件

java多文件导出成压缩包_java_08

java多文件导出成压缩包_spring_09

model: 和表头对应

controller:

service:

 serviceImpl:

 最后运行测试一下:

java多文件导出成压缩包_java多文件导出成压缩包_10

java多文件导出成压缩包_开发语言_11

没有问题,然后在 导出压缩包接口 中传入此方法,并将文件夹路径传进去就可以把这个 Excel 表格输出到服务器上啦。

最新文章