仿照Everything实现的文件搜索工具--SearchEverything

news2024/11/23 7:47:55

 一、项目介绍

项目名称:SearchEverything

项目简介:SearchEverything是仿照Everything实现的一款桌面级的文件搜索软件,它是Everything的增强版,支持跨平台的使用。

项目功能:

        1.选择文件夹后,多线程扫描文件夹下的子文件夹,显示文件的名称、路径、文件类型(文件夹还是文件)、文件大小、上次修改时间。

        2.通过选择文件夹查看到文件信息后,支持搜索相关文件内容(支持模糊搜索)

        3.文件夹扫描完毕后,显示搜索的所有文件和文件夹个数,以及总耗时。

项目难点:

        1. 如何进行多线程的运用,在提高效率的同时保证线程安全,并且在何时关闭线程池。

        2. 使用回调函数进行文件的解耦

        3.如何知道数据库的内容是否已经过期(是否需要删除)以及 如何使用一条sql语句删除文件夹极其文件夹下的所有子文件和子文件夹。

        4.如何进行模糊搜索

项目环境:Windows,IDEA,Maven,JDK1.8

项目技术:JavaFX,多线程,IO流,SQLite数据库

        界面用的是JavaFX(图形化界面),JavaFX是继spring后Java1.8发布的一个图形化的平台)JavaFX必须在Java8下使用,超过Java11,就不再是内置的了,而是变成一个独立的项目去运营。

二、项目搭建

(一)准备工作(3+3)(pom.xml-->放在resource包下)

前置知识:

maven:项目管理工具,方便第三方jar包的导入和管理,方便对当前项目的整个生命周期(打包,测试,发布等)进行跟踪。

jar包:jar包就是一个压缩包,存放一系列编译好的class文件。

        可执行jar包 :jar包中指定入口类和主方法,通过这个主类和主方法可以将整个程序运行起来。

导入3个jar包:

        1. pinyin4j :  汉语拼音的处理工具

        2.sqlite-jdbc :SQLite数据库

        3.lombok:Lombok能通过注解的方式,在编译时自动为属性生成构造方法、getter/setter、equals、hashcode、toString等方法。

添加3个插件:

        1. maven-compiler-plugin :指示maven用什么版本的jdk编译

        2. maven-jar-plugin  : 是一个打包插件

        3. maven-dependency-plugin: 执行package命令将项目打包时也将第三方的jar包打包进来,这样在执行可执行jar时就会找到相应的第三方jar包(放在/target/lib下)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.hy</groupId>
    <artifactId>search_everything1</artifactId>
    <version>1.0-SNAPSHOT</version>

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

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


        <dependency>
<!--  Lombok能通过注解的方式,在编译时自动为属性生成构造方法、getter/setter、equals、hashcode、toString方法。-->
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.28</version>
        </dependency>
    </dependencies>

    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <!--                maven-jar-plugin是一个打包插件-->
                <!--打一个可执行jar包-->
                <!--jar包就是一个压缩包,存放一系列编译好的class文件-->
                <!--可执行jar包指的是,jar中指定入口类和主方法,通过这个主类和主方法将整个程序运行起来-->
                <artifactId>maven-jar-plugin</artifactId>
                <version>2.4</version>
                <configuration>
                    <archive>
                        <manifest>
                            <!-- 指定入口类 ,如util/Test就是util包下的Test 类就是指定的入口类-->
                            <mainClass>util/DBUtil</mainClass>
                            <!-- 在jar的MF文件中生成classpath属性 -->
                            <addClasspath>true</addClasspath>
                            <!-- classpath前缀,即依赖jar包的路径 -->
                            <classpathPrefix>lib/</classpathPrefix>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
            <plugin>
                <!--引入的第三方jar包执行package命令打包时也将第三方的jar包打包进来,这样在执行可执行jar时就会找到相应jar-->
                <!--通过maven-dependency-plugin,我们导入了pinyin4j 和 sqlite-jdbc这两个包,这两个是我们在idea中导入的。
                如果我们要把search-everything中的程序打包成一个包,我们也要把导入的第三方的那两个包打包进这个包中。这样打包之后的文件依然可以使用第三方包
                因为我们在编写程序时用到这些第三方包,如果不一起打包,在执行可执行jar包时,就会找不到相应的jar,编译报错-->
                <!-- 意思就是把自己交给别人用的同时,也要把自己用别人的东西一块拿出来-->
                <!--                所有依赖的第三方包都可以在target下的lib下找到-->
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>

                <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>
        </plugins>
    </build>

</project>

(二)项目的设计和实现

  (二 · 一)项目的指定入口类

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception{
        //FXMLLoader:从 XML 文档加载对象层次结构
        //load(...):从 FXML 文档中加载对象层次结构。
        //参数 inputStream - 包含要加载的 FXML 数据的输入流。
        //返回 加载的对象层次结构。
        //反射:getClass().getClassLoader().getResource("app.fxml")
        //Parent类:javafx.scene.Parent
        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(TheClass.class,args),其中 TheClass 是调用 launch 的方法的外层类。
         * 它必须是 Application 的子类,否则将抛出 RuntimeException。
         * 在应用程序退出(调用 Platform.exit 或关闭所有应用程序窗口)之前,launch 方法不会返回。
         */
        launch(args);
    }
}

在JavaFX中,图形化界面也是线程,此处的start方法就是加载app.fxml这个界面样式,启动界面的线程。 

(二 · 二)项目的工具类(util包)

        1.PinyinUtil--拼音的工具类

功能一:判断给定的字符串是否包含中文

功能二:将传入的文件名转换为拼音全拼和拼音的首字母拼写

public class PinyinUtil {
    //全局常量,
    // 一、在定义时就赋初始值
    private static final HanyuPinyinOutputFormat FORMAT;

    // 二、使用静态块(在类加载时,除了产生对象外,还可以进行一些配置相关的工作)
    // FORMAT 这个配置就表示将汉字字符转为拼音字符串时的一些设置
    static {
        FORMAT = new HanyuPinyinOutputFormat();
        FORMAT.setCaseType(HanyuPinyinCaseType.LOWERCASE);//拼音全转为全小写
        FORMAT.setToneType(HanyuPinyinToneType.WITHOUT_TONE);//拼音无声调
        FORMAT.setVCharType(HanyuPinyinVCharType.WITH_V);//将特殊字母转为v
    }

    //汉字的正则表达式
    //所有的中文对应的Unicode编码区间
    private static final String CHINESE_PATTERN = "[\\u4E00-\\u9FA5]";

    /**
     * 判断给定的字符串是否包含中文
     * @param str 要判断的字符串
     * @return
     */
    public static boolean containsChinese(String str){
        // 特殊符号: .*  可以匹配除了换行符以外的任何字符
        //matches:说明该字符串是否与给定的正则表达式匹配。
        return str.matches(".*" + CHINESE_PATTERN + ".*");
    }

    /**
     *    将文件名转为两个拼音字符串--1.拼音全拼字符串---2.拼音首字母的字符串
     *    核心操作: 遍历文件名中的每个字符,碰到非中文就直接保留,中文处理
     * @param fileName
     * @return
     */
    public static String[] getPinyinByFileName(String fileName){
        if (fileName == null || fileName.trim().length() == 0){
            return null;
        }
        String[] ret = new String[2];
        StringBuilder allNameAppender = new StringBuilder();
        StringBuilder firstCaseAppender = new StringBuilder();
        if (containsChinese(fileName)){
            for(int  i = 0 ; i < fileName.length(); i ++){
                char c = fileName.charAt(i);
                try {
                    // 任意一个汉语字符转为字母字符串,得到的都是一个字符串数组,因为存在多音字
                    //如 '和' --> pinyins = ['he','huo','hu'...]
                    String[] pinyins = PinyinHelper.toHanyuPinyinStringArray(c,FORMAT);
                    // 核心操作: 遍历文件名中的每个字符,碰到非中文就直接保留,中文处理
                    // 如果拼音数组为空或者拼音数组的长度为0,说明此时的字符是一个非中文的字符,直接保留即可
                    if (pinyins == null || pinyins.length == 0){
                        allNameAppender.append(c);
                        firstCaseAppender.append(c);
                    }else {
                        allNameAppender.append(pinyins[0]);//拼音数组的第一个拼音  'he'
                        firstCaseAppender.append(pinyins[0].charAt(0));//拼音数组的第一个拼音的首字母  'he'-->'h'
                    }
                } catch (BadHanyuPinyinOutputFormatCombination e) {
                    // 碰到非中文字符,直接保留
                    allNameAppender.append(c);
                    firstCaseAppender.append(c);
                }
            }
            ret[0] = allNameAppender.toString();
            ret[1] = firstCaseAppender.toString();
        }
        return ret;
    }
}

        2.DBUtil -- 数据库的工具类(创建数据源,获取数据库的连接)

        创建SQLite数据库的目的:在选择文件夹,启动文件的扫描任务后,将当前所选择的文件夹下的所有文件和子文件夹信息都保存到数据库中。在搜索框查询时,可以直接从数据库中查询,不用再次进行扫描,提高效率。

 为什么使用SQLite,而不使用MySQL呢?

        SearchEverything 是一个桌面级的软件,这个项目本来就比较小,属于工具类的项目,而MySQL就比较大了,放在这个项目中就不是很合适。

        SQLite是嵌入式数据库,一个文件就是一个数据库,没有服务端和客户端,体量较小,适合当前项目。

/**
 * Created with IntelliJ IDEA.
 * Description:
 *  SQLite数据库的工具类 ,
 * 1.创建数据源,获取数据库的连接
 * 无论是什么类型的数据库,操作的流程都是JDBC 四步走
 *
 * 2. 多线程版本
 * (1)因为sqlite 是单文件的数据库,因此在多线程场景下,必须保证多线程使用的是同一个数据库连接(单例)
 * (2)子线程在操作完毕后,不能直接关闭连接,其他线程就没法使用了,因为只有一个连接
 * 最后在程序结束的时候(进程(窗口)关闭),连接自动关闭
 */

// 只向外部提供SQLite数据库的连接即可,数据源不提供(封装在工具类的内部)(不用写构造方法)
public class DBUtil {

    private volatile static DataSource DataSource;
    private volatile static Connection CONNECTION;
    // 懒汉式单例
    // 获取数据源的方法,使用double-check单例模式获取数据对象
    private static DataSource getDataSource(){
        //当数据源为空时,才能去创建数据源
        if (DataSource == null){
            synchronized (DBUtil.class){
                // 多线程场景下,只有一个线程能进入同步代码块,
                // 这里再加个if条件句判断,是为了防止DataSource对象创建完毕后,阻塞结束,
                // 之前阻塞在这的其它线程可能会恢复执行,创建多个对象
                // 防止其他线程恢复执行后多次创建单例对象
                if (DataSource == null){
                    // SQLite没有账户密码,只需要配置日期格式即可
                    // SQLite默认的日期格式是一个时间戳,要想对它指定日期格式,需要额外配置(在一个指定的工具类Util中配置)
                    // SQLiteConfig 是SQLite数据源的一个配置
                    SQLiteConfig config = new SQLiteConfig();
                    config.setDateStringFormat(Util.DATE_FORMAT);
                    DataSource = new SQLiteDataSource(config);
                    
                    // 获取数据库的路径
                    // 配置数据源的URL 是SQLite的子类SQLiteDataSource独有的方法,因此要向下转型为SQLiteDataSource
                    // 向下转型:子类名称 子类引用 = (子类名称)父类引用;
                    ( (SQLiteDataSource) DataSource).setUrl(getUrl());
                }
            }
        }
        return DataSource;
    }

    
    // 配置SQLite数据库的地址
    // mysql的配置是 jdbc:mysql://127.0.0.1:3306/......
    // 对于SQLite数据库而言,没有服务器和客户端,因此只需要指定SQLite数据库的地址即可
    private static String getUrl(){
        // 将路径放在 target 路径下
        String path = "D:\\javaCode\\search_everything\\target";
        // File.separator:分隔符
        // 数据库的地址
        String url = "jdbc:sqlite://"+path+ File.separator + "search_everything.db";
        System.out.println("获取数据库的连接为"+url);
        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 main(String[] args) throws SQLException {
        System.out.println(getConnection());
    }

    public static void close( Statement statement) {

        if (statement != null){
            try {
                statement.close();
            }catch (SQLException e){
                throw new RuntimeException(e);
            }

        }
    }

    public static void close(PreparedStatement statement, ResultSet rs) {
        close(statement);
        if (rs!= null){
            try {
                rs.close();
            }catch (SQLException e){
                throw new RuntimeException(e);
            }
        }
    }
}
 volatile : 防止指令重排(内存屏障)
 (必须是按顺序执行代码,最后才执行return语句,
   不然有的线程在进入第二个if语句时,就已经拿到了一个没有创建完毕的DataSource对象,
   若没有volatile指令重排,线程可能会先执行return语句,由此创建多个对象)
double-check单例模式获取数据对象
第一个if语句:判断当前数据源是否为空,只有当数据源为空时,才需要进行创建数据源。
第二个if语句: 防止其他线程恢复执行后多次创建单例对象。
多线程场景下,只有一个线程能进入同步代码块,获取锁,其他线程进入阻塞队列,当第一个线程创建好数据源,释放锁时,将创建好的单例对象返回给主内存,但是对于其他处于阻塞状态的线程来说(工作内存),单例对象仍然为空,因此要多加一个if条件,判断单例对象是否为空,避免多次创建。

        3. DBInit -- 初始化数据库 的工具类

目的:在界面初始化时初始化数据库

/**
 * Created with IntelliJ IDEA.
 * Description:创建一个数据库的初始化方法
 *   在界面初始化时创建文件信息数据表
 *    1.首先要创建一个init.sql文件
 *    2.从resources路径下读取init.sql文件,加载到程序中(就是文件的IO)
 */

public class DBInit {
    /**
     * 1.读取SQL语句
     * @return
     */
    // 从resources路径下读取init.sql文件,加载到程序中,
    // 文件IO
    public static List<String> readSQL() {

        List<String> ret = new ArrayList<>();

            // 这里体现了Java的可移植性,无论是什么类型的操作系统还是电脑,一写百通!!!
            InputStream inputStream = DBInit.class.getClassLoader().
                    getResourceAsStream("init.sql");


            // 从文件获取输入流
            // 对于输入流来说,一律采用Scanner 类来处理
            Scanner scanner = new Scanner(inputStream);
            // scanner.useDelimiter自定义分隔符,即以“;”作为分隔符
            scanner.useDelimiter(";");
        
            // 经过自定义分隔符后, hasNext()方法会判断接下来是否有“;”分隔符.如果有,则返回true,否则返回false
            while (scanner.hasNext()) {
                String str = scanner.next();
                // 如果碰到换行符或者为空,不保存,直接continue
                if ("".equals(str) || "\n".equals(str)) {
                    // 跳过本次循环,进入下次循环
                    continue;
                }
                if (str.contains("--")) {
                    //把更新后的数据存放在str中
                    str = str.replaceAll("--", "");
                }
                ret.add(str);
            }
        return ret;
    }

    /**
     * 2. 在界面初始化时先初始化数据库,创建数据表 (执行SQL)
     *
     */
    public static void init(){
        Connection connection = null;
        Statement statement = null;
        // 资源的连接与释放操作
        try {
            // 首先在DBUtil类中获取数据库的连接
            connection = DBUtil.getConnection();
            // 获取要执行的SQL语句
            //调用readSQL()方法,就可以读取init.sql文件中的内容,按照分隔符进行拆分,并将其保存到结果集中
            List<String> sqls = readSQL();
            // 获取statement对象的连接
            // 1个preparedStatement对象就对应一个sql,创建preparedStatement对象就要传入一个sql
            // 这里有多个sql语句,如果使用preparedStatement不方便(在获取preparedStatement对象时就要把sql语句传进去),所以采用statement对象,对应多个sql语句
            statement = connection.createStatement();
            for (String sql :sqls) {
                System.out.println("执行sql操作" + sql);
                //executeUpdate:增删改
                statement.executeUpdate(sql);
            }
        }catch (SQLException e){
            System.err.println("数据库初始化失败");
            e.printStackTrace();

        }finally {
            // 资源的关闭
            DBUtil.close(statement);
        }
    }

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

使用InputStream 读取init.sql文件:

1.常规操作

InputStream is = new FileInputStream("init.sql");

  但这种方式在打包后是找不到init.sql文件的,因为在打包后,会默认init.sql是与src目录同级,因此程序会直接从search_everything目录下寻找init.sql文件。

2.

  InputStream is = new FileInputStream("src/main/resource/init.sql");

通过这种方式,把项目打包以后,仍然会出现问题,因为search_everything-SNAPSHOT.jar是在 target路径下,而init.sql在src目录下,target与src属于同级目录,所以打包后找到不init.sql。

3.通过类加载器引入资源文件 √

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

所有加载的类编译后都会放在target路径下的classes根目录下

        4.Util -- 通用工具类

public class Util {

    public static final String DATA_FORMAT = "yyyy-MM-dd HH:mm:ss";
}

                Util类中有很多与FileMeta类相关的方法,因此放在FileMeta类后实现。

(二 · 三)资源文件类(resource包)

        1.init.sql(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)
);

      2.app.fxml(界面样式)

<?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="core.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>

 前后端交互: 
fx:controller="core.Controller : 界面上的所有数据最终交给core包下的Controller类来处理

<Button onMouseClicked="#choose" /> 点击按钮之后会触发操作,这个操作和core/Controller类下的同名choose方法一一对应,在choose方法中会获取到刚才选择的目录路径,选择目录之后会执行一个弹出窗口的操作,确定本地文件夹的路径。

(二 · 四)回调接口(callback包) 

1、FileScannerCallBack -- 文件信息扫描的回调接口

 scanThread()方法就是在指定的文件夹中扫描所有的子文件和子文件夹---功能1

callback()方法就是将扫描的文件夹信息保存到终端(此时我们要保存到数据库)中 -- 功能2

                                                                || (共同搭配)

                                                                || 

                将指定目录下的所有文件和文件夹扫描出来之后保存到数据库中

回调函数的使用:

  1. 在FileScanner类中,接收一个回调的接口对象(属性)
  2. 接口对象什么时候传进来 ----通过构造方法传入
  3. 在进行扫描任务的时候(scanThread()--子线程负责具体的扫描任务),每次碰到一个文件夹,就调用this.callbak.callback(filepath),callback对象调用callback方法,将当前文件夹路径传进去,即将当前目录下的所有内容保存到指定终端
public interface FileScannerCallBack {
    /**
     * 文件扫描的回调接口,扫描文件时由具体的子类决定将当前目录下的文件信息持久化到哪个终端
     * 可以是数据库,可以通过网络传输
     * @param dir
     */
    void callback(File dir);
}

2. FileSaveToDB类 -- 文件信息保存到数据库的回调子类

难点1:当再次扫描一个文件夹时,其中一个文件信息发生变化了,数据库如何知道原先的文件没了(保存的修改前的文件信息需要删除,修改后的文件信息需要保存),并且对于其他已经扫描过的且保存到数据库的内容还不产生影响。

        使用FileMeta的equals方法来判断。

        视图1 -- 从OS中扫描的文件信息(保存到内存中,一定是最新数据,这里需要注意的是:保存的路径是该文件的父路径,模仿OS)

        视图2 -- 从数据库查询路径为“XXX”的所有文件信息

        对比视图1 和视图2,内存中有,数据库中没有的,需要保存;数据库中有,内存中没有的,需要删除(插入和删除的顺序没有关系)

难点2:如果数据库中需要删除的是文件夹,如何使用一条sql语句将该文件夹本身以及文件夹下的子文件和子文件夹全部删除?

        

   String sql = "delete from file_meta" +
                    "where (name = ? and path = ?)";//父路径 + 文件名 -->指定唯一的文件
            if (meta.getIsDirectory()){
                sql += " or path = ?" ;
                sql += " or path like ?";


statement.setString(1, meta.getName());//文件名
tatement.setString(2, meta.getPath());//父路径   //父路径 + 文件名 -->指定唯一的文件
statement.setString(3, meta.getPath() + File.separator + meta.getName());//文件夹下的一级目录
statement.setString(4, meta.getName() +File.separator + meta.getName() + File.separator + "%"); //文件夹下的多级目录
/**
 * Created with IntelliJ IDEA.
 * Description:  文件信息保存到数据库的回调子类
 */

public class FileSaveToDB implements FileScannerCallBack {
    @Override
    public void callback(File dir) {
        // 0.边界处理
         File[] files = dir.listFiles();
        if (files != null && files.length != 0) {

            // 1. 先将当前dir下的所有文件信息保存到内存中,缓存中的信息一定是从OS中读取到的最新数据--视图1
            List<FileMeta> locals = new ArrayList<>();
            // 2.从数据库中查询出当前路径下的所有文件信息--视图2
            List<FileMeta> dbFiles = query(dir);


            for (File file :files) {
                FileMeta meta = new FileMeta();
                if (file.isDirectory()){
                    //file.getPath():表示file这个文件的父路径 + file的name,也就是说file文件的全路径
                    //file.getParent():表示file文件的父路径
                    setCommonField(file.getName(), file.getParent(), true, file.lastModified(),meta );
                }else {
                    setCommonField(file.getName(), file.getParent(), false, file.lastModified(),meta );
                    meta.setSize(file.length());
                }
                locals.add(meta);
            }

            // 3.对比视图1 和视图2
            // 3.1 内存中有,而数据库中没有的作插入
            for (FileMeta meta :locals) {
                if (!dbFiles.contains(meta)){
                    save(meta);
                }
            }
            // 3.2 内存中没有,而数据库中有的作删除
            for (FileMeta meta :dbFiles) {
                if (!locals.contains(meta)){
                    delete(meta);
                }
            }
        }
        //else  {files == null && files.length == 0}说明该文件夹下就没有文件或者dir压根就不是文件夹
    }

    /**
     * 删除数据库中指定文件信息
     * @param meta
     */
    private void delete(FileMeta meta) {
        Connection connection = null;
        PreparedStatement statement = null;

        try {
            connection = DBUtil.getConnection();
            //删除文件(文件夹)本身
            String sql = "delete from file_meta where (name = ? and path = ?)";
            //如果待删除的是一个文件夹,该文件夹下的子文件和子文件夹也需要删除(根据path来模糊删除)
            if (meta.getIsDirectory()){
                sql += " or path = ?";//删除一级目录
                sql += " or path like ?";//删除文件夹的多级目录
            }

            statement = connection.prepareStatement(sql);

            statement.setString(1, meta.getName());
            statement.setString(2, meta.getPath());
            if (meta.getIsDirectory()){
                statement.setString(3, meta.getPath() + File.separator + meta.getName());
                statement.setString(4, meta.getPath() + File.separator + meta.getName() + File.separator +"%");//删除一级目录
            }
            int rows = statement.executeUpdate();
        } catch (SQLException e) {
            System.err.println("文件删除出错,请检查SQL语句");
            e.printStackTrace();
        }finally {
            DBUtil.close(statement);
        }

    }

    /**
     * 将指定文件信息保存到数据库中
     * @param meta
     */
    private void save(FileMeta meta) {
        Connection connection = null;
        PreparedStatement statement = null;

        try {
            connection = DBUtil.getConnection();
            String sql = "insert into file_meta values(?,?,?,?,?,?,?)";
            statement = connection.prepareStatement(sql);

            String fileName = meta.getName();
            statement.setString(1, fileName);
            statement.setString(2, meta.getPath());
            statement.setBoolean(3, meta.getIsDirectory());
            if (!meta.getIsDirectory()){
                statement.setLong(4,meta.getSize());
            }
            statement.setTimestamp(5,new Timestamp(meta.getLastModified().getTime()));
            // 只有fileName文件名中包含中文字符,才需要存入拼音
            if(PinYinUtil.containsChinese(fileName)) {
                String[] pinyins = PinYinUtil.getPinYinByFileName(fileName);
                statement.setString(6,pinyins[0]);
                statement.setString(7,pinyins[1]);
            }

            int rows = statement.executeUpdate();
        } catch (SQLException e) {
            System.err.println("保存文件信息出错,请检查SQL语句");
            e.printStackTrace();
        }finally {
            DBUtil.close(statement);
        }

    }

    private List<FileMeta> query(File dir) {

        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet rs = null;
        List<FileMeta> dbFile = new ArrayList<>();
        try{
            connection = DBUtil.getConnection();
            String sql = "select name,path,is_directory,size,last_modified from file_meta" +
                    // 切记sql拼接时,换行需要加空格
                    " where path = ?";
            statement = connection.prepareStatement(sql);
            statement.setString(1, dir.getPath());

            rs = statement.executeQuery();
            while (rs.next()){
                FileMeta meta = new FileMeta();
                meta.setName(rs.getString("name"));
                meta.setPath(rs.getString("path"));
                meta.setIsDirectory(rs.getBoolean("is_directory"));
                meta.setLastModified(new Date(rs.getTimestamp("last_modified").getTime()));

                if (!meta.getIsDirectory()){
                    // 只有是文件时才设置size大小,若是文件夹,不设置size大小
                    // 此处有个bug,数据库中文件夹的size大小为null,但是调用rs.getLong方法若返回值为null,返回0
                    meta.setSize(rs.getLong("size"));
                }
            }

        }catch (SQLException e){
            System.err.println("查询数据库指定路径下的文件出错,请检查SQL语句");
            e.printStackTrace();

        }finally {
            //这里数据库的连接为什么不能关闭
            DBUtil.close(statement,rs);
        }
        return dbFile;
    }

    // 设置公有属性
    // 设置文件名,路径,是否是一个文件夹,上次修改时间 ,以及给哪个meta对象设置属性
    private void setCommonField(String name, String path, boolean isDirectory, Long lastModified, FileMeta meta){
        meta.setName(name);
        meta.setPath(path);
        meta.setIsDirectory(isDirectory);
        //file对象的lastModified是一个以时间戳为单位的长整型,因此要先转换成Data类型
        meta.setLastModified(new Date(lastModified));
    }
}

(二 · 五)辅助核心实现类的任务工具类(task包)

1、FileScanner类(文件的扫描任务类---功能3的实现

核心方法1:根据传入的文件夹进行多线程的扫描  scan(File filePath)

                1. 将具体的扫描任务交给子线程处理
                2.  主线程等在子线程全部处理结束后再继续执行

                2.1 如果子线程任务还没有扫描完毕,就中断了当前的扫描任务,使用 shutdownNow()关闭线程池(正常执行--所有任务线程都已经执行完毕/异常--立即关闭所有正在执行的任务线程)

                3.记录所有扫描的所有文件、文件夹个数 ,以及总耗时情况(---功能3的实现)

核心方法2:子线程的扫描   scanThread(File file)

                提交线程任务:

               1. 首先要将每一个文件夹下的子文件信息都保存到回调函数中

               2. 然后判断子文件是否是文件夹,如果是文件夹,就递归调用 scanThread方法,继续创建线程,提交线程任务

               3. 如果for循环结束,也就是当前线程将这一级目录下的文件夹(创建新线程递归处理)和文件的保存扫描任务执行结束,将线程任务数-1

                4.判断线程任务数是否为0 ,若为0 ,说明扫描任务全部结束,唤醒主线程继续执行。

/**
 * Created with IntelliJ IDEA.
 * Description: 进行文件的扫描任务
 */
@Getter  //程序外部不能修改这些属性值,只能获取
public class FileScanner {
    // 当前扫描的文件个数
    private AtomicInteger fileNum = new AtomicInteger();
    // 当前扫描的文件夹个数
    // 最开始扫描的根路径没有统计,因此初始化文件夹的个数为1,表示从根目录下开始扫描任务
    private AtomicInteger dirNum = new AtomicInteger();
    // 所有扫描文件的子线程个数,只有当子线程个数为0时,主线程再继续执行
    private AtomicInteger threadCount = new AtomicInteger();

    // 当最后一个子线程执行完任务之后,再调用countDown方法唤醒主线程
    private CountDownLatch latch = new CountDownLatch(1);

    // 获取当前电脑的可用CPU个数
    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();

    private ThreadPoolExecutor pool = new ThreadPoolExecutor(CPU_COUNT,2*CPU_COUNT,10,
            TimeUnit.SECONDS,new LinkedBlockingQueue<>(),new ThreadPoolExecutor.AbortPolicy());

    // 文件扫描回调对象
    private FileScannerCallBack callBack;
    public FileScanner(FileScannerCallBack callBack) {
        this.callBack = callBack;
    }

      /**
     * 根据传入的文件夹进行扫描任务(递归)
     * @param filePath 要扫描的根目录
       * 选择要扫描的菜单之后,执行的第一个方法---主线程
     */
    public void scan(File filePath){
        System.out.println("开始文件扫描任务,根目录为 : " + filePath);
        long start = System.nanoTime();
        // 将具体的扫描任务交给子线程处理
        // 此时根目录下的扫描任务已经创建线程处理
        scanThread(filePath);
        threadCount.incrementAndGet();

        try {
            //主线程等在子线程全部处理结束后再继续执行
            latch.await();
        } catch (InterruptedException e) {
            System.err.println("扫描任务中断,根目录为 : " + filePath);
        }finally {
            System.out.println("关闭线程池......");
            // 当所有子线程已经执行结束,就是正常关闭
            // 中断任务,需要立即停止所有还在扫描的子线程
            pool.shutdownNow();
        }
        long end = System.nanoTime();
        System.out.println("文件扫描任务结束,共耗时 : " + (end - start) * 1.0 / 1000000 + "ms");
        System.out.println("文件扫描任务结束,根目录为 : " + filePath);
        System.out.println("共扫描到 : " + fileNum.get() + "个文件");
        System.out.println("共扫描到 : " + dirNum.get() + "个文件夹");
    }

   /**
     * ----子线程
     * @param file
     */
    private void scanThread(File file){
        if (file == null){
            return;
        }
        pool.submit(()->{
       //首先将当先文件夹下的所有子文件信息全都保存到数据库中
            this.callBack.callback(file);
            File[] files = file.listFiles();
        //遍历当前文件下所有的文件/文件夹
            for (File f :files) {
                if (f.isDirectory()){
                    dirNum.incrementAndGet();
                    threadCount.incrementAndGet();
                    scanThread(f);
                }else {
                    fileNum.incrementAndGet();
                }
            }
            // 当前线程将这一级目录下的文件夹(创建新线程递归处理)和文件的保存扫描任务执行结束
            System.out.println(Thread.currentThread().getName() + "扫描 : " + file + "任务结束");
            threadCount.decrementAndGet();

            if (threadCount.get() == 0){
                // 所有线程已经结束任务
                System.out.println("所有扫描任务结束");
                // 唤醒主线程
                latch.countDown();
            }
        });
    }
}

2. FileSearch类 --(功能2的实现

根据选择的文件夹路径和用户输入的内容从数据库中查找出指定的内容并返回(这里支持模糊搜索)

        1. 首先根据用户选择的文件夹路径dir查询内容

       String sql = "select name,path,size,is_directory,last_modified from file_meta " +
                    " where (path = ? or path like ?)";

                                            |                       |

(一级目录)该文件夹下的子文件信息     (二级目录--多级目录下的文件信息)

        2.如果搜索框的内容(中文,拼音,或是拼音首字母)不为空,再根据搜索框的内容在检索文件信息。

        3.将文件夹路径和搜索框内容共同检索到的文件信息保存到list集合中,并返回。

                3-1:设置FileMeta对象的属性

                       

public class FileSearch {
    /**
     *
     * @param dir 用户选择的检索的文件夹路径 一定是不为空的
     * @param content 用户搜索框中的内容 - 可能为空,若为空就展示当前数据库中选择的路径下的所有内容即可
     * @return
     */
    public static List<FileMeta> search(String dir, String content) {
        List<FileMeta> result = new ArrayList<>();
        Connection connection = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            connection = DBUtil.getConnection();
            // 先根据用户选择的文件夹dir查询内容
            String sql = "select name,path,size,is_directory,last_modified from file_meta " +
                    " where (path = ? or path like ?)";
            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 meta = new FileMeta();
                meta.setName(rs.getString("name"));
                meta.setPath(rs.getString("path"));
                meta.setIsDirectory(rs.getBoolean("is_directory"));
                if (!meta.getIsDirectory()) {
                    // 是文件,保存大小
                    meta.setSize(rs.getLong("size"));
                }
                meta.setLastModified(new Date(rs.getTimestamp("last_modified").getTime()));
                result.add(meta);
            }
        }catch (SQLException e) {
            System.err.println("从数据库中搜索用户查找内容时出错,请检查SQL语句");
            e.printStackTrace();
        }finally {
            DBUtil.close(ps,rs);
        }
        return result;
    }
}

(二 · 六)项目的核心实现类(core包)

1.Controller类

1.首先,进行界面的初始化(初始化前,初始化数据库),并添加搜索框监听器,当内容改变时,刷新界面。

2.在choose()方法中进行文件的扫描任务

3.界面的刷新(当搜索框内容改变时,从数据库中去搜索对应的文件信息)

/**
 * app.fxml中的 fx:id的名称要和app包下的Controller类中的属性名称完全一致,
 * 这样的话界面中的内容才会正确地被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();

        // 添加搜索框监听器,内容改变时执行监听事件
        // 在搜索框内输入一个内容,表格会有刷新,(界面的内容一作修改,就会捕捉到这个修改,刷新界面)
        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;
        // 1.获取选择的目录路径,并显示
        String path = file.getPath();
        // 2.在界面中显示路径的内容(选择目录的旁边)
        // srcDirectory 对应app.fxml文件中  <Label fx:id="srcDirectory">
        this.srcDirectory.setText(path);
       // 3.获取要扫描的文件夹路径之后,进行文件的扫描工作
        System.out.println("开始进行文件扫描任务,根路径为: " +path);
        //3.1 创建一个类task.FileScanner类 进行文件扫描
        //3.2产生一个文件扫描的对象

        //3.3 此处进行文件扫描任务,要决定信息到底保存到哪个终端
        // 此时将文件信息保存到数据库中
        FileScanner fileScanner = new FileScanner(new FileSaveToDB());

        //3.4进行文件扫描
        if (scanThread != null){
            scanThread.interrupted();
//            修复多次中断后选择同一文件夹导致的内容显示重复问题
            fileTable.getItems().clear();
        }
        scanThread = new Thread(()->{
            fileScanner.scan(file);
            // TODO 在数据表中展示文件内容(刷新界面,获取扫描到的文件信息(freshTable))
            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();
            //根据选择的路径 + 用户的输入(若为空就展示所有内容),将数据库中的指定内容刷新到界面中
            List<FileMeta> filesFromDB = FileSearch.search(dir,content);
            metas.addAll(filesFromDB);
        }
    }

}

2.FileMeta类

 FileMeta类是和数据库打交道的类,最终程序中获取数据库的记录就通过本类来描述  (实体类)
 这个类就对应我们的数据库表名,数据表中的一行记录就对应我们这个类的一个对象
  该类的一个对象就对应数据表的一行
  数据表的所有内容就对应FileMeta这个类的对象数组

public class FileMeta {
    private String name;
    private String path;
    private Boolean isDirectory;
    private Long size;
    private Date lastModified;
    // 若包含中文名称,名称全拼
    private String pinYin;
    // 拼音首字母
    private String pinYinFirst;

    // 创建FileMeta对象时,将name,path, isDirectory,size, lastModified信息传进来
    // 一个FileMeta对象  对应  数据表中的一行记录
    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;
    }
}
 数据表到Java类的映射中,基本类型使用包装类
 为什么不使用基本数据类型呢?基本数据类型有默认值
 在某些场景下(文件夹的size)就需要为空,使用基本数据类型就会很麻烦

演示:

 

以上FileMeta类中的三个属性名称与app.fxml文件中的属性名称不一致,因此需要将FileMeta的属性做一些处理之后才能展示出来(这些属性名要和app.fxml中保持一致)

修改之后:

FileMeta类:

@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, Date lastModified) {
        this.name = name;
        this.path = path;
        this.isDirectory = isDirectory;
        this.size = size;
        this.lastModified = lastModified;
    }
}

Util类

package util;

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

/**
 * Created with IntelliJ IDEA.
 * Description:  通用工具类
 */

public class Util {
    // 日期格式
    public static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";


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

    /**
     * 根据传入的文件大小返回不同的单位
     * 支持的单位如下 B,KB,MB,GB
     * @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 parseDate(Date lastModified) {
        return new SimpleDateFormat(DATE_FORMAT).format(lastModified);
    }
}

修改后的演示:

 

 

                                                                 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1003529.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

学会这个技能,写字楼立马高级起来!

在当今现代化社会中&#xff0c;写字楼已成为商业和行政活动的中心。成千上万的人们每天涌入这些高楼大厦&#xff0c;从事各种各样的工作&#xff0c;以实现公司和组织的目标。然而&#xff0c;与这种繁忙的办公环境一样&#xff0c;也带来了一系列的安全挑战和管理难题。 随着…

【大数据之Kafka】十一、Kafka消费者及消费者组案例

1 独立消费者案例&#xff08;订阅主题&#xff09; &#xff08;1&#xff09;需求&#xff1a;创建一个独立消费者&#xff0c;消费 first 主题中数据。 &#xff08;2&#xff09;分析&#xff1a; 注意&#xff1a;在消费者 API 代码中必须配置消费者组 id。命令行启动消…

算法通关村第13关【青铜】| 数字与数学基础问题

数字统计专题 1.数组元素积的符号 思路&#xff1a;每回碰到负数就取反 class Solution {public int arraySign(int[] nums) {int res nums[0];if(nums[0]>0){res 1;}else if(nums[0]<0){res -1;}else{return res;}for(int i 1;i<nums.length;i){if(nums[i]<…

Linux基本认识

一、Linux基本概念 Linux 内核最初只是由芬兰人林纳斯托瓦兹&#xff08;Linus Torvalds&#xff09;在赫尔辛基大学上学时出于个人爱好而编写的。 Linux 是一套免费使用和自由传播的类 Unix 操作系统&#xff0c;是一个基于 POSIX 和 UNIX 的多用户、多任务、支持多线程和多…

地下管网实时水位监测用什么设备好?

地下排水管网是城市重要基础设施生命线之一&#xff0c;主要用于排放雨水、地表水和废水&#xff0c;以维护城市的安全运行。然而&#xff0c;在极端天气事件发生时&#xff0c;排水系统可能会面临压力巨大&#xff0c;导致排水不畅引发城市内涝。通过对管网水位实时监测&#…

Java集合大总结——Collection集合

Collection集合的整理 1、List&#xff0c;Set&#xff0c;Queue&#xff0c;Map四者的区别集合底层数据结构梳理2、关于集合的的选用2.1 为什么使用集合3、List接口3.1 ArrayList 和 Array&#xff08;数组&#xff09;的区别&#xff1f;3.1 LinkedList 为什么不能实现Random…

基于python+txt的学生成绩管理系统

基于pythontxt的学生成绩管理系统 一、系统介绍二、效果展示三、其他系统实现四、获取源码 一、系统介绍 录入学生信息查找学生信息删除学生信息修改学生信息排序统计学生信息显示所有学生信息 基于python的学生成绩管理系统&#xff0c;具备基本的增删改查功能&#xff0c;包…

2023-9-12 完全背包问题

题目链接&#xff1a;完全背包问题 初版(时间复杂度拉满) #include <iostream> #include <algorithm>using namespace std;const int N 1010;int n, m; int v[N], w[N]; int f[N][N];int main() {cin >> n >> m;for(int i 1; i < n; i ) cin >…

AntDB数据库参加ACDU中国行杭州站,分享数据库运维实践与经验

关于ACDU 和中国行: ACDU是由墨天轮社区举办的中国数据库联盟的品牌活动之一&#xff0c;在线下汇集数据库领域的行业知名人士&#xff0c;共同探讨数据库前沿技术及其应用&#xff0c;促进行业发展和创新的平台&#xff0c;也为开发者们提供友好交流的机会。 AntDB作为具有技术…

Kafka 基于 S3 的数据导出、导入、备份、还原、迁移方案

在系统升级或迁移时&#xff0c;用户常常需要将一个 Kafka 集群中的数据导出&#xff08;备份&#xff09;&#xff0c;然后在新集群或另一个集群中再将数据导入&#xff08;还原&#xff09;。通常&#xff0c;Kafka集群间的数据复制和同步多采用 Kafka MirrorMaker&#xff0…

【C++】常用集合算法

0.前言 1.set_intersection #include <iostream> using namespace std;// 常用集合算法 交集set_intersection #include<vector> #include<algorithm>void myPrint(int val) {cout << val << " "; }void test01() {vector<int>v…

Oracle启动报错解决:ora-00119和ora-00132

WINDOWS环境下&#xff0c; 查看Oracle的各项服务都正常&#xff0c; 但是SQL窗口启动ORACLE报错ora-00119和ora-00132&#xff0c;如何解决&#xff1a; 一、问题描述 1、ORACLE服务全部打开&#xff0c;没有报错&#xff1b; 2、plsql登陆报ora-12505错&#xff1b; 3、监听…

docker启动MySQL报错:退出状态码1

docker启动mysql反复重启&#xff0c;通过 使用 docker logs 容器ID chown: cannot read directory /var/lib/mysql/: Permission denied 但是目录权限确认没问题&#xff0c;即使 chmod 777 还是报相同的错误&#xff0c;后来发现是selinux的问题 查看状态 getenforce 临时…

保护个人隐私,自建个性图床:Cpolar+Qchan轻量级搭建教程分享

文章目录 前言1. Qchan网站搭建1.1 Qchan下载和安装1.2 Qchan网页测试1.3 cpolar的安装和注册 2. 本地网页发布2.1 Cpolar云端设置2.2 Cpolar本地设置 3. 公网访问测试总结 前言 图床作为云存储的一项重要应用场景&#xff0c;在大量开发人员的努力下&#xff0c;已经开发出大…

照片怎么换背景图?照片抠图换背景方法分享

照片怎么把背景进行更换呢&#xff1f;当我们拍好一张照片&#xff0c;但是对照片的背景不太满意&#xff0c;想要将照片的背景进行更换&#xff0c;怎么做才能实现呢&#xff1f;其实这一类的问题我们在制作海报的时候也会经常遇到&#xff0c;如果不是专业的图片编辑制作人员…

C++之weak_ptr与shared_ptr智能指针实例(一百九十五)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

问 ChatGPT 50个问题,耗水 500 毫升;通讯专家发帖称 iPhone 网络国内造假丨RTE开发者日报 Vol.47

开发者朋友们大家好&#xff1a; 这里是「RTE 开发者日报」&#xff0c;每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享 RTE &#xff08;Real Time Engagement&#xff09; 领域内「有话题的新闻」、「有态度的观点」、「有意思的数据」、「有思考的文章」、「…

mysql在ubuntu上命令行登陆密码不正确

1.登陆提示如下 2.使用mysql -u root -p登录也是类似的 3.打开宝塔面板 点击root密码&#xff0c;更改密码后即可在命令行界面登录 4.登录效果如下

Java从入门到精通-类和对象(二)

0. 类和对象 3. 类的构造方法 构造方法是一种特殊的方法&#xff0c;用于创建和初始化对象。构造方法的名称必须与类名相同&#xff0c;它没有返回值&#xff0c;并且在创建对象时自动调用。构造方法的主要作用是确保对象在创建时具有合适的初始状态。 以下是构造方法的基本概…

掌握这些,让你轻松玩转钡铼PLC网关与西门子S7-1200的MQTT通信

一、软硬件描述 西门子PLC S7-1215钡铼BL102网关mosquitto MQTT服务器&#xff08;腾讯云上搭建&#xff09;可以上网的路由器一套 二、需要使用的软件。 西门子Portal v15.1 (西门子PLC编程软件&#xff09;钡铼BL102调试软件及说明书。 说明书下载链接&#xff1a; http:/…