Java File常用操作总结

Posted by     "Eric" on Wednesday, February 8, 2023

1. File对象

Java File是文件对象的抽象,对应于一个目录或一个文件。

当使用pathname创建一个File对象的时,该目录可以不存在,但在对该对象进行一些操作,比如创建一个FileOutputStream时,如果文件目录不存在,则可能会报错,比如:

public void test1() throws IOException {
    // /Users/wuhao目录下不存在目录aa
    File file = new File("/Users/wuhao/aa/a.txt");
    FileOutputStream fileOutputStream = new FileOutputStream(file);
    fileOutputStream.write(1);
}

执行结果:
java.io.FileNotFoundException: /Users/wuhao/aa/a.txt (No such file or directory)
	at java.io.FileOutputStream.open0(Native Method)
	at java.io.FileOutputStream.open(FileOutputStream.java:270)
	at java.io.FileOutputStream.<init>(FileOutputStream.java:213)
	at java.io.FileOutputStream.<init>(FileOutputStream.java:162)
	at Test.test1(Test.java:23)
  
public void test2() throws IOException {
    // /Users/wuhao目录下不存在目录aa/bb
    File file = new File("/Users/wuhao/aa/bb/a.txt");
    // 使用File的mkdirs()方法创建必要的父目录
  	file.getParentFile().mkdirs();
    FileOutputStream fileOutputStream = new FileOutputStream(file);
    fileOutputStream.write(1);
}

2. File对象的操作

2.1 移动文件(复制文件)

/**
 * 复制文件
 * 方法一: 使用文件流
 *
 * @param source 源文件
 * @param dest   目标文件
 */
private static void copyFileUsingFileStreams(File source, File dest)
        throws IOException {    
    InputStream input = null;    
    OutputStream output = null;    
    try {
           input = new FileInputStream(source);
           output = new FileOutputStream(dest);        
           byte[] buf = new byte[1024];        
           int bytesRead;        
           while ((bytesRead = input.read(buf)) > 0) {
               output.write(buf, 0, bytesRead);
           }
    } finally {
        input.close();
        output.close();
    }
}

/**
 * 复制文件
 * 方法二: 使用NIO的FileChannel复制
 *
 * @param source 源文件
 * @param dest   目标文件
 */
private static void copyFileUsingFileChannels(File source, File dest) throws IOException {    
        FileChannel inputChannel = null;    
        FileChannel outputChannel = null;    
    try {
        inputChannel = new FileInputStream(source).getChannel();
        outputChannel = new FileOutputStream(dest).getChannel();
        outputChannel.transferFrom(inputChannel, 0, inputChannel.size());
    } finally {
        inputChannel.close();
        outputChannel.close();
    }
}

/**
 * 复制文件
 * 方法三: 使用Apache Commons IO提供的工具类进行复制,这个类主要使用了Java NIO Channel
 *
 * @param source 源文件
 * @param dest   目标文件
 */
private static void copyFileUsingApacheCommonsIO(File source, File dest)
        throws IOException {
    FileUtils.copyFile(source, dest);
}

/**
 * 复制文件
 * 方法四: 使用Java7的Files类复制
 *
 * @param source 源文件
 * @param dest   目标文件
 */
private static void copyFileUsingJava7Files(File source, File dest)
        throws IOException {    
        Files.copy(source.toPath(), dest.toPath());
}

2.2 查找文件

/**
 * 寻找dir目录下文件名为filename的子文件
 *
 * @param dir 根目录
 * @param filename 待查找的文件名
 * @return
 */
public static File findFile(String dir, String filename) {
    File file = new File(dir);
    if (!file.exists() || !file.isDirectory()) {
        return null;
    }
    File[] files = file.listFiles();
    for (File subFile : files) {
        if (subFile.isDirectory()) {
            File result = findFile(subFile.getAbsolutePath(), filename);
            if (result != null) {
                return result;
            }
        } else if (subFile.getName().equals(filename)) {
            return subFile;
        }
    }
    return null;
}

2.3 删除文件

public static void delete(String path) {
    if (StringUtils.isBlank(path)) {
        return;
    }
    File f = new File(path);
    if (!f.exists()) {
        return;
    }
    if (f.isDirectory() && f.list() != null) {
      //如果是目录,先递归删除
      String[] list = f.list();
      for (int i = 0; i < list.length; i++) {
        delete(path + File.separator + list[i]);//先删除目录下的文件
      }
    }
  	// 如果File是目录,必须是空目录才能删除,因此需要递归整个目录,将其子文件依次删除
    f.delete();
}

2.4 压缩、解压文件

Zip包的压缩类为ZipOutputStream和ZipInputStream,属于InputStream和OutputStream的继承层次。压缩类的使用非常直观——直接将输出流封装为ZipOutputStream,并将输入流封装成ZipInputStream即可。

/**
 * 压缩成ZIP
 *
 * @param srcDir 压缩文件夹路径
 * @param out 压缩文件输出流
 * @param keepDirStructure 是否保留原来的目录结构,true:保留目录结构;
 *         false:所有文件跑到压缩包根目录下(注意:不保留目录结构可能会出现同名文件,会压缩失败)
 * @throws MarketException 压缩失败会抛出运行时异常
 */
public static void toZip(String srcDir, OutputStream out, boolean keepDirStructure)
        throws RuntimeException {

  long start = System.currentTimeMillis();
  ZipOutputStream zos = null;
  try {
    // ZipOutputStream属于OutputStream继承层次
    zos = new ZipOutputStream(out);
    File sourceFile = new File(srcDir);
    // 核心方法
    compress(sourceFile, zos, sourceFile.getName(), keepDirStructure);
    long end = System.currentTimeMillis();
    log.info("压缩完成,耗时:{} ms", end - start);
  } catch (Exception e) {
      throw new MarketException(ResultStatus.PARAM_ERR, "压缩文件时失败", e);
  } finally {
    if (zos != null) {
      try {
          zos.close();
      } catch (IOException e) {
          log.error("zos关闭失败", e);
      }
    }
  }
}

/**
 * 递归压缩方法
 *
 * @param sourceFile 源文件
 * @param zos zip输出流
 * @param name 压缩后的名称
 * @param keepDirStructure 是否保留原来的目录结构,true:保留目录结构;
 *         false:所有文件跑到压缩包根目录下(注意:不保留目录结构可能会出现同名文件,会压缩失败)
 * @throws Exception
 */
private static void compress(File sourceFile, ZipOutputStream zos, String name,
        boolean keepDirStructure) throws Exception {
  byte[] buf = new byte[BUFFER_SIZE];
  if (sourceFile.isFile()) {
    // 向zip输出流中添加一个zip实体,构造器中name为zip实体的文件的名字
		// 对于每个要加入压缩档案的文件,都必须调用putNextEntry(),并将其传递给一个ZipEntry对象
    zos.putNextEntry(new ZipEntry(name));
    // copy文件到zip输出流中
    int len;
    FileInputStream in = new FileInputStream(sourceFile);
    while ((len = in.read(buf)) != -1) {
        zos.write(buf, 0, len);
    }
    // Complete the entry
    zos.closeEntry();
    in.close();
  } else {
    File[] listFiles = sourceFile.listFiles();
    if (listFiles == null || listFiles.length == 0) {
        // 需要保留原来的文件结构时,需要对空文件夹进行处理
      if (keepDirStructure) {
        // 空文件夹的处理
        zos.putNextEntry(new ZipEntry(name + File.separator));
        // 没有文件,不需要文件的copy
        zos.closeEntry();
      }
    } else {
      for (File file : listFiles) {
        // 判断是否需要保留原来的文件结构
        if (keepDirStructure) {
          // 注意:file.getName()前面需要带上父文件夹的名字加一斜杠,
          // 不然最后压缩包中就不能保留原来的文件结构,即:所有文件都跑到压缩包根目录下了
          compress(file, zos, name + File.separator + file.getName(), keepDirStructure);
        } else {
          // 将所有文件放到根目录下
          compress(file, zos, file.getName(), keepDirStructure);
        }
      }
    }
  }
}
/**
 * 解压缩zip文件
 * 解压zip文件有一个简便的方法——利用ZipFile对象读取文件,该对象有一个entries()方法,用来返回一个ZipEntry对象的Enumeration
 * (枚举)
 * 
 * @param srcFile
 * @param destDirPath
 * @return 返回解压后目录的地址
 * @throws RuntimeException
 */
public static String unZip(File srcFile, String destDirPath) throws RuntimeException {
  // 判断源文件是否存在
  if (!srcFile.exists()) {
      throw new BizException(ResultCode.PARAM_ERROR, srcFile.getPath() + "所指文件不存在");
  }
  // 开始解压
  ZipFile zipFile = null;
  String zipDir = null;
  try {
    zipFile = new ZipFile(srcFile);
    Enumeration<?> entries = zipFile.entries();
    while (entries.hasMoreElements()) {
      ZipEntry entry = (ZipEntry) entries.nextElement();
      if (StringUtils.isBlank(zipDir)) {
        zipDir = entry.getName().substring(0, entry.getName().indexOf("/"));
      }
      // 如果是文件夹,就创建个文件夹
      if (entry.isDirectory()) {
        String dirPath = destDirPath + "/" + entry.getName();
        File dir = new File(dirPath);
        dir.mkdirs();
      } else {
        // 如果是文件,就先创建一个文件,然后用io流把内容copy过去
        File targetFile = new File(destDirPath + "/" + entry.getName());
        // 保证这个文件的父文件夹必须要存在
        targetFile.getParentFile().mkdirs();
        // 将压缩文件内容写入到这个文件中
        InputStream is = zipFile.getInputStream(entry);
        FileOutputStream fos = new FileOutputStream(targetFile);
        int len;
        byte[] buf = new byte[BUFFER_SIZE];
        while ((len = is.read(buf)) != -1) {
            fos.write(buf, 0, len);
        }
        // 关流顺序,先打开的后关闭
        fos.close();
        is.close();
      }
    }
    return destDirPath + File.separator + zipDir;
  } catch (Exception e) {
      throw new BizException(ResultCode.PARAM_ERROR, "解压文件失败", e);
  } finally {
    if (zipFile != null) {
      try {
          zipFile.close();
      } catch (IOException e) {
          e.printStackTrace();
      }
    }
  }
}