1. 注意事项

(1). 需求

1.目前处理的项目中,有个任务线程会一直在网络上下载文件,所以需要定期将这些文件打包压缩,并且定期将过时太久的压缩文件删除,以防磁盘满。

2.删除压缩文件时,希望是根据文件容量大小,即设置一个阈值,当文件容量大小大于这个阈值,就删除文件,直至文件容量总量小于这个阈值。

3.删除压缩文件时,总是删除最老最旧的文件,以保持存在的总是最近最新的文件。

以上三个需求除了处理像我遇到的下载文件之外,同样可用于处理日志文件。

(2).首先要解决的是执行这样的定时任务。定期执行一个任务,可以使用轻量级定时调度工具cron4j,它的mvn引用如下:

<!-- https://mvnrepository.com/artifact/it.sauronsoftware.cron4j/cron4j -->
<dependency>
	<groupId>it.sauronsoftware.cron4j</groupId>
	<artifactId>cron4j</artifactId>
	<version>2.2.5</version>
</dependency>


(3).将打包和删除操作设置成守护线程在后台运行,当mainthread结束退出后,守护线程也会随之退出。

(4).生成压缩文件时,文件名可加入时间信息以防止命名冲突。如果文件夹下还有子文件夹,需要对该文件夹的每个文件进行判断,如果是文件,则直接加入打包,如果是目录,则需要递归进行处理。本示例中没有对这种情况进行处理,对文件夹进行递归压缩的示例将在下一章“java 将指定文件夹递归的进行zip打包压缩”。

(5). 在删除文件时,一定要注意文件是否被其他进程占用或者是否有该文件上的流没有关闭,这个非常重要,因为我就是因为流没有关闭,导致文件最后无法正常删除。这种时候要避免使用匿名类,因为很可能就会忽略文件上的一些流没有关闭,导致删除文件这个操作失败。我会在后面的代码示例中重点强调出来。

(6).本示例代码只处理指定文件夹下全是文件,没有子文件夹的情况,如果你测试本代码时报了“(拒绝访问。)”的错误,请检查一下目录下面是否有子文件夹。

(7).删除文件时,由于要删除最旧最老的文件,此时就需要对文件按命名日期进行排序,而且是倒序:因为我实现时使用的List,删除最后一个文件的操作的时间复杂度是O(1)。

(8).隔A时间就执行1次打包操作,隔B时间就执行1次删除压缩包操作,压缩文件容量超过C字节就执行删除,这3个值根据需要自行设置成合适的值。

2. 代码示例:

package tmp.MavenTest;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

//定时将指定文件夹下的所有文件压缩
public class ZipFilesUtil {
	private static Logger log = Logger.getLogger("zipfiles");
	
	
	//将指定文件夹下的所有文件(除压缩文件除外)压缩,该文件夹下没有子文件夹,否则需递归进行处理
	//压缩文件名为日期名
	public static void zipFiles(String sourceFilePath){
		
		
		//判断是否有指定文件夹
		File sourceFile = new File(sourceFilePath);
		if(!sourceFile.exists())
		{
			//tmpFile.mkdirs();
			log.info("待压缩的文件目录:"+sourceFile+"不存在");
			return;
		}


		String zipName =  generateId();//生产压缩文件名
		File zipFile = new File(sourceFile+"\\"+zipName+".zip");
		File[] sourceFiles = sourceFile.listFiles();
		if(null == sourceFiles || sourceFiles.length<1){
			log.info("待压缩的文件目录:" + sourceFilePath + "里面不存在文件,无需压缩.");
			return;
		}
		
		
		BufferedInputStream bis = null;  
		ZipOutputStream zos = null; 
		byte[] bufs = new byte[1024*10];
		FileInputStream fis = null;
		try {
			zos = new ZipOutputStream(new FileOutputStream(zipFile));
			for(int i=0; i<sourceFiles.length; i++){
				//创建zip实体,并添加进压缩包
				String tmpFileName = sourceFiles[i].getName();
				if(tmpFileName.endsWith(".zip"))
					continue;
				ZipEntry zipEntry = new ZipEntry(tmpFileName);
				zos.putNextEntry(zipEntry);	
				//读取待压缩的文件并写进压缩包里
				fis = new FileInputStream(sourceFiles[i]);
				bis = new BufferedInputStream(fis, 1024*10);
				int read = 0;
				while((read=bis.read(bufs, 0, 1024*10))!=-1){
					zos.write(bufs, 0, read);
				}
				fis.close();//很重要,否则删除不掉!
				sourceFiles[i].delete();//将要进行压缩的源文件删除
			}//end for
			log.info("文件打包成功!");
			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally{
			//关闭流
			try {
				if(null!=bis)
					bis.close();
				if(null!=zos)
					//zos.closeEntry();
					zos.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}	
		}
		
		
	}
	

	//删除指定文件夹下的压缩文件
	public static void deleteZipFiles(String filePath) throws ParseException{
		File delFile = new File(filePath);
		if(!delFile.exists())
		{
			//tmpFile.mkdirs();
			log.info("待删除的文件目录:"+delFile+"不存在");
			return;
			
		}
		
		File[] delFiles = delFile.listFiles();
		if(null == delFiles || delFiles.length<1){
			log.info(filePath+"下没有要删除的文件.");
			return;
		}
		
		
		//收集压缩文件,过滤掉非压缩的文件以及文件夹
		List<File> delFilesTarget = new ArrayList<File>();
		for(int i=0; i<delFiles.length; i++){
			String tmpFileName = delFiles[i].getName();
			if(tmpFileName.endsWith(".zip"))//是压缩文件
				delFilesTarget.add(delFiles[i]);
		}
		
		orderByNameDate(delFilesTarget);//按文件名的日期排序(倒序)

		//计算文件大小总量,然后检查总量是否超过阈值(100KB)。
		//如果超过,则不断删除最老的文件,直至文件总量不再超过阈值
		long len = 0;
		for(int i=0; i<delFilesTarget.size(); i++){
			len += delFilesTarget.get(i).length();//返回字节数
		}
		int threshold = 100000;//100KB,阈值
		int lastIndex = delFilesTarget.size()-1;
		while(len>threshold){
			File delF = delFilesTarget.remove(lastIndex);
			len -= delF.length();
			lastIndex -= 1;
			
			if(!delF.delete()){
				log.info("文件"+delF.getName()+"删除失败!");
			}else{
				log.info("文件"+delF.getName()+"删除成功!");
			}
			
		}

	}
	
	//以日期生成文件名
	public static String generateId(){
		SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");//设置日期格式
		String dateStr = df.format(new Date());//new Date()为获取当前系统时间
		return dateStr;
	}
	
	//按文件名的日期排序(倒序)
	public static void orderByNameDate(List<File> files){
		final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
		
		Collections.sort(files, new Comparator<File>() {
			public int compare(File f1, File f2) {
				// TODO Auto-generated method stub
				try {
					Date f1Date = sdf.parse(f1.getName());
					Date f2Date = sdf.parse(f2.getName());
					//return f1Date.compareTo(f2Date);//正序
					return f2Date.compareTo(f1Date);//逆序
				} catch (ParseException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
				return 0;
			}
			
		});
	}
	
}



package tmp.MavenTest;


import java.text.ParseException;
import it.sauronsoftware.cron4j.Scheduler;

public class Test 
{
	private static Scheduler zipScheduler = null;
	private static Scheduler delScheduler = null;

    public static void main( String[] args )
    {
    	System.out.println( "Main thread start..." );
    	
    	
		String filePath = "D:\\Eclipse_XJ\\work_space\\WebProbe\\downloadfiles";
		startZipAndDelFilesJop(filePath);//启动定期打包任务和定期删除任务    	
    	
    	
    	try{
    		Thread.sleep(60*60*1000);//调度持续1小时,即mainthread 会在这里阻塞(睡眠)1小时
    	} catch(InterruptedException e){
    		e.printStackTrace();
    	}
    	//zipScheduler.stop();//如果不设置成守护进程,就需要手动停止
    	//delScheduler.stop();
    	
    	
    	System.out.println( "Main thread end..." );
    	//此时守护进程也会随之退出
    }
    
    //启动定期打包任务和定期删除任务
    public static void startZipAndDelFilesJop(final String filePath){
    	zipScheduler = new Scheduler();
    	//每2分钟执行1次打包job
    	zipScheduler.schedule("*/2 * * * *", new Runnable() {//前面五个*号按顺序分别代表:分钟、小时、日、月、星期
			public void run() {
				// TODO Auto-generated method stub
				System.out.println( "每2分钟执行1次打包job" );
				ZipFilesUtil.zipFiles(filePath);
			}
		});
    	zipScheduler.setDaemon(true);//设置为守护进程
    	zipScheduler.start();
    	
    	delScheduler = new Scheduler();
    	//每3分钟执行1次删除job
    	delScheduler.schedule("*/3 * * * *", new Runnable() {//前面五个*号按顺序分别代表:分钟、小时、日、月、星期
			public void run() {
				// TODO Auto-generated method stub
				System.out.println( "每3分钟执行1次删除job" );
				try {
					ZipFilesUtil.deleteZipFiles(filePath);
				} catch (ParseException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		});
    	delScheduler.setDaemon(true);//设置为守护进程
    	delScheduler.start();
    }
}