【JAVA开发小技巧】后端实现文件的写入、压缩、下载、删除


前言

对于文件进行管理,是Java后端程序员最基础也是最重要的职责之一,虽然是基础加重点,但是还是有一部分的程序员没有好好地掌握Java的文件管理,这篇文章将总结整理一下开发过程中常见的文件操作,不过并没有做到面面俱到的整理,大家可以根据自己的需求按图索骥。


一、了解文件流

1.什么是文件流?

我们知道文件是存储在磁盘当中的,而我们读写文件的操作是在内存中完成的,而文件流就是在内存和磁盘之间读写文件的信息通道。

2.读写的方式

在程序中,一个流可以理解为一个数据的序列。输入流表示从一个源读取数据,输出流表示向一个目标写数据。

Java 将流按照读写单位划分为字节流和字符流:
字节流:字节流是按照字节的方式读取数据,一次读取一个字节,也就是8个二进制位,这种流是万能的,什么类型的文件都可以读取,包括:文本文件,图片,声音文件,视频文件等。
字符流:读写单位是以字符为单位,所以字符流读取数据有一定的局限性,只能用于读写文本数据。非文本数据不能使用字符流读取(如图片,基本类型数据,Mp3等数据)。

在这里插入图片描述
在这里插入图片描述


二、文件内容写入

比起文件读取,我们在开发需求中更常遇到的操作是文件的写入,往往就是将用户所需的数据写入到文件当中。

1.输出流对象及对应方法

写入文件常用的输出流对象主要有OutputStreamWriter和BufferedWriter。

OutputStreamWriter的成员方法:
在这里插入图片描述
BufferedWriter的成员方法:
在这里插入图片描述
可以看到两个对象的成员方法十分接近,所以这边举例一下使用BufferedWriter的例子,OutputStreamWriter依此类推。

BufferedWriter bw = new BufferedWriter(new FileWriter(fileName));
char[] charArray = str.toCharArray();
for (char ch : charArray) {
    bw.write(ch);
}
bw.flush();   // 别忘了,将缓冲区数据写入磁盘
bw.close();   // 关闭文件流

注意,如果要求文件为linux格式文件,写入时要将换行符"\r\n"变为"\n"。


三、文件压缩成zip

很多时候用户要求下载的数据内容是文件夹格式的数据,但是符合规范的方法就是将文件夹压缩为压缩包供用户下载,这样即安全也更具备兼容性。
在java程序中,压缩的过程是将文件流转为zipOutputStream输出流的过程,但是过程中文件夹需要递归遍历下级文件与文件夹,进而压缩它们。
在这里插入图片描述
压缩文件的示例:

    private final static byte[] buf = new byte[1024];

    /**
     * 将目标文件夹压缩为压缩包
     * @param zipFileName
     * @param targetFileDir
     * @return
     */
    public static Boolean exportZip(String zipFileName, String targetFileDir){
        ZipOutputStream zipOutputStream = null;
        try{
            FileOutputStream fileOutputStream = new FileOutputStream(targetFileDir);
            zipOutputStream = new ZipOutputStream(fileOutputStream);
            File targetFile = new File(targetFileDir);
            compress(targetFile, targetFile.getName(), zipOutputStream);
        } catch (Exception e){
            e.printStackTrace();
            throw new RuntimeException(e);
        }
        finally {
            if(zipOutputStream != null) {
                try {
                    zipOutputStream.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return true;
    }

    /**
     * 递归按照目录层级压缩文件夹
     * @param targetFile
     * @param name
     * @param zipOutputStream
     */
    private static void compress(File targetFile, String name, ZipOutputStream zipOutputStream) throws Exception {
        // 若targetFile是文件,而非文件夹
        if(targetFile.isFile()){
            // 压缩的关键在于zip实体,name为文件名或文件目录名
            zipOutputStream.putNextEntry(new ZipEntry(name));
            // 读取文件内容到文件流中(图中第一步)
            FileInputStream inputStream = new FileInputStream(targetFile);
            int len = inputStream.read(buf);
            while(len != -1){
                //将文件流中数据写入zip输出流(图中第二步)
                zipOutputStream.write(buf, 0, len);
                len = inputStream.read(buf);
            }
            zipOutputStream.closeEntry();
            inputStream.close();
        } else {
            // 获取文件夹的下级文件列表
            File[] listFiles = targetFile.listFiles();
            if(listFiles == null || listFiles.length == 0) {
                zipOutputStream.putNextEntry(new ZipEntry(name + "/"));
                zipOutputStream.closeEntry();
            } else {
                for (File file : listFiles) {
                    compress(file, name + "/" + file.getName(), zipOutputStream);
                }
            }
        }
    }

三、文件的下载

接下来,我们将实现用户下载服务器上经过我们压缩处理好的压缩包,这需要响应对象response中封装的OutputStream对象来实现。

首先我们了解一下response:
我们知道ServletContext代表了整个web应用,在原装的doGet/doPost方法的response的类型就是HttpServletResponse。
HttpServletResponse常用的方法:

ServletOutputStream getOutputStream() throws IOException;  // 往文件流中写入数据时用
PrintWriter getWriter() throws IOException;  // 写入中文数据时用

除此之外,还有以下几种常用方法:
在这里插入图片描述
当用户访问下载文件接口的时候,我们将文件数据写入ouputstream流对象当中,数据以字节数组的形式存储,所以我们要改造一下zip导出的接口,

    public static byte[] exportZip(String zipFileName, String targetFileDir){
        ZipOutputStream zipOutputStream = null;
        ByteArrayOutputStream outputStream = null;
        try{
            outputStream = new ByteArrayOutputStream();
            zipOutputStream = new ZipOutputStream(outputStream);
            File targetFile = new File(targetFileDir);
            compress(targetFile, targetFile.getName(), zipOutputStream);
            return outputStream.toByteArray();
        } catch (Exception e){
            e.printStackTrace();
            throw new RuntimeException(e);
        }
        finally {
            if(zipOutputStream != null) {
                try {
                    zipOutputStream.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return outputStream.toByteArray();
    }

通过以上的方法,我们得到了压缩包字节数组,接下来我们将数据写入到outputstream对象当中,代码示例:

        try {
            OutputStream os = response.getOutputStream();
            // 输入待压缩文件夹,输出字节流数据
            byte[] data = exportZip(zipFileName, targetFileDir);
            response.reset();
            response.setCharacterEncoding("UTF-8");
            response.addHeader("Access-Control-Allow-Origin", "*");
            response.setHeader("Access-Control-Expose-Headers", "*");
            // UTF-8编码,防止文件名乱码问题
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(zipFileName, "UTF-8"));
            response.addHeader("Content-Length", "" + data.length); // 文件长度
            response.setContentType("application/octet-stream;charset=UTF-8");
            IOUtils.write(data, os);
            os.flush();
            os.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

四、文件的删除

在用户下载完压缩包之后,我们为了防止服务器上数据对磁盘空间的侵占,会考虑删除压缩处理完的压缩包,这个工作还是可以简单完成的。

File zipFile = new File(filePath, fileName);
if (!zipFile .delete()) {//删除文件
  System.out.println("删除失败");
}

不过,有时候我们也要面临删除文件夹的问题,这个删除过程也要做到递归处理,代码如下:

    public static boolean deleteDir(String filePath){
        File file = new File(filePath);
        if(!file.exists()){
            System.out.println("文件不存在");
            return false;
        }

        String[] fileChildNames = file.list(); // 获得子文件和子文件夹集
        for (String fileName : fileChildNames) {
            File nextFile = new File(filePath, fileName);
            System.gc(); // 获得系统垃圾回收权限
            if(nextFile.isFile()){
                if(!nextFile.delete()){
                    System.out.println("删除失败");
                }
            } else if (nextFile.isDirectory()){
                deleteDir(nextFile.getAbsolutePath()); // 如果是文件夹则递归删除
                nextFile.delete();
            }
        }
        
        return true;
    }

总结

压缩文件的处理过程多了一个压缩过程,牵涉到对文件夹的操作,需要我们递归处理,以及删除文件夹时需要完成递归操作。对于不同文件的下载功能都有各自的处理方法,下次我将分享对于Excel文件下载功能的处理方法,敬请期待。

  • 17
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值