在Java中操作文件流时,确保及时关闭文件流是非常重要的。在Windows系统上,由于文件锁定机制比较严格,如果一个文件流没有被关闭,可能会导致文件被锁定,从而阻止其它程序对文件的访问,例如进行删除操作。
package com.example.demo.serivice;
import java.io.File;
public interface IFileService {
void handler(File file) throws Exception;
}
package com.example.demo.serivice.impl;
import cn.hutool.core.io.FileUtil;
import com.example.demo.serivice.IFileService;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.FileInputStream;
@Service
public class FileServiceImpl implements IFileService {
@Override
public void handler(File file) throws Exception{
FileInputStream fileInputStream = new FileInputStream(file);
// 文件处理...
FileUtil.del(file);
}
}
package com.example.demo.controller;
import com.example.demo.serivice.IFileService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.File;
@RestController
@RequestMapping("/file")
@Slf4j
public class FileController {
@Autowired
private IFileService fileService;
@PostMapping("/handler")
public void handler(){
log.info("---------开始测试---------");
File file =new File("D:\\test\\example.docx");
try {
fileService.handler(file);
} catch (Exception e) {
e.printStackTrace();
}
log.info("---------结束测试---------");
}
}
项目服务启动后,访问/file/handler接口,控制台中会报错:
cn.hutool.core.io.IORuntimeException: FileSystemException: D:\test\example.docx: 另一个程序正在使用此文件,进程无法访问。
在上述业务逻辑处理中,虽然fileInputStream对象和删除操作都是位于handler方法中,但是由于fileInputStream对象未调用close方法关闭文件流,指定的文件还是处于锁定状态,所以后面接着调用FileUtil工具类的del方法进行删除操作是会报错的。
针对上述这种情况,我们只需要在FileUtil.del();这行代码的前面添加下述代码就可以:
fileInputStream.close();
除了合理的关闭文件流,还有另外一种处理方法,就是在业务逻辑处理的外层调用System.gc();进行垃圾回收。
import com.example.demo.serivice.IFileService;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.FileInputStream;
@Service
public class FileServiceImpl implements IFileService {
@Override
public void handler(File file) throws Exception{
FileInputStream fileInputStream = new FileInputStream(file);
// 文件处理...
}
}
import cn.hutool.core.io.FileUtil;
import com.example.demo.serivice.IFileService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.File;
@RestController
@RequestMapping("/file")
@Slf4j
public class FileController {
@Autowired
private IFileService fileService;
@PostMapping("/handler")
public void handler(){
log.info("---------开始测试---------");
File file =new File("D:\\test\\example.docx");
try {
fileService.handler(file);
System.gc();
} catch (Exception e) {
e.printStackTrace();
} finally {
FileUtil.del(file);
}
log.info("---------结束测试---------");
}
}
不过并不是很建议采用使用垃圾回收这种处理机制来解决这个问题,因为垃圾回收的过程需要占用CPU时间片,而且在执行垃圾回收时,应用程序的运行会被暂停,频繁的垃圾回收会影响我们的用户体验(Web界面卡顿),这个要慎用,当然作为一个排查问题的调试思路还是可以的。
在Java中,如果你使用的是FileOutputstream、Filewriter等文件输出流,这些流在打开文件时可能会获取文件锁,而当流关闭时,相应的文件锁会被释放。因此,如果你没有在使用完文件流后关闭它,可能会导致文件锁一直存在,阻止其它进程对文件的操作。
在Mac或Linux系统上,文件锁定机制和Windows系统不同,可能会出现这么一种情况,即使文件流没有关闭但仍然可以删除文件。在Java中,你可以使用close()方法确保在使用完文件流后关闭流,这样就可以避免文件被锁定,使得在Windows、Mac和Linux系统上都能够正常删除文件。
总之,为了确保跨平台的兼容性和良好的文件管理实践,最好在使用完文件流后及时关闭它,以释放文件锁并允许其它进程对文件执行操作。
文件描述符:文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。
文件描述符就是内核为了高效管理已被打开的文件所创建的索引,用来指向被打开的文件,所有执行I/O操作的系统调用都会通过文件描述符。
资源泄漏:通常指的是系统资源,比如socket、文件描述符等,因为这些在系统中都是有限制的,如果创建了而不归还,久而久之,就会耗尽资源,导致其它程序不可用。
文件泄漏(资源泄漏包含文件泄漏):是指在程序中未正确关闭文件描述符或文件句柄,导致文件资源无法被释放的情况。这可能导致系统资源被耗尽,使得系统无法正常工作。
在Linux系统中,文件泄漏是一个比较常见的问题,一般情况下,删除一个文件并不会导致文件泄漏。删除文件只是从文件系统中删除文件的目录项(directory entry),而文件系统会维护一个文件的引用计数。只有当文件的引用计数降为零时,文件系统才会释放文件的磁盘空间。 文件泄漏通常是由于在程序中未正确关闭文件描述符或文件句柄,导致文件资源无法被释放,而不是由于删除文件引起的。删除文件只是删除了文件系统中的目录项,并不直接影响已经打开的文件描述符。
当一个文件被删除时,如果有进程持有该文件的打开句柄,该文件仍然存在于文件系统中(原始文件会一直占用磁盘空间),直到所有的文件描述符都被关闭。
以下是一些可能导致文件泄漏的常见原因以及如何避免它们:
在程序中,确保在使用完文件后调用相应的关闭函数,例如Java语言中的close()方法。
在程序中处理异常或错误时,确保也关闭相应的文件。使用try-catch或类似机制来捕获异常,然后在适当的位置关闭文件。
FileReader reader = null;
try{
reader = new FileReader("example.txt")
// 使用文件...
}catch(IOException e){
e.printStackTrace();
}finally{
try{
if(reader!=null){
reader.close()
}
}catch(IOException e){
e.printStackTrace();
}
}
如果你在删除文件后仍然遇到文件泄漏问题,你可能需要仔细检查程序中文件的打开和关闭逻辑,确保文件描述符在适当的时候被关闭。
另外,在Linux系统中可以使用"lsof"命令来查看哪些文件虽然已经删除了,但是仍然被打开,可以帮助你找出潜在的文件泄漏问题。
如果你使用Java7或更高版本,可以使用try-with-resources语句,它可以自动关闭实现"AutoCloseable"接口的资源,包括文件流。
import java.io.File;
public interface IFileService {
void handler(File file);
}
import com.example.demo.serivice.IFileService;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.FileInputStream;
@Service
public class FileServiceImpl implements IFileService {
@Override
public void handler(File file){
try(FileInputStream fileInputStream = new FileInputStream(file)){
// 文件处理...
}catch (Exception e){
e.printStackTrace();
}
}
}
import cn.hutool.core.io.FileUtil;
import com.example.demo.serivice.IFileService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.File;
@RestController
@RequestMapping("/file")
@Slf4j
public class FileController {
@Autowired
private IFileService fileService;
@PostMapping("/handler")
public void handler(){
log.info("---------开始测试---------");
File file =new File("D:\\test\\example.docx");
fileService.handler(file);
FileUtil.del(file);
log.info("---------结束测试---------");
}
}
对于更复杂的应用程序,可能需要使用资源管理工具或框架,以确保在资源使用完成后进行适当的清理。例如在Java中可以使用Apache Commons IO库中的"IOUtils.closeQuitely()"方法。
commons-io
commons-io
2.11.0
import java.io.File;
public interface IFileService {
void handler(File file);
}
import com.example.demo.serivice.IFileService;
import org.apache.commons.io.IOUtils;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.FileInputStream;
@Service
public class FileServiceImpl implements IFileService {
@Override
public void handler(File file){
FileInputStream fileInputStream=null;
try{
fileInputStream = new FileInputStream(file);
// 文件处理...
}catch (Exception e){
e.printStackTrace();
}finally {
IOUtils.closeQuietly(fileInputStream); // 使用框架安全关闭文件流
}
}
}
import cn.hutool.core.io.FileUtil;
import com.example.demo.serivice.IFileService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.File;
@RestController
@RequestMapping("/file")
@Slf4j
public class FileController {
@Autowired
private IFileService fileService;
@PostMapping("/handler")
public void handler(){
log.info("---------开始测试---------");
File file =new File("D:\\test\\example.docx");
fileService.handler(file);
FileUtil.del(file);
log.info("---------结束测试---------");
}
}
通过遵循良好的文件资源管理实践,你可以有效地避免文件泄漏问题。同时,在编写代码时要留意异常处理,以确保即使发生错误,也能够正确地关闭文件资源。