阶段性检测实战项目----文件搜索引擎

项目:

仿照 everthing 设计文件搜索引擎,相对于 everthing 支持跨平台的使用,这是因为Java是跨平台的。

原因:

功能简单,主要是阶段性验收学习成果,以及练习多线程编程

使用的环境和工具:

🐟 选用数据库:SQLite  (主要是小(几MB),轻量化,嵌入式的数据库,文件搜索系统在实现过程中只需要调用一张表)

🐟 选择环境语言:Java8 + JavaFX 

🐟 项目管理工具:maven,方便进行第三方 jar 包的导入和管理,方便对当前项目的整个生命周期的掌握。

🐟 插件:lombok

实现功能:

🐳 选择文件夹多线程扫描该文件夹下的子文件,展示文件的名称、路径、大小和修改时间。

🐳 选择路径后,支持搜索相关文件内容,支持文件部分名、全拼或者是首字字母,支持模糊查询。

🐳 文件夹扫描完毕之后,显示搜索的所有文件以及文件夹个数,以及查询时间

分隔符🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀分隔符

分隔符🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀分隔符

准备阶段:

                💧 依赖添加

                💧 数据库创建和连接

                💧 前端页面设计

 

第一天

💧 依赖添加

🙋 🙋 问题一:🙋 🙋

文件名有可能是中文的,所以想要实现文件名搜索,需要把汉字字符转换成字母,但是汉字是存在多音字的,所以需要一个工具类,帮我们把中文字符转换为字母字符串的形式,这样就可以支持模糊查找。

e.g.  七七冲鸭  ➡️ qiqichongya  / qqcy 

        <!--汉语言拼音处理工具-->
        <dependency>
            <groupId>com.belerweb</groupId>
            <artifactId>pinyin4j</artifactId>
            <version>2.5.1</version>
        </dependency>

工具类的代码设计:

package util;

import net.sourceforge.pinyin4j.PinyinHelper;
import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType;
import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat;
import net.sourceforge.pinyin4j.format.exception.BadHanyuPinyinOutputFormatCombination;


/**
 * @Author qiqichongya
 * @Date 2022/8/26 18:28
 * @PackageName:util
 * @ClassName: PinyinUtil
 * @Description: 拼音工具类
 * 将及汉语拼音的字符映射到字母字符串中
 */
public class PinyinUtil {
    // 定义汉语拼的配置:全局变量,必须时全局唯一的且在定义时就要初始化
    private static final HanyuPinyinOutputFormat FORMAT;

    static {
        // 当 PinyinUtil类加载时执行静态代码块,除了可以产生对象外,还可以进行一些配置相关的工作
        FORMAT = new HanyuPinyinOutputFormat();
        // 设置转换之后的英文字母都是英文小写字母
        FORMAT.setCaseType(HanyuPinyinCaseType.LOWERCASE);
    }

    /**
     * 传入任意的文件名称,就能将该文件名称转为字母字符串全拼和首字母小写字符串
     * 若文件名是由中文和英文或数字组成,只需要转换中文即可。
     * e.g. 七七chongya666  -->  qiqichongya666  / qqchongya666
     *
     * @param fileName
     * @return
     */
    public static String[] getPinyinByFileName(String fileName) {
        // 第一个字符串用来存放文件名全拼
        // 第二个字符串用来存放首字母
        String[] ret = new String[2];
        // 全拼
        StringBuilder allNameSpells = new StringBuilder();
        // 首字母
        StringBuilder firstCaseSpells = new StringBuilder();

        for (char c : fileName.toCharArray()) {
            try {
                String[] pinYins = PinyinHelper.toHanyuPinyinStringArray(c, FORMAT);
                if (pinYins == null || pinYins.length == 0) {
                    // 说明碰到非中文的字符,保留
                    allNameSpells.append(c);
                    firstCaseSpells.append(c);
                } else {
                    // 碰到了中文字符
                    allNameSpells.append(pinYins[0]);
                    firstCaseSpells.append(pinYins[0].charAt(0));
                }
            } catch (BadHanyuPinyinOutputFormatCombination e) {
                allNameSpells.append(c);
                firstCaseSpells.append(c);
                e.printStackTrace();
            }
        }
        ret[0] = allNameSpells.toString();
        ret[1] = firstCaseSpells.toString();
        return ret;
    }
}

🙋 🙋 问题二:🙋 🙋

怎末导入SQLite数据库,以及连接操作?三步走战略:创建 + 连接 + 执行 +(关闭)

首先在maven中导入SQLite依赖:

        <!-- SQLite数据库-->
        <dependency>
            <groupId>org.xerial</groupId>
            <artifactId>sqlite-jdbc</artifactId>
            <version>3.36.0.3</version>
        </dependency>

创建SQLite数据源:

创建数据库连接
        A、拿到SQLite数据源

              a、配置数据库的欢号窟码、曰期格式等

              b、配置本和上数据库的路经和uKL

/** 对于SQLite来说,没有服务端和客户端,因此只需指定SQLite数据的地址就行了 **/

        B、拿到数据库连接(从数据源拿到数据库连接)

        C、执行 Statement 的查询或更新方法。

                executeuery() : resultset对象,存储诸返回信息

                executeupdata ():int 执行更新后的修改的行数

        D、关闭连接释放资源 (statement 或 preparedstatement)

需要注意的点:

        1. 因为配置数据源的URL是SQLite子类中独有的方法,所以要向下转型

        2. 由于SQLite不像MySql那样由定义好的日期格式,所以这里需要手动定义一下日期格式。

package util;

import org.sqlite.SQLiteConfig;
import org.sqlite.SQLiteDataSource;

import javax.sql.DataSource;
import java.io.File;
import java.sql.*;

/**
 * @Author qiqichongya
 * @Date 2022/7/17 15:44
 * @PackageName:util
 * @ClassName: DataBaseUtil
 * @Description: SQLite数据库的工具类,创建数据源,创建数据库的连接
 * 只向外部提供SQLite数据库的连接即可,数据源不提供(封装在工具类的内部)
 */
public class DataBaseUtil {
    /**
     * 数据源
     */
    private volatile static DataSource DATASOURCE;
    /**
     * 数据库连接
     */
    private volatile static Connection CONNECTION;

    /**
     * 获取数据源
     * 考虑到多线程场景下,SQLite要满足多个线程使用同一个数据库连接
     * 这里采用double-check单例模式获取数据库连接对象
     * @return
     */
    public static DataSource getDataSource() {
        if (DATASOURCE == null) {
            // 考虑到多线程场景下,只能有一个线程能进入同步代码块
            synchronized (DataBaseUtil.class) {
                if (DATASOURCE == null) {
                    // 设置SQLite
                    SQLiteConfig config = new SQLiteConfig();
                    // 设置时间格式
                    config.setDateStringFormat(Util.DATE_PATTERN);
                    // 防止其他线程回复执行后多次创建单例对象,所以在同步代码中判断 DATASOURCE 为空后再实例化。
                    DATASOURCE = new SQLiteDataSource(config);
                    // 这里是因为配置数据源的URL是SQLite子类中独有的方法,所以要向下转型
                    ((SQLiteDataSource) DATASOURCE).setUrl(getUrl());
                }
            }
        }
        return DATASOURCE;
    }


    /**
     * 获取数据库连接
     * 考虑到多线程场景下,SQLite要满足多个线程使用同一个数据库连接
     * 这里采用double-check单例模式获取数据库连接对象
     * @return
     * @throws SQLException
     */
    public static Connection getConnection() throws SQLException {
        if (CONNECTION == null) {
            // 考虑到多线程场景下,只能有一个线程能进入同步代码块
            synchronized (DataBaseUtil.class) {
                if (CONNECTION == null) {
                    // 获取数据源的数据库连接
                    CONNECTION = getDataSource().getConnection();
                }
            }
        }
        return CONNECTION;
    }


    /**
     * 配置SQLite数据库的地址
     * 对于SQLite数据库来说,没有服务端和客户端,因此只需要指定SQLite数据库的地址即可。
     *
     * @return
     */
    private static String getUrl() {
        // 大家一定记得改为自己电脑的路径!!
        String path = "D:\\ideaworkpace\\search_java\\target";
        String url = "jdbc:sqlite://" + path + File.separator + "search_java.db";
        System.out.println("获取数据库的连接为 : " + url);
        return url;
    }


    /**
     * 数据库释放资源
     * @param statement
     */
    public static void close(Statement statement) {
        if (statement != null) {
            try {
                statement.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * 数据库释放资源
     * @param preparedStatement
     * @param resultSet
     */
    public static void close(PreparedStatement preparedStatement, ResultSet resultSet) {
        close(preparedStatement);
        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public static void main(String[] args) throws SQLException {
        System.out.println(getConnection());
    }
}

SQLite日期格式类:

package util;

/**
 * @Author qiqichongya
 * @Date 2022/7/17 15:53
 * @PackageName:util
 * @ClassName: Util
 * @Description: TODO 通用的工具类
 */
public class Util {
    /**
     * SQLite没有默认的日期格式,需要手动配置一下
     */
    public static final String DATE_PATTERN = "yyyy-MM-dd HH:mm:ss";
}

🙋 🙋 问题三:🙋 🙋

简化操作,引入lombok开发包。这是现在基本每个项目必备的开发包。普通的Java类,都需要写一大堆的getter,setter,toString,hashCode,equals等等一大堆的代码,而使用这个库可以直接几个注解,注入这些代码。没有Lombok之前的代码特别冗余,加入lombok之后就会很简洁。

添加Lombok依赖:

        <!--lombok库-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
        </dependency>

分隔符🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀分隔符

分隔符🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀分隔符

第二天

💧 数据库创建和连接

🙋 🙋 问题四:🙋 🙋

当用户选择文件之后,需要展示文件的名称、路径、大小和修改时间。这就需要使用一张数据表来保存信息,需要保存信息有:

* 绿色:是要展示的信息                        * 浅红色:是创建数据表要用到的信息

                文件名:name

                文件路径:path

                文件大小:size

                最后修改时间:last_modified

                该文加是否是文件夹:is_directory   

                文件字母全拼:pinyin

                文件全拼首字母:initials

创建数据表:

 在resources路径下创建 init.sql文件。每次启动的时候重新创建 file_meta 表,

在表头 -- drop table if exists file_meta; 之后在代码中处理  “--” 注释。

-- drop table if exists file_meta;
create table if not exists file_meta(
    name varchar(50) not null,
    path varchar(100) not null,
    is_directory boolean not null,
    size bigint,
    last_modified timestamp not null,
    pinyin varchar(200),
    initials varchar(50)
);

在使用文件输入流读入 init.sql 文件资源时有,有三种方式:

🐄 InputStream inputStream = new FileInputStream("init.sql");

这种方式需要注意的是,使用的是相对路径,但是这样写是找不到资源的,因为默认init.sql这个资源必须是要和工程目录在同一级下。如图1

-------------------------------------------------------------------------------------------------------------------------

-------------------------------------------------------------------------------------------------------------------------

🐏InputStream inputStream = new FileInputStream("src/main/resources/init.sql");

相对路径写法。这样写也有弊端,那就是在打成 jar 包之后,依旧会报错找不到文件,因为 jar在 target 下,init.sql 在 src 下,它们就不在一个路径下。如图2

-------------------------------------------------------------------------------------------------------------------------

-------------------------------------------------------------------------------------------------------------------------

🐇 InputStream inputStream =    DataBaseUtil.class.getClassLoader(). getResourceAsStream("init.sql");

所有源文件打包之后会被编译到target目录下的classes文件下,所以采用类加载器的方式引入资源文件,JVM在加载类的时候用到ClassLoader类,getClassLoader()方法是获得编译后的classes目录,因为打包之后所有的resources文件都放到了该文件下。如图3

-------------------------------------------------------------------------------------------------------------------------

-------------------------------------------------------------------------------------------------------------------------

第三种方法是处理相对路径的通用写法,体现了Java的可以移植性。

 

 

 

数据库初始化代码设计:

package util;

import java.io.FileInputStream;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

/**
 * @Author qiqichongya
 * @Date 2022/7/17 15:38
 * @PackageName:util
 * @ClassName: DataBaseInit
 * @Description: 数据库初始化
 */
public class DataBaseInit {

    /**
     * 在页面初始化之前要先初始化数据库,先创建数据表
     */
    public static void init() {
        Connection connection = null;
        Statement statement = null;
        try {
            // 拿到数据库连接
            connection = DataBaseUtil.getConnection();
            // 拿到 .sql 文件中的内容
            List<String> sqls = readSQL();
            // statement预编译SQL。
            statement = connection.createStatement();
            // 此时要执行多个sql,所以采用的是Statement,而不是PrepareStatement
            for (String sql : sqls) {
                // 执行SQL操作。
                statement.executeUpdate(sql);
            }
        } catch (SQLException e) {
            System.out.println("数据库初始化失败!");
            e.printStackTrace();
        } finally {
            // 无论数据库是否初始化成功都要关闭释放资源
            DataBaseUtil.close(statement);
        }
    }


    /**
     * 从resources路径下读取 .sql 文件,并加载到程序中
     *
     * @return
     */
    public static List<String> readSQL() {
        // 创建一个 List 对象 用来存放 .sql 文件中的内容。
        List<String> list = new ArrayList<>();
        // 要获取 .sql 文件中的内容需要通过文件 IO 操作来实现
        // 绝对不能出现绝对路径
        // 采用类加载器的方式引入资源文件,JVM在加载类的时候用到ClassLoader类,
        // getClassLoader()方法是获得编译后的classes目录,因为打包之后所有的resources文件都放到了该文件下
        InputStream inputStream = DataBaseUtil.class.getClassLoader().
                getResourceAsStream("init.sql");
        // 对于输入流,采用Scanner类来处理
        Scanner scanner = new Scanner(inputStream);
        // 按照 ";" 分隔,自定义分隔符
        scanner.useDelimiter(";");
        while (scanner.hasNext()) {
            // 因为 nextLine 是默认碰到换行符自动分隔,所以在这里采用 next按照自定义的分隔符分隔。
            String str = scanner.next();
            if ("".equals(str) || "\n".equals(str)) {
                continue;
            }
            if (str.contains("--")) {
                // 用空字符串替换掉 --
                str = str.replace("--", "");
            }
            list.add(str);
        }
        return list;
    }
}

💧 前端页面设计

前端界面的设计:

在resources目录下创建一个.fxml 文件,采用JavaFX设计一个前端的简易界面,以供可视化操作。

前端可视化界面:

前端界面代码设计:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.control.cell.*?>

<GridPane fx:id="rootPane" alignment="center" hgap="10" vgap="10" xmlns:fx="http://javafx.com/fxml/1"
          xmlns="http://javafx.com/javafx/8" fx:controller="app.Controller">
    <children>

        <Button onMouseClicked="#choose" prefWidth="90" text="选择目录" GridPane.columnIndex="0" GridPane.rowIndex="0"/>
        <Label fx:id="srcDirectory">
            <GridPane.margin>
                <Insets left="100.0"/>
            </GridPane.margin>
        </Label>
        <TextField fx:id="searchField" prefWidth="900" GridPane.columnIndex="0" GridPane.rowIndex="1"/>

        <TableView fx:id="fileTable" prefHeight="1000" prefWidth="1300" GridPane.columnIndex="0" GridPane.columnSpan="2"
                   GridPane.rowIndex="2">
            <columns>
                <TableColumn fx:id="nameColumn" prefWidth="220" text="名称">
                    <cellValueFactory>
                        <PropertyValueFactory property="name"/>
                    </cellValueFactory>
                </TableColumn>
                <TableColumn prefWidth="400" text="路径">
                    <cellValueFactory>
                        <PropertyValueFactory property="path"/>
                    </cellValueFactory>
                </TableColumn>
                <TableColumn fx:id="isDirectory" prefWidth="90" text="文件类型">
                    <cellValueFactory>
                        <PropertyValueFactory property="isDirectoryText"/>
                    </cellValueFactory>
                </TableColumn>
                <TableColumn fx:id="sizeColumn" prefWidth="90" text="大小(B)">
                    <cellValueFactory>
                        <PropertyValueFactory property="sizeText"/>
                    </cellValueFactory>
                </TableColumn>
                <TableColumn fx:id="lastModifiedColumn" prefWidth="160" text="修改时间">
                    <cellValueFactory>
                        <PropertyValueFactory property="lastModifiedText"/>
                    </cellValueFactory>
                </TableColumn>
            </columns>
        </TableView>
    </children>
</GridPane>

小结:

前期的准备工作已经完成,接下来就是具体的实现功能,主要是集中在后端功能的完善和升级。

分隔符🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀分隔符

分隔符🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀分隔符

实现功能阶段:

 

第三天

问题五:

由于每次先前端页面显示文件信息,都要从数据库中拿到数据,所以为了我们需要专门设计一个类,用来和数据库打交道,最终程序中获取数据库的记录就通过该类来描述。

数据库对象类设计:

这个类就对应我们数据库中的表名,数据表中的一行记录就对应这个类的一个对象。则数据表的所有内容就对应 FileMeta 这个类的对象数组

特别注意的是:

1.在这种数据表到类的映射关系中,基本类型统一使用包装类,因为基本类型有默认值,在之后的处理中会比较麻烦。

2.因为 pinyin 和 pinYinFirst 这两个变量并不是每一个对象都需要,到但是其他的对象是每一个都会有的,所以在这里有参构造就排除了 pinyin 和 pinYinFirst。

package app;

import com.sun.xml.internal.ws.spi.db.DatabindingException;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;

import java.util.Date;

/**
 * @Author qiqichongya
 * @Date 2022/7/17 15:29
 * @PackageName:app
 * @ClassName: FileMeta
 * @Description:
 *
 *     name varchar(50) not null,
 *     path varchar(100) not null,
 *     is_directory boolean not null,
 *     size bigint,
 *     last_modified timestamp not null,
 *     pinyin varchar(200),
 *     pinyin_first varchar(50)
 */
@Data
@NoArgsConstructor
@EqualsAndHashCode
@ToString
public class FileMeta {
    private String name;
    private String path;
    private Boolean isDirectory;
    private Long size;
    private Date lastModified;

    public FileMeta(String name, String path, Boolean isDirectory, Long size, Date lastModified) {
        this.name = name;
        this.path = path;
        this.isDirectory = isDirectory;
        this.size = size;
        this.lastModified = lastModified;
    }

    private String pinyin;
    private String pinYinFirst;
}

连接控制前端的类设计:

        前端界面创建好之后,我还需要在创建一个类来控制前端的一些操作,比如选择文件的butten按钮等。这里创建一个控制类:Controller 用来点击选择目录,最终界面上就会展示选择的文件夹下内容。这里需要注意一定要和前端页面对应,所以要先创建一个 app 包,在 app 包下创建Controller。

此外,app.fxml 中的 fx:id 的名称要和 app.Controller 类中的属性名称完全一致,这样的话界面中的内容才会正确的被 Controller 类所接收。

要实现的功能:

        😸  在系统界面初始化之前,要先初始化数据库。

                        调用DataBaseInit.init();

        😸  点击选择目录,最终界面上就会展示选择的文件夹下内容。

                        需要一个FileScanner类,用来文件扫描。

                        其中文件类型、大小、修改时间需要进行特殊处理再展示。

        😸  显示当前选择的文件路径

                        把当前文件路径放入文本:this.srcDirectory.setText(path);

        😸  刷新表格数据

                        创建一个 freshTable() 方法。

package app;

import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.event.Event;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.stage.DirectoryChooser;
import javafx.stage.Window;
import task.FileScanner;
import util.DataBaseInit;

import java.io.File;
import java.net.URL;
import java.util.ResourceBundle;


public class Controller implements Initializable {

    @FXML
    private GridPane rootPane;

    @FXML
    private TextField searchField;

    @FXML
    private TableView<FileMeta> fileTable;

    @FXML
    private Label srcDirectory;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        // 在系统界面初始化之前,要先初始化数据库。
        DataBaseInit.init();
        // 添加搜索框监听器,内容改变时执行监听事件
        searchField.textProperty().addListener(new ChangeListener<String>() {
            @Override
            public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
                freshTable();
            }
        });
    }

    /**
     * 点击选择目录,最终界面上就会展示选择的文件夹下内容。
     * @param event
     */
    public void choose(Event event) {
        // 选择文件目录
        DirectoryChooser directoryChooser = new DirectoryChooser();
        Window window = rootPane.getScene().getWindow();
        File file = directoryChooser.showDialog(window);
        if (file == null) {
            return;
        }
        // 获取选择的目录路径,并显示
        String path = file.getPath();
        // 每次选择文件之后显示当前路径
        this.srcDirectory.setText(path);
        // 实例化文件扫描对象
        FileScanner fileScanner = new FileScanner();
        // 传入选择路径
        fileScanner.scan(file);
        // TODO
    }

    // 刷新表格数据
    private void freshTable() {
        ObservableList<FileMeta> metas = fileTable.getItems();
        metas.clear();
        // TODO
    }

}

文件扫描类的设计:

思路:

        😹 因为文件有文件夹其他文件之分,说首先需要创建 fileNum 和 directoryNum 来分别记录当前扫描到的文件个数 当前扫描到的文件夹个数。

        😹 再使用 List 集合来保存 所有扫描到的文件信息。

        😹 创建一个 scan 扫描方法,传入文件路径用来。

                       先判断边界条件:判断传入路径中是否为空

                       File[ ] 类型的对象调用 listFiles() 方法获取当前目录下所有file对象。

                       这里需要递归,因为文件夹下可能还有文件夹

                       遍历 File[ ] 对象,判断是否是文件夹  isDirectory()

注意:

        👹 此时最开始扫描的根路径没有统计,因此初始化文件夹的个数为1,表示从根目录下开始进行扫描任务。

        👹 同时,该类只添加了 @Getter,没有 @setter 所以程序外部没有办法修改属性。

        👹 设置最后修改时间时,lastModified 是一个长整型,需要转换为 Date 类型

package task;

import app.FileMeta;
import lombok.Getter;

import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @Author qiqichongya
 * @Date 2022/7/22 17:45
 * @PackageName:task
 * @ClassName: FileScanner
 * @Description: 文件扫描类
 */

@Getter
public class FileScanner {
    /**
     * 当前扫描到的文件个数
     */
    private int fileNum;
    /**
     * 当前扫描到的文件夹个数
     */
    private int directoryNum;

    /**
     * 保存所有扫描到的文件信息
     */
    List<FileMeta> fileMetas = new ArrayList<>();

    /**
     * 根据传入的文件夹进行扫描任务。
     *
     * @param filePath
     */
    public void scan(File filePath) {
        // 判断传入路径中是否为空
        if (filePath == null) {
            return;
        }
        // 获取当前目录下所有file对象。
        File[] files = filePath.listFiles();
        // 遍历files数组,根据files数组中的对象是否为文件夹继续递归操作。
        for (File file : files) {
            FileMeta meta = new FileMeta();
            if (file.isDirectory()) {
                // 是文件夹,就不设置大小了
                directoryNum++;
                setCommenFiled(file.getName(), file.getPath(), true, file.lastModified(), meta);
                fileMetas.add(meta);
                // 需要递归扫描
                scan(file);
            } else {
                // 文件有大小
                meta.setSize(file.length());
                setCommenFiled(file.getName(), file.getPath(), false, file.lastModified(), meta);
                fileMetas.add(meta);
                // 不是文件夹
                fileNum++;
            }
        }
    }

    /**
     * 把扫描到的文件和文件夹通用的属性添加到FileMeta的集合中
     * @param name
     * @param path
     * @param isDirectory
     * @param lastModified
     * @param meta
     */
    private void setCommenFiled(String name, String path, boolean isDirectory, long lastModified, FileMeta meta) {
        meta.setName(name);
        meta.setPath(path);
        meta.setIsDirectory(isDirectory);
        // 设置最后修改时间,但是 lastModified 是一个长整型,此处需要转换为Date类型
        meta.setLastModified(new Date(lastModified));
    }


}

做完以上类的设计之后,运行程序会发现展示框中,文件类型、大小、修改时间这三个属性并没有显示,这是因为前端的 JavaFX 页面 和后端定义的属性名不一致导致的,在这里它们需要进行特殊处理再展示。比如:

文件类型:不能展示 true / false

文件大小:要根据文件的大小来展示不同的单位,如 B,KB,MM,GB等

修改时间:需要根据年月日时分秒格式化展示。

        所以不能直接使用FileMeta中的属性值,需要做一些特殊的处理展示。首先需要改造下FileMeta类。

FileMeta类设计:

package app;

import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import util.Util;

import java.util.Date;

/**
 * @Author qiqichongya
 * @Date 2022/7/17 15:29
 * @PackageName:app
 * @ClassName: FileMeta
 * @Description: name varchar(50) not null,
 * path varchar(100) not null,
 * is_directory boolean not null,
 * size bigint,
 * last_modified timestamp not null,
 * pinyin varchar(200),
 * pinyin_first varchar(50)
 */
@Data
@NoArgsConstructor
@EqualsAndHashCode
@ToString
public class FileMeta {
    private String name;
    private String path;
    private Boolean isDirectory;
    private Long size;
    private Date lastModified;

    // 以下三个属性需要在界面中展示,将当前属性值做处理之后展示
    // 这些属性名要和app.fxml中保持一致
    // 文件类型
    private String isDirectoryText;
    // 文件大小
    private String sizeText;
    // 上次修改时间
    private String lastModifiedText;

    public void setSize(Long size) {
        this.size = size;
        this.sizeText = Util.parseSize(size);
    }

    public void setIsDirectory(Boolean directory) {
        isDirectory = directory;
        this.isDirectoryText = Util.parseFileType(directory);
    }

    public void setLastModified(Date lastModified) {
        this.lastModified = lastModified;
        this.lastModifiedText = Util.parseDate(lastModified);
    }

    public FileMeta(String name, String path, Boolean isDirectory, Long size, Date lastModified) {
        this.name = name;
        this.path = path;
        this.isDirectory = isDirectory;
        this.size = size;
        this.lastModified = lastModified;
    }

    private String pinyin;
    private String pinYinFirst;
}

接下就是构造处理 文件类型、大小、修改时间的方法。此方法在通用工具类 Util 中构造。

通用工具类设计:

package util;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @Author qiqichongya
 * @Date 2022/7/17 15:53
 * @PackageName:util
 * @ClassName: Util
 * @Description: TODO 通用的工具类
 */
public class Util {
    /**
     * SQLite没有默认的日期格式,需要手动配置一下
     */
    public static final String DATE_PATTERN = "yyyy-MM-dd HH:mm:ss";

    /**
     * 根据传入的文件大小返回不同的单位
     * 支持显示 B,KB,MM,GM
     *
     * @param size
     * @return
     */
    public static String parseSize(Long size) {
        String[] unit = {"B", "KB", "MB", "GB"};
        int flag = 0;
        while (size > 1024) {
            size /= 1024;
            flag++;
        }
        return size + unit[flag];
    }

    public static String parseFileType(Boolean directory) {
        return directory ? "文件夹" : "文件";
    }

    public static String parseDate(Date lastModified) {
        return new SimpleDateFormat(DATE_PATTERN).format(lastModified);
    }
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值