java实现csv大文件拆分,每个小文件都有标题行

简介: java实现csv大文件拆分,每个小文件都有标题行

一、背景


开发中,我们经常需要导入csv文件到数据库中,但是如果csv文件太大了,可能会报错,这时候可以对csv文件进行拆分,分批导入。本节就以spring boot项目为例实现csv大文件拆分并输出拆分后的zip包。


二、后端实现


1、controller层,我们传下面几个参数:


(1)file参数:  前端传的大csv文件


(2)size参数:要拆分的小文件最大行数


(3)request参数:请求体


(4)response参数 :响应体


2、controller主要代码如下:


(1)比较容易理解,前半部分目的是获取前端传的文件的基本信息


(2)SplitUtils.getCsvZipPath(inputStream, fileName, splitSize);方法对csv文件进行拆分并返回拆分后的文件夹路径。


(3)exportZipUtils.zipExport(zipPath, request, response);方法将拆分后的csv文件夹打包输出到前端。

 @RequestMapping(value = "/upload", method = RequestMethod.POST)
    @ResponseBody
    public String uploadAndGetSplitZip(@RequestParam("file") MultipartFile file, @RequestParam("size") String size,
                                       HttpServletRequest request, HttpServletResponse response) {
        try {
            InputStream inputStream = file.getInputStream();
            String originalFilename = file.getOriginalFilename();
            String[] split = originalFilename.split("\\.");
            String type = split[split.length - 1];
            int index = originalFilename.lastIndexOf(".");
            String fileName = originalFilename.substring(0, index);
            int splitSize = Integer.valueOf(size);
            logger.info("文件信息:文件名:{}  文件类型: {}" +
                            "  文件大小:{}MB   要求拆分文件最大行数: {}", fileName, type,
                    file.getSize() / 1024 / 1024, splitSize);
            String zipPath = SplitUtils.getCsvZipPath(inputStream, fileName, splitSize);
            exportZipUtils.zipExport(zipPath, request, response);
            forceDeleteFilesUtils.deleteAllFilesOfDir(new File(zipPath));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "success";
    }

3、拆分csv文件方法主要代码如下:


(1)参数inputStream:为大csv文件流。


(2)参数fileName :为前端所传文件名。


(3)参数 splitSize: 为拆分后小文件的最大行数。


(4)这个方法主要思路将大文件流放到BufferedReader里面,然后获取总行数,根据参数splitSize计算需要拆分成几个小文件,需要几个文件,我们就创建几个,放到list集合里,一行一行遍历源文件,第一行的内容所以文件都写入,除第一行外的内容,随机写入创建的小文件里面。最后把所有的小文件关流。

@Component
public class SplitUtils {
    private static Logger logger = LoggerFactory.getLogger(SplitUtils.class);
    private static String defaultDir = System.getProperty("java.io.tmpdir") + File.separator;
  /**
     * 拆分csv文件并返回文件夹路径
     *
     * @param inputStream
     * @param filename
     * @param splitSize
     * @return
     */
    public static String getCsvZipPath(InputStream inputStream, String filename, int splitSize) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        try {
            InputStreamReader reader = new InputStreamReader(inputStream);
            BufferedReader bufferedReader = new BufferedReader(reader);
            Stream<String> lines = bufferedReader.lines();
            List<String> contents = lines.collect(Collectors.toList());
            long fileCount = contents.size();
            int splitNumber = (int) ((fileCount % splitSize == 0) ? (fileCount / splitSize) : (fileCount / splitSize + 1));
            logger.info("csv文件总行数: {}行  拆分文件个数:{}个", fileCount, splitNumber);
            //将创建的拆分文件写入流放入集合中
            List<BufferedWriter> listWriters = new ArrayList<>();
            //创建存放拆分文件的目录
            File dir = new File(defaultDir + filename);
            //文件夹存在,可能里面有内容,删除所有内容
            if (dir.exists()) {
                delAllFile(dir.getAbsolutePath());
            }
            dir.mkdirs();
            for (int i = 0; i < splitNumber; i++) {
                String splitFilePath = defaultDir + filename + File.separator + filename + i + ".csv";
                File splitFileName = new File(splitFilePath);
                splitFileName.createNewFile();
                BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(splitFileName)));
                listWriters.add(bufferedWriter);
            }
            for (int i = 0; i < fileCount; i++) {
                if (i == 0) {
                    for (int count = 0; count < splitNumber; count++) {
                        listWriters.get(count).write(contents.get(i));
                        listWriters.get(count).newLine();
                    }
                } else {
                    listWriters.get(i % splitNumber).write(contents.get(i));
                    listWriters.get(i % splitNumber).newLine();
                }
            }
            //关流
            listWriters.forEach(it -> {
                try {
                    it.flush();
                    it.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
        } catch (IOException e) {
            logger.info("csv拆分文件失败  :" + e);
            e.printStackTrace();
        }
        stopWatch.stop();
        logger.info("csv文件拆分共花费:  " + stopWatch.getTotalTimeMillis() + " ms");
        return defaultDir + filename + File.separator;
    }

(5)拆分文件时,存放临时文件的地方可能已存在同名的文件,需要删除。意思就是我们拆分文件时,肯定需要把拆分的文件放到一个地方,可能这个地方不干净,有其他文件,所以我们放之前先删除一下这里的文件。方法如下:这个方法在上面拆分文件方法里用到了。在这里补充一下。

 /***
     * 删除文件夹
     *
     */
    public static void delFolder(String folderPath) {
        try {
            delAllFile(folderPath); // 删除完里面所有内容
            String filePath = folderPath;
            filePath = filePath.toString();
            File myFilePath = new File(filePath);
            myFilePath.delete(); // 删除空文件夹
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /***
     * 删除指定文件夹下所有文件
     *
     * @param path 文件夹完整绝对路径
     * @return
     */
    public static boolean delAllFile(String path) {
        boolean flag = false;
        File file = new File(path);
        if (!file.exists()) {
            return flag;
        }
        if (!file.isDirectory()) {
            return flag;
        }
        String[] tempList = file.list();
        File temp = null;
        for (int i = 0; i < tempList.length; i++) {
            if (path.endsWith(File.separator)) {
                temp = new File(path + tempList[i]);
            } else {
                temp = new File(path + File.separator + tempList[i]);
            }
            if (temp.isFile()) {
                temp.delete();
            }
            if (temp.isDirectory()) {
                delAllFile(path + "/" + tempList[i]);// 先删除文件夹里面的文件
                delFolder(path + "/" + tempList[i]);// 再删除空文件夹
                flag = true;
            }
        }
        return flag;
    }

4、拆分为小文件后,我们需要打包传到前端,exportZipUtils.zipExport(zipPath, request, response);这个方法就是干这个事的,代码如下,就是个打包组件,复制使用就可以了。


(1)filePath为存放拆分后的小文件路径


(2)request和response分别为请求体和响应体。

@Component
public class ExportZipUtils {
    private static Logger logger = LoggerFactory.getLogger(ExportZipUtils.class);
    public void zipExport(String filePath, HttpServletRequest request, HttpServletResponse response) {
        //zip包的名称
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        String zipName = "package.zip";
        //要打包的文件夹路径
        String packagePath = filePath;
        response.setContentType("octets/stream");
        response.setCharacterEncoding("UTF-8");
        String agent = request.getHeader("USER-AGENT");
        try {
            if (agent.contains("MSIE") || agent.contains("Trident")) {
                zipName = URLEncoder.encode(zipName, "UTF-8");
            } else {
                zipName = new String(zipName.getBytes("UTF-8"), "ISO-8859-1");
            }
        } catch (UnsupportedEncodingException e) {
            logger.error(e.getMessage(), e);
        }
        response.setHeader("Content-Disposition", "attachment;fileName=\"" + zipName + "\"");
        ZipOutputStream zipos = null;
        try {
            zipos = new ZipOutputStream(new BufferedOutputStream(response.getOutputStream()));
            zipos.setMethod(ZipOutputStream.DEFLATED);
            zipos.setLevel(0);
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
        }
        DataOutputStream os = null;
        InputStream is = null;
        try {
            String[] fileNameList = new File(packagePath).list();
            for (int i = 0; i < fileNameList.length; i++) {
                File file = new File(packagePath + File.separator + fileNameList[i]);
                zipos.putNextEntry(new ZipEntry(fileNameList[i]));
                os = new DataOutputStream(zipos);
                is = new FileInputStream(file);
                //输入流转换为输出流
                IOUtils.copy(is, os);
                is.close();
                zipos.closeEntry();
            }
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
        } finally {
            stopWatch.stop();
            logger.info("输出zip包共耗时: " + stopWatch.getTotalTimeMillis() + " ms");
            // 推荐使用try-with-resource
            try {
                if (is != null) {
                    is.close();
                }
                if (os != null) {
                    os.flush();
                    os.close();
                }
                if (zipos != null) {
                    zipos.flush();
                    zipos.close();
                }
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            }
        }
    }
}

5、打包的文件传到前端后,如果想删除临时文件,可以使用这个方法,传进去要删除的文件路径,该路径下的所有文件就被删除了,工具代码如下:

 /**
     * 删除文件夹(强制删除)
     *
     * @param path
     */
    public  void deleteAllFilesOfDir(File path) {
        if (null != path) {
            if (!path.exists()){
                return;
            }
            if (path.isFile()) {
                boolean result = path.delete();
                int tryCount = 0;
                while (!result && tryCount++ < 10) {
                    System.gc(); // 回收资源
                    result = path.delete();
                }
            }
            File[] files = path.listFiles();
            if (null != files) {
                for (int i = 0; i < files.length; i++) {
                    deleteAllFilesOfDir(files[i]);
                }
            }
            path.delete();
        }
    }
    /**
     * 删除文件
     */
    public  boolean deleteFile(String pathname) {
        boolean result = false;
        File file = new File(pathname);
        if (file.exists()) {
            file.delete();
            result = true;
            System.out.println("文件已经被成功删除");
        }
        return result;
    }

三、测试效果

1、我们通过Postman进行请求,视图如下:

image.png

2、返回结果如下:

(1)日志输出

image.png

(2)文件效果如下:

image.png

目录
相关文章
|
2月前
|
Java Unix Go
【Java】(8)Stream流、文件File相关操作,IO的含义与运用
Java 为 I/O 提供了强大的而灵活的支持,使其更广泛地应用到文件传输和网络编程中。!但本节讲述最基本的和流与 I/O 相关的功能。我们将通过一个个例子来学习这些功能。
198 1
|
5月前
|
监控 Java API
Java语言按文件创建日期排序及获取最新文件的技术
这段代码实现了文件创建时间的读取、文件列表的获取与排序以及获取最新文件的需求。它具备良好的效率和可读性,对于绝大多数处理文件属性相关的需求来说足够健壮。在实际应用中,根据具体情况,可能还需要进一步处理如访问权限不足、文件系统不支持某些属性等边界情况。
273 14
|
5月前
|
存储 Java 编译器
深入理解Java虚拟机--类文件结构
本内容介绍了Java虚拟机与Class文件的关系及其内部结构。Class文件是一种与语言无关的二进制格式,包含JVM指令集、符号表等信息。无论使用何种语言,只要能生成符合规范的Class文件,即可在JVM上运行。文章详细解析了Class文件的组成,包括魔数、版本号、常量池、访问标志、类索引、字段表、方法表和属性表等,并说明其在Java编译与运行过程中的作用。
154 0
|
Java
java小工具util系列5:java文件相关操作工具,包括读取服务器路径下文件,删除文件及子文件,删除文件夹等方法
java小工具util系列5:java文件相关操作工具,包括读取服务器路径下文件,删除文件及子文件,删除文件夹等方法
251 9
|
6月前
|
存储 Java 数据安全/隐私保护
Java技术栈揭秘:Base64加密和解密文件的实战案例
以上就是我们今天关于Java实现Base64编码和解码的实战案例介绍。希望能对你有所帮助。还有更多知识等待你去探索和学习,让我们一同努力,继续前行!
486 5
|
5月前
|
存储 人工智能 Java
java之通过Http下载文件
本文介绍了使用Java实现通过文件链接下载文件到本地的方法,主要涉及URL、HttpURLConnection及输入输出流的操作。
352 0
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
366 2
|
12月前
|
人工智能 自然语言处理 Java
FastExcel:开源的 JAVA 解析 Excel 工具,集成 AI 通过自然语言处理 Excel 文件,完全兼容 EasyExcel
FastExcel 是一款基于 Java 的高性能 Excel 处理工具,专注于优化大规模数据处理,提供简洁易用的 API 和流式操作能力,支持从 EasyExcel 无缝迁移。
2727 65
FastExcel:开源的 JAVA 解析 Excel 工具,集成 AI 通过自然语言处理 Excel 文件,完全兼容 EasyExcel
|
6月前
|
网络协议 安全 Java
实现Java语言的文件断点续传功能的技术方案。
像这样,我们就完成了一项看似高科技、实则亲民的小工程。这样的技术实现不仅具备实用性,也能在面对网络不稳定的挑战时,稳稳地、不失乐趣地完成工作。
386 0
|
9月前
|
前端开发 Cloud Native Java
Java||Springboot读取本地目录的文件和文件结构,读取服务器文档目录数据供前端渲染的API实现
博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
Java||Springboot读取本地目录的文件和文件结构,读取服务器文档目录数据供前端渲染的API实现