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

目录
相关文章
|
8天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
28 2
|
1月前
|
Java
Java“解析时到达文件末尾”解决
在Java编程中,“解析时到达文件末尾”通常指在读取或处理文件时提前遇到了文件结尾,导致程序无法继续读取所需数据。解决方法包括:确保文件路径正确,检查文件是否完整,使用正确的文件读取模式(如文本或二进制),以及确保读取位置正确。合理设置缓冲区大小和循环条件也能避免此类问题。
|
1月前
|
Java
利用GraalVM将java文件变成exe可执行文件
这篇文章简明地介绍了如何使用GraalVM将一个简单的Java程序编译成exe可执行文件,首先通过javac命令编译Java文件生成class文件,然后使用native-image命令将class文件转换成独立的exe文件,并展示了如何运行这个exe文件。
69 0
利用GraalVM将java文件变成exe可执行文件
|
11天前
|
存储 缓存 安全
在 Java 编程中,创建临时文件用于存储临时数据或进行临时操作非常常见
在 Java 编程中,创建临时文件用于存储临时数据或进行临时操作非常常见。本文介绍了使用 `File.createTempFile` 方法和自定义创建临时文件的两种方式,详细探讨了它们的使用场景和注意事项,包括数据缓存、文件上传下载和日志记录等。强调了清理临时文件、确保文件名唯一性和合理设置文件权限的重要性。
29 2
|
20天前
|
存储 安全 Java
如何保证 Java 类文件的安全性?
Java类文件的安全性可以通过多种方式保障,如使用数字签名验证类文件的完整性和来源,利用安全管理器和安全策略限制类文件的权限,以及通过加密技术保护类文件在传输过程中的安全。
|
22天前
|
存储 Java API
Java实现导出多个excel表打包到zip文件中,供客户端另存为窗口下载
Java实现导出多个excel表打包到zip文件中,供客户端另存为窗口下载
29 4
|
24天前
|
Java 数据格式 索引
使用 Java 字节码工具检查类文件完整性的原理是什么
Java字节码工具通过解析和分析类文件的字节码,检查其结构和内容是否符合Java虚拟机规范,确保类文件的完整性和合法性,防止恶意代码或损坏的类文件影响程序运行。
|
24天前
|
Java API Maven
如何使用 Java 字节码工具检查类文件的完整性
本文介绍如何利用Java字节码工具来检测类文件的完整性和有效性,确保类文件未被篡改或损坏,适用于开发和维护阶段的代码质量控制。
|
1月前
|
Java
用java搞定时任务,将hashmap里面的值存到文件里面去
本文介绍了如何使用Java的`Timer`和`TimerTask`类创建一个定时任务,将HashMap中的键值对写入到文本文件中,并提供了完整的示例代码。
37 1
用java搞定时任务,将hashmap里面的值存到文件里面去
|
1月前
|
Java
Java开发如何实现文件的移动,但是在移动结束后才进行读取?
【10月更文挑战第13天】Java开发如何实现文件的移动,但是在移动结束后才进行读取?
55 2
下一篇
无影云桌面