search-everything项目

search-everything项目

    • 项目简介
      • 项目的技术:
      • 基本功能
    • 导入的依赖和插件
    • app.fxml文件:
    • 类概述
    • 数据库的初始化文件
    • 通用工具类
    • 拼音工具类PinyinUtil
    • 数据库工具类DBUtil
    • 数据库的初始化类
    • 文件元类实现
    • 文件扫描类实现
    • 文件搜索类实现
    • 文件保存到数据库类实现
    • Controller类实现
    • 项目入口类
    • 程序执行时数据流向

项目简介

仿照Everything的文件搜索软件,但是Everything只支持windows系统,此项目由java实现,支持多平台的使用

项目的技术:

Java8+JavaFX+多线程+IO流+SQLite数据库
扫描文件夹时,多线程扫描,效率大大提升
SQLite嵌入式数据库,小型数据库,占用资源低,小巧轻便

基本功能

  1. 选择一个文件夹,多线程搜索该文件夹下的子文件,展示文件的名称,路径,大小,修改时间以及是否为文件夹
  2. 选择路径后,支持搜索该路径下的相关文件内容,支持模糊搜索(首字母,全拼,文件名)

导入的依赖和插件

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>

app.fxml文件:

前端界面展示的样子,配合control类使用,由前端开发人员完成
search-everything项目_第1张图片

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

类概述

search-everything项目_第2张图片
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)
);

前五项是需要展示在界面上的内容,而拼音首字母和拼音全拼只存储在数据库中供搜索使用,并不显示在界面上
search-everything项目_第3张图片

通用工具类

提供其它类中会用到的通用工具

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);
    }
}

拼音工具类PinyinUtil

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;
    }
}

数据库工具类DBUtil

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);
    }
}

Controller类实现

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);
    }
}

程序执行时数据流向

初次选择某一文件路径时,先将数据保存到内存,此时数据库没有该路径的文件数据,对比内存和数据库,将数据传入数据库,此时搜索框无内容,根据路径搜索,将数据库的内容写回内存,展示在窗口上。
此时再在搜索框输入内容,则直接在数据库中进行搜索,将对应内容写回内存,展示在界面上

第二次选择相同路径时,会再次将路径下的所有文件属性写入内存,对比内存和数据库中的内容是否相同;若文件属性有所改变,根据内存修改数据库内容,再根据路径和搜索框搜索数据库写回内存

你可能感兴趣的:(数据库,sqlite,java)