前言
对于文件进行管理,是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文件下载功能的处理方法,敬请期待。