仿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加载器引入布局文件.
- 设置应用程序主类,定义一些界面的参数
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);
}
}
- 布局和样式
使用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));
}
}
效果展示
单线程下扫描文件夹并把文件信息保存到终端功能基本完成.