之前在学习文件操作时没有认真学习,认为用不到。结果风水轮流转,最近被要求要做一个web版的文件管理系统,所以回来填坑。
文件管理系统的博客一搜一大把,其中不乏有大量大神的源代码,作为一个小白来说,有的地方还是不不好理解,所以便有了这篇小白文章。
文件管理的内容是我自己一步一步趟出来,从简单的功能到复杂的,包含了很多遇见的BUG以及解决方法。希望对小白有所帮助吧,正文开始:
1. 构造方法
File(String path): 如果path是实际存在的路径, 则该File对象表示的是目录; 如果path是文
件名, 则该File对象表示的是文件。
File(String path, String name): path是路径名, name是文件名。
File(File dir, String name): dir是路径对象, name是文件名。
3. 获得文件名
String getName( ): 获得文件的名称, 不包括路径。
String getPath( ): 获得文件的路径。
String getAbsolutePath( ): 获得文件的绝对路径。
String getParent( ): 获得文件的上一级目录名。
4. 文件属性测试
boolean exists( ): 测试当前File对象所表示的文件是否存在。
boolean canWrite( ): 测试当前文件是否可写。
boolean canRead( ): 测试当前文件是否可读。
boolean isFile( ): 测试当前文件是否是文件。
boolean isDirectory( ): 测试当前文件是否是目录。
5. 文件操作
long lastModified( ): 获得文件最近一次修改的时间。
long length( ): 获得文件的长度, 以字节为单位。
boolean delete( ): 删除当前文件。 成功返回 true, 否则返回false。
boolean renameTo(File dest): 将重新命名当前File对象所表示的文件。 成功返回 true, 否
则返回false。05. 目录操作
boolean mkdir( ): 创建当前File对象指定的目录。
String[] list(): 返回当前目录下的文件和目录, 返回值是字符串数组。
String[] list(FilenameFilter filter): 返回当前目录下满足指定过滤器的文件和目录, 参数是
实现FilenameFilter接口对象, 返回值是字符串数组。
File[] listFiles(): 返回当前目录下的文件和目录, 返回值是File数组。
File[] listFiles(FilenameFilter filter): 返回当前目录下满足指定过滤器的文件和目录, 参数
是实现FilenameFilter接口对象, 返回值是File数组。
File[] listFiles(FileFilter filter): 返回当前目录下满足指定过滤器的文件和目录, 参数是实
现FileFilter接口对象, 返回值是File数组。
1.输入/输出流
2.File类的基本方法
3.try catch,异常的处理
4.日志类
5.字符串的常用操作
6.三目运算符
7.递归
约定:
1.在文件复制中,D://file 为源路径,D://file_copy 为目标路径
2.其他文件操作都在源路径中
3.方法都在junit测试中进行
4.此程序完全按照Windows资源管理器设计
/**
* 文件重命名测试
*/
@Test
public void reNameTest(){
//父级目录
String url = "D:"+File.separator+"file";
File file = new File(url,"reNameTest.txt");
//判断是否是一个文件
log.info(file.isFile()?file.getName()+"是一个文件":file.getName()+"不是一个文件");
//重命名
boolean rename = file.renameTo(new File(url, "重命名测试文件"));
log.info(rename?"重命名成功!":"重命名失败!");
}
测试结果:
源路径:
原本的txt文件现在名字重命名了,但是类型却变了,检查代码发现是因为新的文件名没有加上后缀名
//重命名
boolean rename = file.renameTo(new File(url, "重命名测试文件"));
直接手动加上就行了
这里是允许通过修改文件名来修改文件类型的
直接删除刚刚重命名的文件
/**
* 文件删除测试用例
*/
@Test
public void deleteFile(){
//父级目录
String url = "D:"+File.separator+"file";
File file = new File(url,"重命名测试文件.txt");
//判断是否是一个文件
log.info(file.isFile()?file.getName()+"是一个文件":file.getName()+"不是一个文件");
boolean delete = file.delete();
log.info(delete ? "删除成功!" : "删除失败!");
}
运行结果
文件的.isDelete()方法简单,直接调用即可,如果出现删除失败情况,检查路径文件名是否正确以及流是否关闭
在源路径下新建一个copyTest.txt文件,里面写一行copyTest
文件复制的方法有很多种,我这边使用的是FileChannel方法主要代码如下:
/**
* 文件复制测试
*/
@Test
public void copyFile() {
//源文件路径
String url1 = "D://file/copyTest.txt";
//拷贝的目标路径
String url2 = "D://file_copy/";
File fromFile = new File(url1);
//判断是不是一个文件
if (!fromFile.isFile()) {
log.error("不是一个文件!");
}
File dir = new File(url2);
//判断目标路径是否存在,不存在需要建立该文件夹
if (!dir.exists()) {
//创建文件夹
boolean mkdirs = dir.mkdirs();
if (mkdirs) {
log.info("创建" + dir.getPath() + "成功!");
} else {
log.info("创建" + dir.getPath() + "失败!");
}
}
//在目标路径下新建一个与原文件同名的文件对象
File file = new File(url2, fromFile.getName());
// copy file
try (
//这里是java7的新特性,此括号中的流会在结束时自动关闭
FileInputStream in = new FileInputStream(fromFile);
FileOutputStream ou = new FileOutputStream(file);
FileChannel ci = in.getChannel();
FileChannel co = ou.getChannel();
) {
//执行复制
ci.transferTo(0, ci.size(), co);
} catch (IOException e) {
log.error(e.getMessage());
} finally {
log.info("文件复制成功!");
}
}
ci.transferTo(0, ci.size(), co);//核心代码
上面已经实现了文件的复制功能,剪切功能无非就是在文件复制完成之后,把原来的文件删除就行了,所以代码如下:
/**
* 文件复制测试
*/
@Test
public void copyFile() {
//源文件路径
String url1 = "D://file/copyTest.txt";
//拷贝的目标路径
String url2 = "D://file_copy/";
File fromFile = new File(url1);
if (!fromFile.isFile()) {
log.error("不是一个文件!");
}
File dir = new File(url2);
if (!dir.exists()) {
boolean mkdirs = dir.mkdirs();
if (mkdirs) {
log.info("创建" + dir.getPath() + "成功!");
} else {
log.info("创建" + dir.getPath() + "失败!");
}
}
File file = new File(url2, fromFile.getName());
// copy file
try (
FileInputStream in = new FileInputStream(fromFile);
FileOutputStream ou = new FileOutputStream(file);
FileChannel ci = in.getChannel();
FileChannel co = ou.getChannel();
) {
//执行复制
ci.transferTo(0, ci.size(), co);
//删除原文件
boolean delete = fromFile.delete();
if (delete) {
log.info("旧文件删除成功!");
} else {
log.info("旧文件删除失败!");
}
} catch (IOException e) {
log.error(e.getMessage());
} finally {
log.info("文件复制成功!");
}
}
运行时却发现
程序没有报错,却无法删除原文件,使用Windows资源管理器是可以删除的。
检查代码发现
这里的流是得在try{}的语句执行完毕之后才会关闭的,而删除的代码也是写在try{}里面的,就是说流没有关闭,该文件被占用了,所以无法删除。
对代码进行修改:
// copy file
try (
FileInputStream in = new FileInputStream(fromFile);
FileOutputStream ou = new FileOutputStream(file);
FileChannel ci = in.getChannel();
FileChannel co = ou.getChannel();
) {
//执行复制
ci.transferTo(0, ci.size(), co);
} catch (IOException e) {
log.error(e.getMessage());
} finally {
log.info("文件复制成功!");
//删除原文件
boolean delete = fromFile.delete();
if (delete) {
log.info("旧文件删除成功!");
} else {
log.info("旧文件删除失败!");
}
}
再次运行:
源文件已被删除
文件删除是无法在文件被占用(流没有关闭)情况下进行的
将file文件夹重命名为fileRename
/**
* 文件夹重命名测试
*/
@Test
public void folderRename(){
String url = "D://file";
File file = new File(url);
boolean exists = file.exists();
if (!exists){
log.error(file.getName()+"不存在!");
}
boolean isDirectory = file.isDirectory();
if (!isDirectory){
log.error(file.getName()+"不是一个文件夹!");
}
boolean fileRename = file.renameTo(new File("D://fileRename"));
log.info(fileRename?"重命名成功!":"重命名失败!");
}
在java中,非空文件夹是无法直接使用.delete()方法删除的,并且需要考虑一下三种情况:
/**
* 文件夹删除
*/
@Test
public void deleteFolder(){
String url = "D://deleteFolder";
folderDelete(url);
}
private static void folderDelete(String url){
File file = new File(url);
boolean isDirectory = file.isDirectory();
if (!isDirectory) {
log.warn(file.getName()+"不是一个文件夹");
}
//获取文件夹下的所有文件
File[] files = file.listFiles();
//判断是否为空文件夹,是则直接删除
if (files == null||files.length==0) {
log.info(file.getName()+"为空文件夹");
boolean delete = file.delete();
log.info(delete?"删除成功!":"删除失败!");
}
}
在deleteFolder文件夹下新建一个deleteTest.txt文件
/**
* 文件夹删除
*/
@Test
public void deleteFolder(){
String url = "D://deleteFolder";
folderDelete(url);
}
private static void folderDelete(String url){
File formFile = new File(url);
boolean isDirectory = formFile.isDirectory();
if (!isDirectory) {
log.warn(formFile.getName()+"不是一个文件夹");
}
//获取文件夹下的所有文件
File[] files = formFile.listFiles();
//判断是否为空文件夹,是则直接删除
if (files == null||files.length==0) {
log.info(formFile.getName()+"为空文件夹");
boolean delete = formFile.delete();
log.info(delete?"删除成功!":"删除失败!");
}else {
for (File file : files) {
log.info(file.delete()?file.getName()+"删除成功!":file.getName()+"删除成功!");
}
}
}
运行结果:
可以看到文件夹下的文件已经删除掉了,可是文件夹还在,只需要在for循环结束后删除原文件夹就行了
log.info(formFile.delete()?formFile.getName()+"删除成功!":formFile.getName()+"删除成功!");
在文件夹里面添加多个文件之后重新运行
资源管理器里面该文件夹也不在了
之前也有讲过,这里是要使用递归的,所以还不了解递归的同学需要先去补补课,接下来还要用到的。
原理:遍历文件数组时对文件进行判断,文件则直接删除,非空文件夹则调用删除文件夹方法,传入该文件夹的路径。简单来说,就是循环删除,是文件夹就先删除文件夹下的文件,然后删除自己。
代码修改部分如下:
for (File file : files) {
if (file.isFile()) {
log.info(file.delete()?file.getName()+"删除成功!":file.getName()+"删除成功!");
}else {
File[] listFiles = file.listFiles();
//判断是否非空
if (listFiles == null||listFiles.length==0) {
log.info(file.getName()+"为空文件夹");
boolean delete = file.delete();
log.info(delete?"删除成功!":"删除失败!");
}else {
//非空则调用删除文件夹方法
folderDelete(file.getAbsolutePath());
}
}
}
再次建立deleteFolder 文件夹,目录结构如下
上面已经完成了文件夹的删除功能,复制功能实际上差不太多,把删除操作改为复制操作即可
所以就不一步一步演示,直接贴代码,重点部分会写注释
新建文件夹file,目录结构为
/**
* 复制文件夹
*/
@Test
public void copyFolderTest() throws FileNotFoundException {
String fromUrl = "D://file/";
String toUrl = "D://file_copy/";
copyFolder(fromUrl, toUrl);
}
/**
* 文件夹复制
* @param fromUrl 源路径
* @param toUrl 目标路径
*/
private static void copyFolder(String fromUrl, String toUrl) {
File fromFile = new File(fromUrl);
//判断源路径是不是文件夹,这里暂时只考虑文件夹,在多文件操作中文件夹和文件都可以进行操作
if (!fromFile.isDirectory()) {
log.warn(fromFile.getName() + "不是一个文件夹!");
}
//创建文件夹
File toFile = new File(toUrl, fromFile.getName());
//判断目标路径是否存在,是不是文件夹
if (!toFile.exists()) {
boolean mkdirs = toFile.mkdirs();
if (mkdirs) {
log.info(toFile + "文件夹创建成功!");
}
}
if (!toFile.isDirectory()) {
log.warn(toFile + "不是一个文件夹!");
}
//判断该文件夹是否为空
File[] files = fromFile.listFiles();
if ( files == null||files.length == 0) {
log.info(fromFile + "文件夹下没有文件!");
log.info(fromFile.getName() + "复制到" + toFile.getName() + "完成!");
} else {
//遍历文件数组
for (File file : files) {
//判断文件是文件还是文件夹
if (!file.isDirectory()) {
log.info(file.getName() + "是一个文件!");
//是文件则直接复制
try (
//将源文件装入输入流,从输出流写出
FileInputStream in = new FileInputStream(file);
FileOutputStream ou = new FileOutputStream(new File(toFile, file.getName()));
FileChannel ci = in.getChannel();
FileChannel co = ou.getChannel();
) {
ci.transferTo(0, ci.size(), co);
log.info(file.getName() + "复制成功!");
} catch (IOException e) {
log.error(e.getMessage());
}
} else {
//这是处理递归时传入的文件夹路径,由于是动态的,所以要动态的获取父目录
// 的路径和本文件夹的名称进行拼接
String name = file.getParentFile().getName();
log.info("父目录文件名:" + name);
//结构路径处理
boolean endsWith = toUrl.endsWith("/");
String url = endsWith ? toUrl + name : toUrl + File.separator + name;
copyFolder(file.getAbsolutePath(), url);
}
}
}
}
主要麻烦的地方是在于对路径的处理,需要多试几遍
运行结果:
文件复制顺序则是从外到内
和文件剪切一样,在遍历结束后删除文件夹就行了,最好是再判断一下是不是空的文件夹。
在这里,我又给复制方法加上了一个参数,boolean isDelete 是否删除,true则为删除原文件,达到剪切的功能。
代码:
/**
* 复制文件夹
*/
@Test
public void copyFolderTest() throws FileNotFoundException {
String fromUrl = "D://file";
String toUrl = "D://file_copy/";
copyFolder(fromUrl, toUrl, true);
}
private static void copyFolder(String fromUrl, String toUrl, boolean isDelete) {
File fromFile = new File(fromUrl);
if (!fromFile.isDirectory()) {
log.warn(fromFile.getName() + "不是一个文件夹!");
}
//创建文件夹
File toFile = new File(toUrl, fromFile.getName());
if (!toFile.isDirectory()) {
log.warn(toFile + "不是一个文夹!");
}
if (!toFile.exists()) {
boolean mkdirs = toFile.mkdirs();
if (mkdirs) {
log.info(toFile + "文件夹创建成功!");
}
}
//判断该文件夹是否为空
File[] files = fromFile.listFiles();
assert files != null;
if (files.length == 0) {
log.info(fromFile + "文件夹下没有文件!");
log.info(fromFile.getName() + "复制到" + toFile.getName() + "完成!");
if (isDelete){
boolean delete = fromFile.delete();
log.info(delete?fromFile.getName() +"删除成功!":fromFile.getName() +"删除失败!");
}
} else {
for (File file : files) {
if (!file.isDirectory()) {
log.info(file.getName() + "是一个文件!");
try (
FileInputStream in = new FileInputStream(file);
FileOutputStream ou = new FileOutputStream(new File(toFile, file.getName()));
FileChannel ci = in.getChannel();
FileChannel co = ou.getChannel();
) {
ci.transferTo(0, ci.size(), co);
log.info(file.getName() + "复制成功!");
} catch (IOException e) {
log.error(e.getMessage());
} finally {
if (isDelete) {
boolean delete = file.delete();
if (delete) {
log.info("源文件" + file.getName() + "删除成功!");
} else {
log.info("源文件" + file.getName() + "删除失败!");
}
}
}
} else {
log.info(file.getName() + "是一个目录!");
log.info("路径为:" + file.getAbsolutePath());
log.info("父目录为:" + file.getParent());
String name = file.getParentFile().getName();
log.info("父目录文件名:" + name);
//结构路径处理
boolean endsWith = toUrl.endsWith("/");
String url = endsWith ? toUrl + name : toUrl + File.separator + name;
copyFolder(file.getAbsolutePath(), url, isDelete);
}
}
if (isDelete) {
//判断该文件夹是否还有子文件
File[] folderFiles = fromFile.listFiles();
if (folderFiles==null||folderFiles.length == 0) {
log.info("文件夹为空!");
boolean delete = fromFile.delete();
log.info(delete ? "删除成功!" : "删除失败!");
}else {
String name = fromFile.getParentFile().getName();
log.info("父目录文件名:" + name);
//结构路径处理
boolean endsWith = toUrl.endsWith("/");
String url = endsWith ? toUrl + name : toUrl + File.separator + name;
copyFolder(fromFile.getAbsolutePath(), url, true);
}
}
}
}
基本操作已经做完了,接下来就是实际中会用到的了,因为在业务中不可能一直都是针对文件的操作或者是只针对文件夹的操作,也不可能一直都是同时只操作一个文件或者一个文件夹,所以接下来要实现的就是多文件的操作;
以下代码不再区分操作的是文件夹还是文件,而是根据路径进行区别文件夹和文件
多文件重命名首先存在的问题就是同名问题(文件夹和文件不存在同名问题),
处理方式为:
检测该文件/文件夹是否已经存在,存在则在后面加上(n)后缀 n为数字编号
初步代码为:
/**
* 文件夹重命名测试
*/
@Test
public void folderRename(){
//多文件路径
List<String> urls = Arrays.asList("D://file/test1", "D://file/test2", "D://file/test3","D://file/test1.txt");
//新文件名
String newName = "rename";
//编号 文件夹和文件的编号区分开避免混淆
int fileIndex = 0;
int folderIndex = 0;
for (String url : urls) {
File file = new File(url);
if (!file.exists()){
log.error(file.getName()+"不存在!");
continue;
}
if (file.isFile()){
log.info(file.getName()+"是一个文件!");
//建立一个新文件对象
File toFile = new File(file.getParent(), newName);
//判断新文件是否存在
boolean exists = toFile.exists();
if (exists){
log.info(new File(file.getParent(), newName).getName() + "文件已存在!");
//文件的重命名需要操作文件名,把(index)加到后缀前面
fileIndex++;
String fileName = file.getName();
//用 . 把文件名切分开,取后缀名
String[] split = fileName.split("\\.");
log.info("split[0]:"+split [0]+",split[1]:"+split [1]);
File newFile = new File(file.getParent(), newName+"("+fileIndex+")."+split[1]);
boolean fileRename = file.renameTo(newFile);
log.info(fileRename?file.getName()+"重命名成功!新文件名"+newFile.getName():file.getName()+"重命名失败!");
}else {
log.info(new File(file.getParent(), newName).getName() + "文件不存在!");
String[] split = file.getName() .split("\\.");
File newFile = new File(file.getParent(), newName+"."+split[1]);
boolean fileRename = file.renameTo(newFile);
log.info(fileRename?file.getName()+"重命名成功!新文件名"+newFile.getName():file.getName()+"重命名失败!");
}
}else if(file.isDirectory()) {
boolean exists = new File(file.getParent(), newName).exists();
if (exists){
log.info(new File(file.getParent(), newName).getName() + "文件夹已存在!");
//文件夹的重命名比较简单,直接在末尾加上(index)即可
folderIndex++;
File newFile = new File(file.getParent(), newName + "(" + folderIndex + ")");
boolean fileRename = file.renameTo(newFile);
log.info(fileRename?file.getName()+"重命名成功!新文件名"+newFile.getName():file.getName()+"重命名失败!");
}else {
log.info(new File(file.getParent(), newName).getName() + "文件夹不存在!");
File newFile = new File(file.getParent(), newName);
boolean fileRename = file.renameTo(newFile);
log.info(fileRename?file.getName()+"重命名成功!新文件名"+newFile.getName():file.getName()+"重命名失败!");
}
}
}
}
运行结果
文件夹重命名没问题,可是只有一个txt文件,为什么会被重命名成rename(1).txt呢?
根据输出发现,txt文件提示的是rename文件存在,这里判断的是文件夹而不是文件,检查代码发现确实存在错误,这里应该判断文件是否存在
新增一个test2.txt文件,更新后的代码如下:
/**
* 文件夹重命名测试
*/
@Test
public void folderRename(){
//多文件路径
List<String> urls = Arrays.asList("D://file/test1", "D://file/test2", "D://file/test3","D://file/test1.txt","D://file/test2.txt");
//新文件名
String newName = "rename";
//编号 文件夹和文件的编号区分开避免混淆
int fileIndex = 0;
int folderIndex = 0;
for (String url : urls) {
File file = new File(url);
if (!file.exists()){
log.error(file.getName()+"不存在!");
continue;
}
if (file.isFile()){
log.info(file.getName()+"是一个文件!");
String fileName = file.getName();
//用 . 把文件名切分开,取后缀名
String[] split = fileName.split("\\.");
//建立一个新文件对象
File toFile = new File(file.getParent(), newName+"."+split[1]);
//判断新文件是否存在
boolean exists = toFile.exists();
if (exists){
log.info(toFile.getName() + "文件已存在!");
//文件的重命名需要操作文件名,把(index)加到后缀前面
fileIndex++;
File newFile = new File(file.getParent(), newName+"("+fileIndex+")."+split[1]);
boolean fileRename = file.renameTo(newFile);
log.info(fileRename?file.getName()+"重命名成功!新文件名"+newFile.getName():file.getName()+"重命名失败!");
}else {
log.info(toFile.getName() + "文件不存在!");
boolean fileRename = file.renameTo(toFile);
log.info(fileRename?file.getName()+"重命名成功!新文件名"+toFile.getName():file.getName()+"重命名失败!");
}
}else if(file.isDirectory()) {
File toFile = new File(file.getParent(), newName);
boolean exists = toFile.exists();
if (exists){
log.info(toFile.getName() + "文件夹已存在!");
//文件夹的重命名比较简单,直接在末尾加上(index)即可
folderIndex++;
File newFile = new File(file.getParent(), newName + "(" + folderIndex + ")");
boolean fileRename = file.renameTo(newFile);
log.info(fileRename?file.getName()+"重命名成功!新文件名"+newFile.getName():file.getName()+"重命名失败!");
}else {
log.info(toFile.getName() + "文件夹不存在!");
File newFile = new File(file.getParent(), newName);
boolean fileRename = file.renameTo(newFile);
log.info(fileRename?file.getName()+"重命名成功!新文件名"+newFile.getName():file.getName()+"重命名失败!");
}
}
}
}
在文件夹删除中已经把多文件删除的大概功能完成了,和上面方法一样,把多个需要删除的文件路径组成数组,然后遍历数组操作就行了。这里不再演示。
之前的文件夹复制中,留了一个坑,就是没有处理文件的复制,现在来填上。
依旧是file文件夹,目录结构如下
/**
* 复制文件夹和文件
*/
@Test
public void copyFolderTest() {
List<String> fromUrls = Arrays.asList("D://file/test1","D://file/test1.txt");
String toUrl = "D://file_copy/";
for (String fromUrl : fromUrls) {
copyFolder(fromUrl,toUrl,false);
}
}
/**
* 文件夹复制
* @param fromUrl 源路径
* @param toUrl 目标路径
*/
private static void copyFolder(String fromUrl, String toUrl,boolean isDelete) {
File fromFile = new File(fromUrl);
//判断源路径是否存在
if (!fromFile.exists()) {
log.error(fromFile.getName() + "不存在!");
//跳出方法
return;
}
//判断源路径是文件还是文件夹
boolean isFile = fromFile.isFile();
if (isFile){
//判断目标路径是否存在
File toFile = new File(toUrl);
if (!toFile.exists()) {
boolean mkdirs = toFile.mkdirs();
if (mkdirs) {
log.info(toFile.getName() + "文件夹创建成功!");
}
}
//是文件则直接复制
try (
//将源文件装入输入流,从输出流写出
FileInputStream in = new FileInputStream(fromFile);
FileOutputStream ou = new FileOutputStream(new File(toFile, fromFile.getName()));
FileChannel ci = in.getChannel();
FileChannel co = ou.getChannel();
) {
ci.transferTo(0, ci.size(), co);
log.info(fromFile.getName() + "复制成功!");
} catch (IOException e) {
log.error(e.getMessage());
}finally {
//是否删除原文件
if (isDelete) {
boolean delete = fromFile.delete();
log.info(delete ? "源文件" + fromFile.getName() + "删除成功!" : "源文件" + fromFile.getName() + "删除失败!");
}
}
}else {
//判断目标路径是否存在
File toFile = new File(toUrl, fromFile.getName());
if (!toFile.exists()) {
boolean mkdirs = toFile.mkdirs();
if (mkdirs) {
log.info(toFile.getName() + "文件夹创建成功!");
}
}
//判断该文件夹是否为空
File[] files = fromFile.listFiles();
if ( files == null||files.length == 0) {
log.info(fromFile + "文件夹下没有文件!");
log.info(fromFile.getName() + "复制到" + toFile.getName() + "完成!");
} else {
//遍历文件数组
for (File file : files) {
//判断文件是文件还是文件夹
if (!file.isDirectory()) {
log.info("原文件"+file.getName() + "是一个文件!");
//是文件则直接复制
try (
//将源文件装入输入流,从输出流写出
FileInputStream in = new FileInputStream(file);
FileOutputStream ou = new FileOutputStream(new File(toFile, file.getName()));
FileChannel ci = in.getChannel();
FileChannel co = ou.getChannel();
) {
ci.transferTo(0, ci.size(), co);
log.info(file.getName() + "复制成功!");
} catch (IOException e) {
log.error(e.getMessage());
}finally {
if (isDelete) {
boolean delete = file.delete();
if (delete) {
log.info("源文件" + file.getName() + "删除成功!");
} else {
log.info("源文件" + file.getName() + "删除失败!");
}
}
}
} else {
//这是处理递归时传入的文件夹路径,由于是动态的,所以要动态的获取父目录
// 的路径和本文件夹的名称进行拼接
String name = file.getParentFile().getName();
//结构路径处理
boolean endsWith = toUrl.endsWith("/");
String url = endsWith ? toUrl + name : toUrl + File.separator + name;
copyFolder(file.getAbsolutePath(), url,isDelete);
}
}
if (isDelete) {
//判断该文件夹是否还有子文件
File[] folderFiles = fromFile.listFiles();
if (folderFiles==null||folderFiles.length == 0) {
log.info(fromFile.getName()+"文件夹为空!");
boolean delete = fromFile.delete();
log.info(delete ? fromFile.getName()+ "删除成功!" : fromFile.getName()+ "删除失败!");
}else {
String name = fromFile.getParentFile().getName();
//结构路径处理
boolean endsWith = toUrl.endsWith("/");
String url = endsWith ? toUrl + name : toUrl + File.separator + name;
copyFolder(fromFile.getAbsolutePath(), url, true);
}
}
}
}
}
这里需要注意的是判断目标路径是否存在的File对象路径,文件的话直接判断提供的路径就行了,文件夹是需要加上自己的文件名的,因为需要创建新的文件夹
运行结果
实际上还是漏了一点需求,如果复制的时候存在同名的情况咋办呢,所以我们要将之前所做的重命名整合进来,复制时判断是否已存在,已存在的话进行编号。
一开始我打算这样进行修改,然后发现这样的话两个index的值无法保证,因为同名情况不是一直都发生的,主要方法内部使用index++也无法同步到外部的这两个值,下次的值还是会一样。所以只能把遍历放到主要方法内部
------------------------------------------------------------------
说到这,我也以为我的想法是正确的,但是我忽略了一个问题,就是我这个程序只考虑到一次同名问题,举个栗子,我要拷贝test1.txt文件到file_copy文件夹,这时候test1.txt文件已经存在了。按照我的程序,这时候会重命名为test1(1).txt然后复制进去,但是,此文件夹下面的test(1).txt 文件已经存在了,因此还需要用到递归方法。
纠正一点,window资源管理器复制后的文件名为:文件名-副本.后缀 再进行复制的话为 文件名-副本(2).后缀 文件夹也是一样,在下面的代码中会进行更正
再参考一下Windows资源管理器,基本逻辑就已经形成了
第一次复制在后缀前面加上“-副本”,第二次会在"-副本"后面加上“(2)”,里面的数字递增,如果你复制了十个,括号里面的数字到10,随便删除中间的一个文件,比如说删除“-副本(5)”,然后再进行复制时,你会发现下一个不是11,而是刚刚删除的5。所以我猜想,Windows使用的也是递归,如果这个文件名存在就把数字+1再判断是否存在,直到找到不存在的文件名再进行保存。
括号中的数字是如何获得的呢?使用字符串操作方法找到第一个"(“和最后一个”)“的下标,取出中间的内容,判断是否为纯数字,是的话直接把数字加一放回去,如果不是则在末尾加上”(2)"。
这个我是通过Windows的小bug推出来的,创建一个文件名带括号,括号里面有数字的文件,复制时候会发现自己写的括号里面的数值会递增,但如果括号里面不是纯数字的话则无效,如果有多个括号,会一个一个检查是不是为纯数字,哪个是就把哪个括号的数字加一,都不是则自己添加
根据上面的分析,有以下代码
把上面代码中的boolean值改为true即可
运行结果
file文件夹没有删除的原因是复制的文件和文件夹为file文件夹下面的,而不是file整个文件夹
重点内容来啦,文件上传功能,我这边使用的是MultipartFile类来接收request中的文件
代码如下:
@RestController
@Log4j2
@ApiOperation(value = "文件处理控制器",notes = "??")
public class FileController extends BaseController{
/**
* 文件保存路径
*/
private static final String FOLDER ="D://file/";
@PostMapping("/file")
public String upload (MultipartFile file) throws Exception {
if (file == null) {
log.error("文件不存在");
throw new FileNotFoundException("文件不存在!");
}
if (file.isEmpty()) {
throw new FileNotFoundException("文件不能为空");
}
//取出原始文件名
String originalFilename = file.getOriginalFilename();
if (originalFilename==null|| "".equals(originalFilename)){
//如果 没有文件名或者文件名为空,则设置文件名为当前时间戳
originalFilename = System.currentTimeMillis()+"";
}
File localFile = new File(FOLDER,originalFilename);
File dir = new File(FOLDER);
log.info("该路径是否存在:"+dir.exists());
if (!dir.exists()){
boolean mkdirs = localFile.mkdirs();
if (mkdirs) {
log.info("创建文件夹成功!");
}else {
log.info("创建失败!");
}
}
//使用UUID生成随机文件名
String rename = UUID.randomUUID().toString().replace("-", "");
String[] split = originalFilename.split("\\.");
File reNameFile = new File(FOLDER, rename + "." + split[1]);
log.info("是否是一个文件夹:"+dir.isDirectory());
file.transferTo(reNameFile);
return "OK";
}
}
同时前端给了我一份文件上传的页面,我改好接口直接使用就行
这样就上传成功啦
@GetMapping("/file/{fileName}")
public void download(@PathVariable String fileName, HttpServletRequest request, HttpServletResponse response) throws Exception{
try (
InputStream inputStream = new FileInputStream(new File(FOLDER, fileName));
OutputStream outputStream = response.getOutputStream();){
response.setContentType("application/x-download;charset=UTF-8");
response.setHeader("Content-Disposition","attachment;filename="+fileName);
IOUtils.copy(inputStream, outputStream);
outputStream.flush();
}catch (IOException e){
log.error(e.getMessage());
}
}
下载代码也很简单,只需要把文件夹中的文件读取到流中复制到响应中就行了
多文件的操作我这边偷了个懒,前端直接根据接收到的文件数量来发多个请求,则后端不需要处理