65.9K
CodeProject 正在变化。 阅读更多。
Home

文件清理器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.76/5 (6投票s)

2011年11月15日

CPL

6分钟阅读

viewsIcon

22972

downloadIcon

959

文件清理器 - 压缩和删除旧文件

引言

文件清理工具”旨在压缩和/或删除早于X天/月/年的文件。它被开发用于在系统或服务器上腾出额外的空间,因为这些系统或服务器会生成大量的日志/文本文件。

此工具已在Windows XP/Vista/7、Windows Server 2000/2003、Windows Developer Preview (Windows 8)、Ubuntu 11、Fedora 14、UNIX - Sun Solaris、Mac OS X Lion上成功测试。

背景

我的一个朋友正在和我讨论生产批处理组件服务器以及批处理作业在一段时间内占用的磁盘空间。

例如,“服务器A”是一个批处理组件服务器,配置了大约100多个批处理作业。大多数作业被配置为每天运行,并每天消耗大约3 GB的磁盘空间,其中包括输入文件、输出文件、各种日志文件等。根据这个事实,如果服务器分配了500 GB的SAN,磁盘将在大约5个月内满。

问题

现在,在实际生产环境中,每5个月增加500 GB的SAN是不可取的。此外,大多数批处理组件确实会生成日志文件,但不会进行清理,这在实际生产环境中是好的,有助于调查问题。

解决方案

根据上述情况,业务需要某种文件清理工具。大多数时候,相应的“服务器支持”和/或“应用程序支持”团队会编写特定于操作系统的微型脚本来实现此目的,我的朋友也已在他的生产环境中这样做。

现在假设我们有20台服务器,其中10台是Windows,4台是Linux,5台是UNIX,1台是OS X。这样,至少会有3个不同版本的特定于操作系统的微型脚本。作为一名技术人员,我不会喜欢有3个不同版本的微型脚本,所以我使用JAVA开发了这个小工具。

由于大多数操作系统都预装了基本版本的JAVA运行时,我选择使用Core JAVA开发此工具。

使用工具

该工具配置和使用非常简单。我们需要使用JAVA运行时执行JAR文件,并将“.properties”文件作为参数传递。“.properties”文件包含该工具使用的配置或参数。一个示例“.properties”文件如下所示。

sample.properties

# +--------------------------------+
# | FileCleanerLauncher properties |
# +--------------------------------+
# if zip_files is not provided, true will be used
zip_files = true
# if delete_files is not provided, true will be used
delete_files = true
# +-----------------------+
# | FileFilter properties |
# +-----------------------+
directory = D:\\
file_expression = *.txt
# Expected values [days, months, years]
keep_last_type = days
keep_last = 1
# +-----------------------+
# | FileZipper properties |
# +-----------------------+
# if zip_directory is not provided, directory of FileFilter will be used
#zip_directory =
zip_file_format = a-%tF.zip
# if buffer_size is not provided, default value 1024 will be used
#buffer_size = 2048 

JAR文件执行

java -jar FileCleaner.jar sample.properties 

代码

这个工具是一个非常基本的JAVA控制台应用程序。代码是4个核心类和2个辅助类的集合,每个类都将在下面描述。

PropertiesConfiguration 类

此类有助于从“.properties”文件中读取设置。“.properties”文件可以是默认的“filecleaner.properties”或作为命令行参数传递的自定义文件,如“JAR文件执行:”下所示

private static String DEFAULT_PROPERTIES_FILENAME = "filecleaner.properties";
private static PropertiesConfiguration instance = null;

public static void init(String propertiesFile) {
    if (null == instance) {
        try {
            instance = new PropertiesConfiguration(propertiesFile);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

public static void init() {
    init(DEFAULT_PROPERTIES_FILENAME);
}  

辅助类

我当时缺少一个好的.NET中的“String”函数,叫做“IsNullOrEmpty”,所以我只是在这个类中创建了一个辅助函数。

public final class Helper {

   public static Boolean hasString(String string) {
       return !((null == string) || string.isEmpty());
   }
} 

FileFilter 类

这个类是这个工具的第一步。它负责过滤并生成要压缩和/或删除的文件列表。它有四个设置需要从“.properties”文件中读取。它有两个方法,根据参数过滤文件并返回一个String数组的列表。

  • directory:查找文件的有效目录或文件夹路径。路径语法根据“操作系统”而异
  • file_expression:用于过滤文件的有效JAVA字符串表达式。这也可以包含通配符,如“?”和“*”。
  • keep_last_type:值可以是DAYS/MONTHS/YEARS,表示从系统日期中扣除的类型。如果值为DAYS,则扣除天数。如果值为MONTHS,则扣除月数。如果值为YEARS,则扣除年数。
  • keep_last:整数值,表示要过滤的早于“X”天/月/年的文件。在过滤文件时,此值根据“keep_last_type”中指定的值从当前系统日期中扣除。
    public FileFilter() throws Exception {
    
        String value = null;
    
        value = PropertiesConfiguration.getPropertyValue("directory");
        if (!Helper.hasString(value)) {
            throw new Exception("Invalid property value: directory");
        }
        this.directory = value;
    
        value = PropertiesConfiguration.getPropertyValue("file_expression");
        if (!Helper.hasString(value)) {
            throw new Exception("Invalid property value: file_expression");
        }
        this.fileExpression = value;
    
        value = PropertiesConfiguration.getPropertyValue("keep_last_type");
        if (!Helper.hasString(value)) {
            throw new Exception("Invalid property value: keep_last_type");
        }
    
        value = value.toUpperCase();
        if ("DAYS".equals(value)) {
            this.keepLastType = Calendar.DATE;
        } else if ("MONTHS".equals(value)) {
            this.keepLastType = Calendar.MONTH;
        } else if ("YEARS".equals(value)) {
            this.keepLastType = Calendar.YEAR;
        } else {
            throw new Exception("Invalid property value: keep_last_type");
        }
    
        value = PropertiesConfiguration.getPropertyValue("keep_last");
        this.keepLast = Integer.parseInt(value);
    } 
  • getKeepDate - 方法:返回应与系统日期进行比较的Date
    protected Date getKeepDate() {
    
        Calendar calendar = Calendar.getInstance();
        calendar.add(this.keepLastType, (this.keepLast * -1));
    
        return calendar.getTime();
    } 
  • getFileNames - 方法:以String数组形式返回过滤后的文件列表。返回的数组可以传递给另外两个步骤:压缩删除
    public String[] getFileNames() throws FileNotFoundException {
    
        String matchExpression = fileExpression.replace("?", ".?").replace("*", ".*");
        Date keepDate = this.getKeepDate();
    
        File dir = new File(this.directory);
        if (!dir.isDirectory() || !dir.exists()) {
            throw new FileNotFoundException(this.directory);
        }
    
        ArrayList<string> filteredFileNames = new ArrayList<string>();
        String[] fileNames = dir.list();
    
        for (String fileName : fileNames) {
            if (!fileName.matches(matchExpression)) {
                continue;
            }
    
            File file = new File(dir, fileName);
            if (!file.isFile() || !file.exists()) {
                continue;
            }
    
            Date lastModified = new Date(file.lastModified());
            if (lastModified.after(keepDate)) {
                continue;
            }
    
            filteredFileNames.add(file.toString());
        }
    
        return filteredFileNames.toArray(new String[filteredFileNames.size()]);
    } 

FileZipper 类

这个类是这个工具的第二步。它负责将提供的文件列表压缩为ZIP/tar.gz文件。它有三个设置需要从“.properties”文件中读取。它有两个方法用于压缩和创建ZIP/tar.gz文件。

  • zip_directory:创建压缩文件的有效目录或文件夹路径。如果未定义或提供此值,它将使用“FileFilter”类的“directory”设置。路径语法根据“操作系统”而异
  • zip_file_format:有效的压缩文件名称格式。有关有效格式的更多详细信息,请参阅“java.util.Formatter 类”。
  • buffer_size:整数值,表示用于读/写文件的缓冲区大小(以字节为单位)。如果未提供,将使用默认值1024字节。通过为大文件设置更大的缓冲区大小,可以提高性能。
    public FileZipper() throws Exception {
    
        String value = null;
    
        value = PropertiesConfiguration.getPropertyValue("zip_directory");
        if (!Helper.hasString(value)) {
            value = PropertiesConfiguration.getPropertyValue("directory");
            if (!Helper.hasString(value)) {
                throw new Exception("Invalid property value: zip_directory");
            }
        }
        this.directory = value;
    
        value = PropertiesConfiguration.getPropertyValue("zip_file_format");
        if (!Helper.hasString(value)) {
            throw new Exception("Invalid property value: zip_file_format");
        }
        this.zipFileFormat = value;
    
        value = PropertiesConfiguration.getPropertyValue("buffer_size");
        if (!Helper.hasString(value)) {
            value = "1024";
        }
        this.bufferSize = Integer.parseInt(value);
    } 
  • getZipFileName - 方法:以string形式返回ZIP/tar.gz文件路径。
    protected String getZipFileName() {
    
        Formatter formatter = new Formatter();
        formatter.format(zipFileFormat, Calendar.getInstance());
    
        return this.directory.concat(formatter.toString());
    } 
  • zipFiles - 方法:以String数组形式压缩提供的文件,并以string形式返回新创建的压缩文件路径。它使用“BEST_COMPRESSION”作为压缩级别,可以将普通文本文件压缩约95%。
    public String zipFiles(String[] fileNames) throws IOException {
    
        if (null == fileNames || 0 == fileNames.length) {
            return null;
        }
    
        String zipFileName = this.getZipFileName();
    
        FileOutputStream fos = new FileOutputStream(zipFileName);
        ZipOutputStream zos = new ZipOutputStream(fos);
        zos.setLevel(Deflater.BEST_COMPRESSION);
    
        int bytesRead;
        byte[] buffer = new byte[this.bufferSize];
        CRC32 crc = new CRC32();
    
        for (String fileName : fileNames) {
            File file = new File(fileName);
            if (!file.isFile() || !file.exists()) {
                continue;
            }
    
            BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
            crc.reset();
    
            while ((bytesRead = bis.read(buffer)) != -1) {
                crc.update(buffer, 0, bytesRead);
            }
            bis.close();
    
            bis = new BufferedInputStream(new FileInputStream(file));
    
            ZipEntry entry = new ZipEntry(file.getName());
            entry.setCrc(crc.getValue());
            zos.putNextEntry(entry);
    
            while ((bytesRead = bis.read(buffer)) != -1) {
                zos.write(buffer, 0, bytesRead);
            }
    
            bis.close();
        }
    
        zos.close();
    
        return zipFileName;
    } 

FileDeleter 类

这个类是此工具的第三步。这是一个非常简单的类,它只删除作为String数组提供的文件。

public void deleteFileNames(String[] fileNames) {

    for (String fileName : fileNames) {
        File file = new File(fileName);
        if (!file.isFile() || !file.exists()) {
            continue;
        }

        file.delete();
    }
} 

FileCleanerLauncher 类

这个类是主类。它也有两个设置需要从“.properties”文件中读取,并有四个方法。

public static void main(String[] args) {

    if (args.length > 0) {
        PropertiesConfiguration.init(args[0]);
    } else {
        PropertiesConfiguration.init();
    }

    FileCleanerLauncher launcher = new FileCleanerLauncher();
    launcher.run();
    launcher = null;
} 
  • zip_files:如果文件需要压缩,则为“true”,否则为“false”。如果未提供,默认值为“false”。如果设置为“true”,文件将使用“FileZipper”类进行压缩。如果设置为“false”,文件将不会被压缩。
  • delete_files:如果文件需要删除,则为“true”,否则为“false”。如果未提供,默认值为“false”。如果设置为“true”,文件将使用“FileDelete”类进行删除。如果设置为“false”,文件将不会被删除。
    注意:如果两者都设置为“false”,则什么也不会做。在其他情况下,如果“zip_files”设置为“false”而“delete_files”设置为“true”,则文件将被删除,且无法恢复。如果“zip_files”设置为“true”而“delete_files”设置为“false”,则文件将被压缩但不会被删除,这并不能释放磁盘空间。
    public FileCleanerLauncher() {
    
        String value = null;
    
        value = PropertiesConfiguration.getPropertyValue("zip_files");
        if (!Helper.hasString(value)) {
            value = "true";
        }
        this.zipFiles = Boolean.parseBoolean(value);
    
        value = PropertiesConfiguration.getPropertyValue("delete_files");
        if (!Helper.hasString(value)) {
            value = "true";
        }
        this.deleteFiles = Boolean.parseBoolean(value);
    } 
  • run - 方法:它只调用定义为每个步骤的三个不同方法。
    public void run() {
    
        try {
            String[] fileNames = this.getFileNames();
            this.zipFiles(fileNames);
            this.deleteFiles(fileNames);
        } catch (Exception e) {
            e.printStackTrace();
        }
    } 
  • getFileNames - 方法:借助“FileFilter”类,它获取过滤后的文件列表作为String数组。
    protected String[] getFileNames() throws Exception {
    
        String[] fileNames = null;
    
        FileFilter filter = new FileFilter();
        fileNames = filter.getFileNames();
        filter = null;
    
        return fileNames;
    } 
  • zipFiles - 方法:借助“FileZipper”类,它压缩提供的文件列表并返回新创建的压缩文件的文件路径。如果“zip_files”设置为“false”,则跳过此步骤。
    protected String zipFiles(String[] fileNames) throws Exception {
    
        if (!this.zipFiles) {
            return null;
        }
    
        String zipFileName = null;
    
        FileZipper zipper = new FileZipper();
        zipFileName = zipper.zipFiles(fileNames);
        zipper = null;
    
        return zipFileName;
    } 
  • deleteFiles - 方法:借助“FileDelete”类,它删除提供的文件列表。如果“delete_files”设置为“false”,则跳过此步骤。
    protected void deleteFiles(String[] fileNames) {
    
        if (!this.deleteFiles) {
            return;
        }
    
        FileDeleter deleter = new FileDeleter();
        deleter.deleteFileNames(fileNames);
        deleter = null;
    } 

结论

我不是JAVA领域的专家,但了解JAVA中的ZIP文件概念是一个非常好的体验。此外,在多个平台上进行测试是一个挑战,通过“Virtual Box”的帮助实现了这一点。

历史

  • 2011年11月15日:初始发布
© . All rights reserved.