1. 应用场景:
文件上传的时候,如果文件过大(可以自己设置),为了降低内存消耗会在系统某个目录下生成 临时文件,程序中 处理的时候,只会带着 这个临时文件 的目录(而不是字节数组),但是随着时间的推移,临时文件越来越大,对磁盘也是一种很大的压力,这种时候就需要虚引用和引用队列了,当 一个对象A刚创建的时候,将这个对象和一个虚引用对象关联,同时将虚引用对象放到 引用队列里,当对象A 被垃圾回收的时候,就可以从引用队列里获取 虚引用对象,虚引用对象 可以定义 文件 全名称 属性,进行 对临时文件的删除操作。
2. 代码如下:
工具类:
package com.example.demo.clean;
import java.io.File;
/**
* @program: springboot_01
* @description:
* @author: guoyiguang
* @create: 2021-08-07 11:12
**/
public class FileCleaner {
/**
* The instance to use for the deprecated, static methods.
*/
static final FileCleaningTracker theInstance = new FileCleaningTracker();
public static void addTracker(String path, Object marker) {
theInstance.addTracker(path, marker);
}
public static FileCleaningTracker getInstance() {
return theInstance;
}
//简单测试
public static void main(String[] args){
// E:\shan
// getInstance().track("E:\\shan\\tt.txt", new Object());
// getInstance().track("E:\\shan\\ttt.txt",new Object());
// System.gc();
// System.exit(0);
}
}
package com.example.demo.clean;
/**
* @program: 文件跟踪类(一个文件和 一个对象绑定,对象被垃圾回收进行相应操作)
* @description: 2、FileCleaningTracker类,
* @author: guoyiguang
* @create: 2021-08-07 11:09
**/
import java.io.File;
import java.io.IOException;
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.util.Collection;
import java.util.Vector;
public class FileCleaningTracker {
// 引用队列
ReferenceQueue q = new ReferenceQueue();
// synchronized
final Collection trackers = new Vector();
/**
* Whether to terminate the thread when the tracking is complete.
*/
volatile boolean exitWhenFinished = false;
// 用于删除文件的线程
Thread reaper;
/**
* @Description: synchronized 关键字 分布式的情况下 也是可以用的(如下的这种情况就可以用)
* @Param:
* @return:
* @Author: guoyiguang
* @Date:
*/
public synchronized void addTracker(String path, Object marker) {
if (path == null) {
throw new NullPointerException("The path must not be null");
}
// synchronized block protects reaper
if (exitWhenFinished) {
throw new IllegalStateException("No new trackers can be added once exitWhenFinished() is called");
}
if (reaper == null) {
reaper = new Reaper();
// 开启线程
reaper.start();
}
// 将 marker 和 引用队列绑定,marker 被回收后 ,进行删除文件
// 放到 trackers 集合里 是为了方便监控
trackers.add(new Tracker(path, marker, q));
}
/**
* @Description: 清理临时文件的守护线程
* @Param:
* @return:
* @Author: guoyiguang
* @Date:
*/
private final class Reaper extends Thread {
Reaper() {
super("File Reaper");
setPriority(Thread.MAX_PRIORITY);
// 设置为守护线程
setDaemon(true);
}
public void run() {
// thread exits when exitWhenFinished is true and there are no more tracked objects
while (exitWhenFinished == false || trackers.size() > 0) {
Tracker tracker = null;
try {
// 当 真实对象被删除 ,引用 队列 弹出 虚引用 对象(有 path 属性 知道 删除哪个 文件)
tracker = (Tracker) q.remove();
} catch (Exception e) {
continue;
}
if (tracker != null) {
// 删除文件
try {
FileDeleteStrategy.doDelete(new File(tracker.path));
} catch (IOException e) {
e.printStackTrace();
}
tracker.clear();
// 记录
trackers.remove(tracker);
}
}
}
}
//虚引用
private static final class Tracker extends PhantomReference {
// 要删除的文件路径
private final String path;
Tracker(String path, Object marker, ReferenceQueue queue) {
// 将 Tracker 虚引用 对象 的 marker对象 和 引用队列绑定 , 监控 marker 对象的生命周期
super(marker, queue);
this.path = path;
}
}
}
package java.lang.ref;
public class PhantomReference<T> extends Reference<T> {
/**
* Returns this reference object's referent. Because the referent of a
* phantom reference is always inaccessible, this method always returns
* <code>null</code>.
*
* @return <code>null</code>
*/
public T get() {
return null;
}
/**
* Creates a new phantom reference that refers to the given object and
* is registered with the given queue.
*
* <p> It is possible to create a phantom reference with a <tt>null</tt>
* queue, but such a reference is completely useless: Its <tt>get</tt>
* method will always return null and, since it does not have a queue, it
* will never be enqueued.
*
* @param referent the object the new phantom reference will refer to
* T 是 和虚引用对象关联的 对象
* @param q the queue with which the reference is to be registered,
* or <tt>null</tt> if registration is not required
*/
public PhantomReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
package com.example.demo.clean;
import java.io.File;
import java.io.IOException;
/**
* @program: 删除文件的工具类
* @description:
* @author: guoyiguang
* @create: 2021-08-07 11:06
**/
public class FileDeleteStrategy {
public static final FileDeleteStrategy NORMAL = new FileDeleteStrategy("Normal");
private final String name;
protected FileDeleteStrategy(String name) {
this.name = name;
}
public static boolean doDelete(File fileToDelete) throws IOException {
if (fileToDelete == null || fileToDelete.exists() == false){
System.out.println("文件"+fileToDelete.getPath()+ "不存在");
return false;
}
System.out.println("准备 删除文件"+fileToDelete.getPath());
boolean delete = fileToDelete.delete();
System.out.println(" 删除文件 result " + delete);
return delete;
}
}
测试方法:
@SneakyThrows
@Test
public void testList32() {
// windows C:\Users\dell\AppData\Local\Temp
File location = new File(System.getProperty("java.io.tmpdir"));
// sizeThreshold: The threshold (阈值 ) above which uploads will be stored on disk.
// 封装了临时文件的全路径(包括文件名)
FileItem item = new DiskFileItem("hh", "image/png",
false, "fileName.jpg", 1, location);
OutputStream outputStream = null;
try {
// 生成 临时文件 File 对象 和 临时文件名称 (以 .temp 后缀命名)
outputStream = item.getOutputStream();
// 写入内容
// 查看 实际走的OutputStream 接口的 哪个实现类
// class org.apache.tomcat.util.http.fileupload.DeferredFileOutputStream
System.out.println(outputStream.getClass());
// DeferredFileOutputStream 父类的 write 方法
outputStream.write("DCTK_2021080300011234565432123456543234565432234543234543234543234554323456".getBytes(StandardCharsets.UTF_8));
ApplicationPart part = new ApplicationPart(item, location);
MultipartFile multipartFile = new StandardMultipartFile2(part,"hh");
System.out.println(multipartFile);
// multipartFile.getInputStream();
String s = new String(multipartFile.getBytes());
System.out.println(s);
// 删除临时文件(断点 知道 DeferredFileOutputStream 里面 有 path 属性)
DeferredFileOutputStream outputStream1 = (DeferredFileOutputStream) outputStream;
System.out.println(" 绑定的目录 为:");
System.out.println( ((DeferredFileOutputStream) outputStream).getFile().getPath());
// 生成虚引用 对象
FileCleaner.getInstance().addTracker(outputStream1.getFile().getPath(), multipartFile);
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
// 必须关闭 流 ;否则对象回收不了
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
System.gc();
System.exit(0);
}
代码说明:
FileItem item = new DiskFileItem("hh", "image/png",
false, "fileName.jpg", 1, location);
// 生成 临时文件 File 对象 和 临时文件名称 (以 .temp 后缀命名)
OutputStream outputStream = item.getOutputStream();
生成临时文件:
@Override
public OutputStream getOutputStream()
throws IOException {
if (dfos == null) {
File outputFile = getTempFile();
dfos = new DeferredFileOutputStream(sizeThreshold, outputFile);
}
return dfos;
}
protected File getTempFile() {
if (tempFile == null) {
File tempDir = repository;
if (tempDir == null) {
// java 系统目录
tempDir = new File(System.getProperty("java.io.tmpdir"));
}
// 格式化 临时文件名称
// UID = UUID.randomUUID().toString().replace('-', '_')
// getUniqueId 生成唯的数
String tempFileName =
String.format("upload_%s_%s.tmp", UID, getUniqueId());
// 某个目录下创建某个文件
tempFile = new File(tempDir, tempFileName);
}
// 返回临时文件
return tempFile;
}
测试效果:
查看写入的数据:
手动调用:
System.gc();
后:
文件被删除:
查看日志: