仿照Everything的文件搜索软件,但是Everything只支持windows系统,此项目由java实现,支持多平台的使用
Java8+JavaFX+多线程+IO流+SQLite数据库
扫描文件夹时,多线程扫描,效率大大提升
SQLite嵌入式数据库,小型数据库,占用资源低,小巧轻便
1.将汉字转换成对应的拼音字符串
<dependency>
<groupId>com.belerweb</groupId>
<artifactId>pinyin4j</artifactId>
<version>2.5.1</version>
</dependency>
2.SQLite数据库包
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.36.0.3</version>
</dependency>
3.lombok开发包:
用注解代替get,set等方法,避免代码冗余
@Date:注入属性的getter和setter方法
@EqualsAndHashCode:注入equals放啊和hashcode法
@AllArgsConstructor:注入全部参数的有参构造
@ToString:注入toString方法
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
4.可执行jar包:指定项目从哪一个类进入
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<!-- 指定入口类 -->
<mainClass>Main</mainClass>
<!-- 在jar的MF文件中生成classpath属性 -->
<addClasspath>true</addClasspath>
<!-- classpath前缀,即依赖jar包的路径 -->
<classpathPrefix>lib/</classpathPrefix>
</manifest>
</archive>
</configuration>
</plugin>
5.在将代码打包的同时将导入的包也一并打包进去
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.8</version>
<executions>
<execution>
<id>copy</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<!-- 指定依赖包的输出路径,需与上方的classpathPrefix保持一致 -->
<outputDirectory>${project.build.directory}/lib</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
前端界面展示的样子,配合control类使用,由前端开发人员完成
<?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>
Contoller:配合界面文件app.fxml使用,界面中的数据交给这个类来执行,特是通过这个类写回界面
FileMeta:存储每个文件的相关信息,每一个实例化对象都对应数据库中的一条数据
FileScannerCallback:回调接口
SaveToDB:回调接口的具体实现类,将扫描到的信息存储到数据库中
FileScanner:根据选择的文件夹扫描文件
FileSearch:根据搜索框中输入的数据搜索数据库
DBInit:读取init.sql,进行数据库的初始化操作
DBUtil:提供数据库的数据源,创建数据库的连接,关闭连接等操作
PinyinUtil:配置拼音格式,将汉字转换成字符串
Util:通用工具类
app.fxml:界面文件
init.sql:初始化数据库的语句资源
即init.sql中的语句
--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),
pinyin_first varchar(50)
);
前五项是需要展示在界面上的内容,而拼音首字母和拼音全拼只存储在数据库中供搜索使用,并不显示在界面上
提供其它类中会用到的通用工具
public class Util {
//数据库的日期格式
public static final String DATE_PATTERN = "yyyy-MM-dd HH:mm:ss";
//是否为文件夹以汉字显示而非ture,false或1,0
public static String parseDirectory(Boolean directory) {
return directory?"文件夹":"文件";
}
//将文件的大小以较为容易理解的方式展示
public static String parseSize(Long size) {
String[] str = {"B","KB","MB","GB"};
int index = 0;
while(size >= 1024){
index ++;
size = size / 1024;
}
return size + str[index];
}
//界面显示的日期以yyyy-MM-dd HH:mm:ss格式显示
public static String parseLastModified(Date lastModified) {
return new SimpleDateFormat(DATE_PATTERN).format(lastModified);
}
}
public class PinyinUtil {
public static HanyuPinyinOutputFormat FORMAT;
//汉字对应的编码区间
private static final String CHINESE_PATTERN = "[\\u4E00-\\u9FA5]";
//汉语拼音相关配置,对应的配置为,1.绿的拼音为lv,2.不带音调3.以小写展示
static {
FORMAT = new HanyuPinyinOutputFormat();
FORMAT.setVCharType(HanyuPinyinVCharType.WITH_V);
FORMAT.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
FORMAT.setCaseType(HanyuPinyinCaseType.LOWERCASE);
}
//用汉字对应的unicode对应的编码区间判断文件名是否包含汉字
public static boolean containsChinese(String fileName){
return fileName.matches(".*" + CHINESE_PATTERN + ".*");
}
public static String[] getPinyinByFileName(String fileName){
String[] ret = new String[2];
//分别为汉字的全拼和首字母
StringBuilder pinyin = new StringBuilder();
StringBuilder pinyinFirst = new StringBuilder();
//将当前文件名转换成字符数组
for(char c : fileName.toCharArray()){
try{
//按照上方的配置输出
String[] pinyins = PinyinHelper.toHanyuPinyinStringArray(c,FORMAT);
//如果当字符是汉字,则转换成字符的长度不会为0
//如果当前字符不是汉字,直接添加
if(pinyins == null || pinyins.length == 0){
pinyin.append(c);
pinyinFirst.append(c);
}else{
//如果当前字符是多音字,只取第一个读音,首字母选择第一个读音的第一个字母
pinyin.append(pinyins[0]);
pinyinFirst.append(pinyins[0].charAt(0));
}
//若出现异常,直接添加字符
}catch (BadHanyuPinyinOutputFormatCombination e){
pinyin.append(c);
pinyinFirst.append(c);
}
}
ret[0] = pinyin.toString();
ret[1] = pinyinFirst.toString();
return ret;
}
}
public class DBUtil {
//单例模式创建数据源,数据源全局唯一,并且对外只提供数据库的连接,不提供数据源
//单例模式创建数据库连接,多个线程共用一个连接
private volatile static DataSource DATASOURCE;
private volatile static Connection CONNECTION;
public 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);
//SQLite数据库不用账户密码,只需要配置相关的路径即可
((SQLiteDataSource)DATASOURCE).setUrl(getUrl());
}
}
}
return DATASOURCE;
}
//数据库链接的地址,设置为当前项目下的target目录下
private static String getUrl(){
String path = "F:\\66\\target";
String url = "jdbc:sqlite://" + path + File.separator + "everything.db";
System.out.println();
return url;
}
public static Connection getConnection() throws SQLException {
if (CONNECTION == null) {
synchronized (DBUtil.class) {
if (CONNECTION == null) {
CONNECTION = getDatasource().getConnection();
}
}
}
return CONNECTION;
}
//数据库中的关闭操作,这里并不关闭连接,程序执行完自动关闭
public static void close(Statement ps){
if(ps != null){
try{
ps.close();
}catch (SQLException e){
throw new RuntimeException();
}
}
}
public static void close(Statement ps, ResultSet rs){
close(ps);
if(rs != null){
try{
rs.close();;
}catch (SQLException e){
throw new RuntimeException();
}
}
}
}
//进行数据库的初始化操作
public class DBInit {
private static List<String> readSql(){
List<String> sqls = new ArrayList<>();
try{
//使用输入流从init.sql中读取相关语句
//这里使用了类加载器的方式获取文件输入流的原因 1.若使用绝对路径,换一台电脑路径就会错误2.若使用相对路径,将此项目打包执行时相对路径改变,也会出错
InputStream in = DBUtil.class.getClassLoader().getResourceAsStream("init.sql");
//输入流搭配Scanner类使用
Scanner scanner = new Scanner(in);
//读取语句时,用";"作为分隔符
scanner.useDelimiter(";");
while(scanner.hasNext()){
String ret = scanner.next();
if(ret.equals("") || ret.equals("\n")){
continue;
}else{
sqls.add(ret);
}
}
}catch (Exception e){
throw new RuntimeException();
}
return sqls;
}
//初始化数据库方法
public static void init(){
//用于获取数据库连接
Connection connection = null;
//用于执行sql语句
Statement statement = null;
try{
connection = DBUtil.getConnection();
//调用readSql函数读取sql语句返回String数组,用Statement对象循环执行
List<String> sqls = readSql();
statement = connection.createStatement();
for(String sql:sqls){
statement.executeUpdate(sql);
System.out.println("执行sql语句" + sql);
}
}catch (SQLException e){
System.out.println("数据库初始化失败");
e.printStackTrace();
}finally {
DBUtil.close(statement);
}
}
}
@Data
@EqualsAndHashCode
@NoArgsConstructor
//与数据库搭配使用的类,每一个对象对于数据库一条语句
public class FileMeta {
//name和path可直接展示在界面上,无需处理
private String name;
private String path;
//在界面上展示时isDirectory是Boolean值,而用户看到的应该是文件夹or文件,故这里的isDirectory需要经过处理才可显示
//同上size为Long,不能直接展示,而应转换为nkb,nMB,nGB,lastModified也应以yyyy-MM-dd HH:mm:ss形式展示
private Boolean isDirectory;
private Long size;
private Date lastModified;
//下面三个属性为经过转换后的isDirectory,size和lastModified,与fxml中的属性对应,是直接展示在界面上的值
private String isDirectoryText;
private String sizeText;
private String lastModifiedText;
//调用通用工具类中的方法分别将isDirectory,size和lastModified转换成对应的String类型,便于展示
public void setIsDirectory(Boolean directory){
this.isDirectory = directory;
this.isDirectoryText = Util.parseDirectory(directory);
}
public void setSize(Long size){
this.size = size;
this.sizeText = Util.parseSize(size);
}
public void setLastModified(Date lastModified){
this.lastModified = lastModified;
this.lastModifiedText = Util.parseLastModified(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;
}
}
public class FileScanner {
//由于是多线程扫描,所以这里使用对应的原子类进行操作
private AtomicInteger dirNum = new AtomicInteger();
private AtomicInteger fileNum = new AtomicInteger();
//定义一个回调接口,加载类时可传入任意子类
private FileScannerCallback callback;
//线程数量初始化为1是因为第一次执行scanInternal提交任务时已经创建了一个线程
private AtomicInteger threadCount = new AtomicInteger(1);
//获取当前的CPU个数便于确定线程池的设定
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
//创建线程池对象,核心线程数为CPU个数,最大线程个数为CPU*2,临时线程10s后自动销毁,任务阻塞队列,拒绝策略为直接拒绝
private ThreadPoolExecutor pool = new ThreadPoolExecutor(CPU_COUNT,CPU_COUNT*2,10, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(),new ThreadPoolExecutor.AbortPolicy());
//计数器的初始值为1,最后一个扫描线程结束时可直接唤醒主线程
CountDownLatch latch = new CountDownLatch(1);
//在实例化FileScanner需要定义一个回调接口的子类来确定保存文件的方式
public FileScanner(FileScannerCallback callback) {
this.callback = callback;
}
public void scan(File filePath){
if(filePath == null){
return;
}
scanInternal(filePath);
try{
//在子线程执行扫描任务时,主线程进入休眠,知道你被唤醒或打断
latch.await();
}catch (InterruptedException e){
//任务被打断
System.out.println("扫描任务中断");
}finally {
//无论任务顺利结束还是被打断,都立刻停止线程
pool.shutdownNow();
}
System.out.println("扫描到的文件夹个数为" + dirNum.get());
System.out.println("扫描到的文件个数为" + fileNum.get());
}
//使用回调函数保存数据,scanInternal扫描数据库,不同的工作交由不同方法实现
private void scanInternal(File filePath) {
//文件存在才执行扫描操作
if(filePath == null){
return;
}
//将文件夹的扫描操作交给子线程完成
pool.submit(()->{
//条用当前回调对象的回调方法执行保存操作
this.callback.callback(filePath);
File[] files = filePath.listFiles();
for(File file : files) {
if (file.isDirectory()) {
//每次遇到文件夹代表需要创建子线程执行扫描任务,线程数量加一
threadCount.incrementAndGet();
//保存文件夹和文件个数,便于调试
dirNum.incrementAndGet();
//每当遇到一个文件夹时,都将当前文件的子文件(不包含孙文件)的保存操作的提交给一个子线程
scanInternal(file);
} else {
fileNum.incrementAndGet();
}
}
//每当一个子线程执行完毕时,线程数减一并且判断是否为零,若为0代表所有扫描任务完成,唤醒主线程
threadCount.decrementAndGet();
if(threadCount.get() == 0){
latch.countDown();
}
});
}
}
public class FileSearch {
//根据路径和搜索框输入的内容从数据库中搜索相关内容
public static List<FileMeta> search(String dir,String content){
//创建数组保存数据库中搜索到的数据
List<FileMeta> metas = new ArrayList<>();
Connection connection = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
connection = DBUtil.getConnection();
//根据选中的路径搜索相关内容
String sql = "select name,path,last_modified,size,is_directory from file_meta"
+ " where (path = ? or path like ?)";
//content为搜索框中的内容,当搜索框没有内容时内没有语句,搜索框有输入时才添加相关搜索
if(content != null && content.trim().length() != 0){
sql += " and (name like ? or pinyin like ? or pinyin_first like ?)";
}
ps = connection.prepareStatement(sql);
ps.setString(1,dir);
ps.setString(2,dir + File.separator + "%");
//当搜索框有内容才添加搜索
if(content != null && content.trim().length() != 0){
ps.setString(3,"%" + content + "%");
ps.setString(4,"%" + content + "%");
ps.setString(5,"%" + content + "%");
}
rs = ps.executeQuery();
while(rs.next()){
//将数据库中搜索到的每一条语句都以FileMeta对象保存到数组中
FileMeta meta = new FileMeta();
meta.setName(rs.getString("name"));
meta.setPath(rs.getString("path"));
//从数据库中获取的时间为时间戳,需转换成Date
meta.setLastModified(new Date(rs.getTimestamp("last_modified").getTime()));
meta.setIsDirectory(rs.getBoolean("is_directory"));
//只有普通文件才需要设置大小,文件夹不需要
if(!meta.getIsDirectory()){
meta.setSize(rs.getLong("size"));
}
metas.add(meta);
}
}catch (SQLException e){
System.out.println("执行搜索语句出错");
}finally {
DBUtil.close(ps,rs);
}
return metas;
}
}
public class SavaToDB implements FileScannerCallback {
//回调函数,用于保存当前文件夹下的子文件(不包含孙文件)
@Override
public void callback(File dir) {
File[] files = dir.listFiles();
//文件存在才执行保存操作
if(files != null && files.length != 0){
//locals数组,保存的为为当前时间的文件加下存在的文件,保存在内存中
List<FileMeta> locals = new ArrayList<>();
for(File file:files){
FileMeta meta = new FileMeta();
if(file.isDirectory()){
//这里保存的路径为父路径,确定一个文件的位置为父路径+名称
setCommon(file.getName(),file.getParent(),file.lastModified(),true,meta);
}else{
setCommon(file.getName(),file.getParent(),file.lastModified(),false,meta);
//只有普通文件才设置大小
meta.setSize(file.length());
}
locals.add(meta);
}
//将数据库存储的dir目录下的文件扫描出来,存储到DBFiles数组中
//与locals文件进行对比,对数据库进行更新,这样即使文件在之后被修改了,也能保证数据库中的数据是正确的
List<FileMeta> DBFiles = query(dir);
//当locals中有而数据库没有时,对数据库进行添加操作,将对应的值保存
for(FileMeta filemeta :locals){
if(!DBFiles.contains(filemeta)){
save(filemeta);
}
}
//当数据库有而内存没有时,删除数据库相关记录
for(FileMeta filemeta:DBFiles){
if(!locals.contains(filemeta)){
delete(filemeta);
}
}
}
}
//数据库文件的删除操作
private void delete(FileMeta filemeta) {
Connection connection = null;
PreparedStatement ps = null;
try{
connection = DBUtil.getConnection();
String sql = "delete from file_meta where (name = ? and path = ?)";
//当需要删除的文件为文件夹时,其子孙文件都需要删除,才添加相应语句
if(filemeta.getIsDirectory()){
sql += "or path = ?";
sql += "or path like ?";
}
ps = connection.prepareStatement(sql);
ps.setString(1,filemeta.getName());
ps.setString(2,filemeta.getPath());
//当需要删除的文件为文件夹时,当前路径下的字孙文件都需要删除,使用like模糊查询删除子孙文件
if(filemeta.getIsDirectory()){
ps.setString(3,filemeta.getPath() + File.separator + filemeta.getName());
ps.setString(4,filemeta.getPath() + File.separator + filemeta.getName() +
File.separator + "%");
}
ps.executeUpdate();
}catch (SQLException e){
System.out.println("执行删除语句出错");
e.printStackTrace();
}finally {
DBUtil.close(ps);
}
}
//文件的保存操作
private void save(FileMeta filemeta) {
Connection connection = null;
PreparedStatement ps = null;
try{
connection = DBUtil.getConnection();
String sql = "insert into file_meta values(?,?,?,?,?,?,?)";
ps = connection.prepareStatement(sql);
ps.setString(1,filemeta.getName());
ps.setString(2,filemeta.getPath());
ps.setBoolean(3,filemeta.getIsDirectory());
if(!filemeta.getIsDirectory()){
ps.setLong(4,filemeta.getSize());
}
//setTimeStamp需要的参数为时间戳,需要进行类型转换
ps.setTimestamp(5,new Timestamp(filemeta.getLastModified().getTime()));
//只有在文件名包含汉字时才有全拼和首字母
if(PinyinUtil.containsChinese(filemeta.getName())){
//调用PinyinUtil中的方法转换全拼和首字母
String[] ret = PinyinUtil.getPinyinByFileName(filemeta.getName());
ps.setString(6,ret[0]);
ps.setString(7,ret[1]);
}
ps.executeUpdate();
}catch (SQLException e){
System.out.println("保存数据库语句出错");
}finally {
DBUtil.close(ps);
}
}
//数据库的查询操作
private List<FileMeta> query(File dir) {
Connection connection = null;
PreparedStatement ps = null;
ResultSet rs = null;
//数组用于返回查询到的从数据库内容
List<FileMeta> DBFiles = new ArrayList<>();
try{
connection = DBUtil.getConnection();
//搜索当前文件夹下所有子孙
String sql = "select name,path,is_directory,size,last_modified from file_meta"
//字符串拼接sql语句时前面加空格
+ " where path = ?";
ps = connection.prepareStatement(sql);
ps.setString(1,dir.getPath());
rs = ps.executeQuery();
while(rs.next()){
FileMeta meta = new FileMeta();
meta.setName(rs.getString("name"));
meta.setPath(rs.getString("path"));
meta.setIsDirectory(rs.getBoolean("is_directory"));
//普通文件的size属性单独赋值
if(!meta.getIsDirectory()){
meta.setSize(rs.getLong("size"));
}
//FileMeta的lastModified类型为Date,需要将long类型转为Date类型
meta.setLastModified(new Date(rs.getTimestamp("last_modified").getTime()));
DBFiles.add(meta);
}
}catch (SQLException e){
System.out.println("查询数据库操作出错");
e.printStackTrace();
}finally {
DBUtil.close(ps,rs);
}
return DBFiles;
}
//只给文件和文件夹共有的属性赋值
private void setCommon(String name, String path,Long lastModified, Boolean isDirectory,FileMeta meta) {
meta.setName(name);
meta.setPath(path);
meta.setLastModified(new Date(lastModified));
meta.setIsDirectory(isDirectory);
}
}
public class Controller implements Initializable {
@FXML
private GridPane rootPane;
//对应界面搜索框内容
@FXML
private TextField searchField;
//对应对应界面展示的文件内容
@FXML
private TableView<FileMeta> fileTable;
//对应选择路径模块
@FXML
private Label srcDirectory;
//创建一个子线程执行扫描操作,这样可以随时中断
private Thread scanThread;
//界面初始化时加载的方法
public void initialize(URL location, ResourceBundle resources) {
//在初始化界面的同时初始化数据库
DBInit.init();
// 添加搜索框监听器,内容改变时执行监听事件,每当搜索框内容有所改变,都会进行一次文件搜索并且刷新表格,例如:输入abcd会进行四次搜索
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();
srcDirectory.setText(path);
//创建文件扫描对象,传入的回调类为保存到数据库的回调类
FileScanner fileScanner = new FileScanner(new SavaToDB());
//当选择一个新的文件路径时,终端当前线程的执行,并且将任务提交给一个新的线程
if(scanThread != null){
scanThread.interrupt();
fileTable.getItems().clear();
}
//没当选择一次路径都需要进行一次搜索,刷新表格
scanThread = new Thread(()->{
fileScanner.scan(file);
freshTable();
});
scanThread.start();
}
// 刷新表格数据
private void freshTable(){
//真正展示在界面表格中的数据,每当界面刷新时,表格清空,重新获取数据
ObservableList<FileMeta> metas = fileTable.getItems();
metas.clear();
// TODO
//根据选择的目录和搜索框内容搜索数据库,执行搜索时搜索框可以为空,路径不能为空
String dir = srcDirectory.getText();
if(dir != null || dir.trim().length() != 0){
String content = searchField.getText();
//调用FileSearch.search在数据库中根据路径和搜索框内容查询文件
List<FileMeta> fileMetas = FileSearch.search(dir,content);
//将查询到的文件添加到表格
metas.addAll(fileMetas);
}
}
}
//项目的入口类
@AllArgsConstructor
public class Main extends Application {
@Override
public void start(Stage primaryStage) throws Exception{
//与app.fxml建立连接
Parent root = FXMLLoader.load(getClass().getClassLoader().getResource("app.fxml"));
//对应界面的名称
primaryStage.setTitle("search_everything");
//对应界面窗口的大小
primaryStage.setScene(new Scene(root, 1000, 800));
//展示窗口
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
初次选择某一文件路径时,先将数据保存到内存,此时数据库没有该路径的文件数据,对比内存和数据库,将数据传入数据库,此时搜索框无内容,根据路径搜索,将数据库的内容写回内存,展示在窗口上。
此时再在搜索框输入内容,则直接在数据库中进行搜索,将对应内容写回内存,展示在界面上
第二次选择相同路径时,会再次将路径下的所有文件属性写入内存,对比内存和数据库中的内容是否相同;若文件属性有所改变,根据内存修改数据库内容,再根据路径和搜索框搜索数据库写回内存