仿Everything的文件搜索工具

发布于:2022-08-08 ⋅ 阅读:(389) ⋅ 点赞:(0)

仿Everything的文件搜索工具

项目介绍

这是一个仿照Everything的文件搜索工具,支持跨平台使用.

项目背景

项目技术

java8 + javaFX + 多线程 + IO流 + SQLite(嵌入式数据库)

项目功能

通过java实现一个仿照Everything的文件搜索工具,当用户点击文件夹的时候能够进行文件扫描,并在桌面上展示文件名,文件路径,文件文件大小,文件类型,最后修改日期等功能.

项目实现

创建一个Maven项目

Maven是什么

Maven是一个项目管理的综合工具,maven,意为知识的积累,起初是为了简化项目的构建.
我们想拥有一种能够清楚的了解项目的结构及功能,maven为我们提供了这个功能,它能够使开发人员更加轻松,能够使用已经积累的东西,能够在不同项目之间共享jar包
我们下面使用的PinYin工具类,就是使用别人构建好的内容,可以使我们的开发更加简单.

工具类及数据库功能

导包

一个项目就类似于搭积木的过程,一般都是从工具类或者数据库的操作开始的.

  • 拼音工具
  • SQLite
    由于我们只想做一个搜索工具,所以不能太大,就借助于SQLite这种嵌入式数据库.

由于要使工具类支持拼音输入和模糊匹配,所以借助拼音工具类,将文件名的拼音,文件名的每个字符首字母存入收据库.

创建数据库的连接

SQLite数据库的配置和MySQL一样,按照JDBC的格式来就可以.

/**
 * SQLite数据库的工具类,创建数据源,创建数据库连接
 * 只向外部提供数据库连接即可,不提供数据源(封装在工具类的内部)
 */
public class DBUtil {
    // 数据源这个对象是全局唯一的,所以创建时,应该是线程安全的
    // 这里我们使用懒汉单例模式
    private static volatile DataSource DATASOURCE;

    private static DataSource getDataSource() {
        if (DATASOURCE == null) {
            synchronized (DBUtil.class) {
                if (DATASOURCE == null) {
                    // SQLite没有账户密码,所以只需要配置数据格式
                    SQLiteConfig config = new SQLiteConfig();
                    config.setDateStringFormat(Util.DATE_PATTERN);
                    DATASOURCE = new SQLiteDataSource(config);
                    ((SQLiteDataSource) DATASOURCE).setUrl(getUrl());

                }
            }
        }
        return DATASOURCE;
    }

    /**
     * SQLite没有服务器端,仅仅是一个文件,所以配置的时候仅仅需要指定它文件的路径.
     *
     * @return
     */
    public static String getUrl() {
        String path = "E:\\byte_java\\project\\Everything\\target";
        String url = "jdbc:sqlite://" + path + File.separator + "Everything.db";
        System.out.println("获取的数据源为: " + url);
        return url;
    }

    /**
     * 获取连接
     *
     * @return
     */
    public static Connection getConnection() throws SQLException {
        return getDataSource().getConnection();
    }
}

创建数据表

界面初始化的时候创建数据表

public class DBInit {

    /**
     * 在界面初始化的时候,创建数据表
     */
    public static List<String> readSQL() {
        List<String> ret = new ArrayList<>();
        try {
            InputStream in = DBInit.class.getClassLoader()
                    .getResourceAsStream("init.sql");
            // 使用输入流读取SQL语句
            Scanner scanner = new Scanner(in);
            // 自定义换行符
            scanner.useDelimiter(";");
            while (scanner.hasNext()) {
                // scanner.next
                String str = scanner.next();
                if ("".equals(str) || "\n".equals(str)) {
                    continue;
                }
                if (str.contains("--")) {
                    str = str.replaceAll("--","");
                }
                ret.add(str);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        System.out.println("读取到的SQL为 : ");
        System.out.println(ret);
        return ret;
    }

    /**
     * 在界面初始化的时候,初始化数据库
     */
    public static void init() {
        Connection connection = null;
        Statement statement = null;
        try {
            connection = DBUtil.getConnection();
            List<String> sqls = readSQL();
            statement = connection.createStatement();
            for (String sql : sqls) {
                System.out.println("执行的SQL为: " + sql);
                statement.executeUpdate(sql);
            }
        } catch (SQLException e) {
            System.out.println("初始化数据库失败");
            e.printStackTrace();
        } finally {
            close(connection, statement);
        }
    }

    private static void close(Connection connection, Statement statement) {

    }

    public static void main(String[] args) {
        init();
    }
}

至此,工具类的的功能基本已经完成,后续再进行优化

前端页面(javaFX)

创建窗口界面

这里使用了javaFX技术.

javaFx是一个强大的图形和多媒体处理工具的集合,使用javaFX可以很轻松的设计创建桌面应用程序,并且和java一样具有跨平台性.

javaFX8中支持代码与界面布局等分离的技术,在app.xml中编写图形界面布局和组件相关的功能,然后在主类中使用FXMLLoader加载器引入布局文件.

  1. 设置应用程序主类,定义一些界面的参数
public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception{
        // 使用FXMLLoader加载器,加载名为 app.xml 的文件
        Parent root = FXMLLoader.load(getClass().getClassLoader().getResource("app.fxml"));
        // 为图形界面创建一个标题
        primaryStage.setTitle("Everything");
        // 创建一个场景
        primaryStage.setScene(new Scene(root, 1000, 600));
        // 图形界面窗口设置为可见
        primaryStage.show();
    }
    public static void main(String[] args) {
        // 通过Application抽象类的launch()方法启动程序
        launch(args);
    }
}
  1. 布局和样式

使用GradPane布局

<?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>

运行截图

在这里插入图片描述

后端交互

将扫描到的文件信息展示到界面中

public class Controller implements Initializable {

    @FXML
    private GridPane rootPane;

    @FXML
    private TextField searchField;

    @FXML
    private TableView<FileMeta> fileTable;

    @FXML
    private Label srcDirectory;

    private List<FileMeta> fileMetas;

    // 界面初始化的时候,运行的程序,当界面刷新时,执行事件
    public void initialize(URL location, ResourceBundle resources) {
        // 界面创建的时候,初始化数据库
        DBInit.init();
        // 添加搜索框监听器,内容改变时执行监听事件
        searchField.textProperty().addListener(new ChangeListener<String>() {
    
            public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
                freshTable();
            }
        });
    }

    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();
        System.out.println("开始进行文件扫描,根路径为: " + path);
        long start = System.nanoTime();
        fileScanner.traversal(file);
        long end = System.nanoTime();
        System.out.println("扫描到的文件夹个数: " + fileScanner.getDirNum());
        System.out.println("扫描到的文件个数: " + fileScanner.getFileNum());
        System.out.println("共耗时: " + (end - start) / 1000000 + "ms");
        fileMetas = fileScanner.getFileMetas();
        // 刷新界面,展示刚才展示的信息
        freshTable();
    }

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

保存文件信息

@Data
@NoArgsConstructor
@ToString
@EqualsAndHashCode
public class FileMeta {
    private String name;
    private String path;
    private Boolean isDirectory;
    private Long size;
    private Date lastModified;

    private String pinYin;
    // 拼音首字母
    private String pinYinFirst;

    // 以下属性需要在界面展示,将当前属性值处理之后展示
    // 这些属性值要与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, Data lastModified) {
        this.name = name;
        this.path = path;
        this.isDirectory = isDirectory;
        this.size = size;
        this.lastModified = (Date) lastModified;
    }
}

扫描文件信息

@Getter
public class FileScanner {
    // 文件夹的个数
    private int dirNum = 1;
    // 文件的个数
    private int fileNum;
    // 保存所有的信息
    List<FileMeta> fileMetas = new ArrayList<>();

    /**
     * @param filePath 根据传入的文件夹路径进行扫描
     */
    public void traversal(File filePath) {
        if (filePath == null) {
            return;
        }
        // 获取当前对象的所有内容
        File[] files = filePath.listFiles();
        // 遍历这个集合,就能得到所有的文件
        for (File file : files) {
            FileMeta meta = new FileMeta();
            if (file.isFile()) {
                // 设置属性
                setCommonFile(file.getName(), file.getPath(), false, file.lastModified(), meta);
                meta.setSize(file.length());
                fileNum++;
            } else {
                // 是目录
                setCommonFile(file.getName(), file.getPath(), true, file.lastModified(), meta);
                dirNum++;
                traversal(file);
            }
            fileMetas.add(meta);
        }
    }

    public void setCommonFile(String name, String path, boolean isDirectory, long lastModified, FileMeta fileMeta) {
        fileMeta.setName(name);
        fileMeta.setPath(path);
        fileMeta.setIsDirectory(isDirectory);
        fileMeta.setLastModified(new Date(lastModified));
    }
}

效果展示

在这里插入图片描述
单线程下扫描文件夹并把文件信息保存到终端功能基本完成.


网站公告

今日签到

点亮在社区的每一天
去签到