背景

经常使用 Typora 编辑器写笔记。有时候,粘贴一张图片之后,又觉得不合适,于是直接在编辑器中直接删除图片的引用。但是 Markdown 文件所在的目录中,对应的图片并没有被清除。

alternatives删除所有java_apache

于是思索着,不如写一个清理图片的小工具。

思路

  1. 读取 markdown 文件,利用正则表达式匹配找出所有图片引用。
  2. 列出 markdown 文件所在目录中的所有图片。
  3. 比较两者,找出冗余图片,并将其删除。

实现

为了防止误操作导致无法挽回的损失,先将被测试的笔记备份。

引入依赖

新建 Maven Project,引入依赖:

<project>
    <properties>
        <commons-io.version>2.6</commons-io.version>
        <commons-lang3.version>3.9</commons-lang3.version>
        <logback-classic.version>1.2.3</logback-classic.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>${commons-io.version}</version>
        </dependency>
        
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>${commons-lang3.version}</version>
        </dependency>
        
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>${logback-classic.version}</version>
        </dependency>
    </dependencies>
</project>

这里引入三个依赖,其中,Commons IO 用于文件操作,Commons Lang3 用于字符串操作,Logback 用于日志记录。

需要注意的是,如果你打算使用 SLF4J,并不需要在 pom.xml 文件中显式地引入其依赖,因为 logback-classic 中已经包含了相关的依赖。

参考:http://www.slf4j.org/manual.html#projectDep

日志配置文件

日志配置文件 src/main/resources/logback.xml:

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
    <!-- 定义日志文件的输出路径 -->
    <!-- <property name="USER_HOME" value="G:/log" /> -->
    
    <!-- 输出到控制台 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%msg%n</pattern>
        </encoder>
    </appender>
    
    <!-- 基于大小以及时间的轮转策略 -->
    <!-- 参考:http://www.logback.cn/04%E7%AC%AC%E5%9B%9B%E7%AB%A0Appenders.html -->
    <appender name="ROLLING" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 要写入文件的名称。如果文件不存在,则新建。 -->
        <!-- <file>${USER_HOME}/logback.log</file> -->
        <file>logback.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>%d{yyyyMMdd}/logback-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxFileSize>100KB</maxFileSize>
            <!-- 最多保留多少数量的归档文件,将会异步删除旧的文件。 -->
            <maxHistory>30</maxHistory>
            <!-- 所有归档文件总的大小。当达到这个大小后,旧的归档文件将会被异步的删除。 -->
            <totalSizeCap>1000MB</totalSizeCap>
        </rollingPolicy>

        <encoder>
            <pattern>%d{HH:mm:ss.SSS} %-5level [%-10thread] %logger - %msg%n</pattern>
        </encoder>
    </appender>
    
    <!-- 日志输出级别 -->
    <root level="info">
        <appender-ref ref="ROLLING" />
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

关于 Logback 日志配置文件的加载路径,可以参考:http://logback.qos.ch/manual/configuration.html#auto_configuration

Logback 英文参考手册:http://logback.qos.ch/manual/index.html

Logback 中文参考手册:http://www.logback.cn/

功能实现

import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class PictureClear {
    final static Logger logger = LoggerFactory.getLogger(PictureClear.class);
    
    public static void main(String[] args) {
        logger.info("冗余图片清理工具 V1.0.0");
        
        Scanner scanner = new Scanner(System.in);
        
        String path = null;
        try {
            logger.info("请输入 Markdown 文件的路径:");
            path = scanner.nextLine();
        } catch (Exception e) { // 用户可能按下 Ctrl + C 终止程序
            logger.info("程序结束!");
            scanner.close();
            System.exit(0);
        }
        
        // 预检查,判断用户输入的文件是否存在
        File file = null;
        if (StringUtils.isNotBlank(path)) {
            file = new File(path.trim());
            if (!file.exists()) {
                logger.info("Markdown 文件不存在!");
                file = null;
            }
        }
        
        // 读取 Markdown 文件的内容
        String content = null;
        if (file != null) { // 如果用户输入的文件不存在,跳过此次操作
            logger.info("您输入的路径:" + file.getAbsolutePath());
            try {
                content = FileUtils.readFileToString(file, "UTF-8");
            } catch (IOException e) {
                logger.error("文件读取异常:", e);
                content = null;
            }
        }
        
        if(content != null) {
            // 找出 Markdown 文件中所有图片的引用
            String regex = "(!\\[.*\\])(\\(.*\\))"; // 捕获组,匹配类似于 "![*](*)" 的字符串
            Pattern pattern = Pattern.compile(regex);
            Matcher matcher = pattern.matcher(content);
            HashSet<String> picturesInMarkdown = new HashSet<String>();
            logger.info("Markdown 文件中所引用的图片:");
            while (matcher.find()) {
                String ref = matcher.group(0);
                // 获取图片名称
                int beginIndex = ref.indexOf("](") + 2;
                int endIndex = ref.length() - 1;
                String picture = ref.substring(beginIndex , endIndex);
                logger.info(picture);
                // 保存图片名称
                picturesInMarkdown.add(picture);
            }
            
            // 列出 Markdown 文件所在目录中的图片名称
            File directory = file.getParentFile();
            String[] extensions = { "png", "jpg", "jpeg", "bmp" }; // 图片扩展名
            boolean recursive = false; // 不扫描子目录
            HashSet<String> picturesInDirectory = new HashSet<String>();
            logger.info("Markdown 文件所在目录中包含的图片(冗余标记 x):");
            for (File picture : FileUtils.listFiles(directory, extensions , recursive)) {
                String name = picture.getName();
                picturesInDirectory.add(name);
                if (!picturesInMarkdown.contains(name)) {
                    name = name + " x"; // 标记冗余图片
                }
                logger.info(name);
            }
            
            // 列出冗余图片,并将其删除
            picturesInDirectory.removeAll(picturesInMarkdown);
            for (String picture : picturesInDirectory) {
                logger.info("冗余图片:" + picture);
                
                logger.info("删除这张图片吗?[Y/N/QUIT]:");
                String delete = scanner.nextLine();
                
                if (StringUtils.isNotBlank(delete) && delete.trim().equalsIgnoreCase("y")) {
                    logger.info("删除图片:" + directory.getAbsolutePath() + File.separator + picture);
                    try {
                        FileUtils.forceDelete(new File(directory, picture));
                    } catch (IOException e) {
                        logger.error("文件删除异常:", e);
                    }
                } else if(delete.trim().equalsIgnoreCase("quit")) {
                    logger.info("您选择终止所有操作!");
                    break;
                }
            }
            logger.info("操作完成,程序结束!");
        }
        
        scanner.close();
        System.exit(0);
    }
}

打包项目

要将项目打包成可执行 jar,需要引入 Maven 插件。

<project>
    ...

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.2.1</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <transformers>
                                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>com.mk.PictureClear</mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

第 21 行指定应用程序的入口点。

关于 Maven 插件的用法,参考:

http://maven.apache.org/plugins/maven-shade-plugin/examples/executable-jar.html

http://maven.apache.org/plugins/index.html

执行 Maven install 之后,可以在项目的 target 目录中找到该可执行文件。

alternatives删除所有java_java_02

运行测试

使用 java -jar 命令运行该 jar 文件,效果如下:

java -jar picture-clear-1.0.0.jar
冗余图片清理工具 V1.0.0
请输入 Markdown 文件的路径:
G:\20191212\Spring Boot 开启 HTTPS\Spring Boot 开启 HTTPS.md
您输入的路径:G:\20191212\Spring Boot 开启 HTTPS\Spring Boot 开启 HTTPS.md
Markdown 文件中所引用的图片:
image-20200119163246754.png
image-20200119165306747.png
image-20200119165424156.png
image-20200119153912075.png
image-20200119170233272.png
Markdown 文件所在目录中包含的图片(冗余标记 x):
image-20200119153912075 - 副本.png x
image-20200119153912075.png
image-20200119163246754 - 副本.png x
image-20200119163246754.png
image-20200119165306747 - 副本.png x
image-20200119165306747.png
image-20200119165424156.png
image-20200119170233272.png
冗余图片:image-20200119165306747 - 副本.png
删除这张图片吗?[Y/N/QUIT]:
y
删除图片:G:\20191212\Spring Boot 开启 HTTPS\image-20200119165306747 - 副本.png
冗余图片:image-20200119163246754 - 副本.png
删除这张图片吗?[Y/N/QUIT]:
y
删除图片:G:\20191212\Spring Boot 开启 HTTPS\image-20200119163246754 - 副本.png
冗余图片:image-20200119153912075 - 副本.png
删除这张图片吗?[Y/N/QUIT]:
y
删除图片:G:\20191212\Spring Boot 开启 HTTPS\image-20200119153912075 - 副本.png
操作完成,程序结束!

相关的操作过程记录在日志文件中,该文件可在 jar 所在的目录中找到:

alternatives删除所有java_apache_03