文件扫描模块

news2024/11/17 13:38:30

文章目录

  • 前言
  • 文件扫描模块
    • 设计初级扫描方案一
      • 实现单线程扫描
      • 整合扫描步骤
    • 设计初级扫描方案二
    • 周期性扫描
  • 总结

前言

我们这个模块考虑的是数据库里面的内容从哪里获取。
获取完成后,这时候,我们就需要把目录里面文件/子文件都获取出来,并存入数据库。

文件扫描模块

文件扫描模块,这个模块我们要考虑的是基本的业务逻辑理清楚,我们究竟要干什么,我们基本的步骤如下:

设计初级扫描方案一

我们会设计扫描方案,具体的扫描方案如下:

  • 针对单个⽬录, 列出该⽬录下现有的 ⽂件 + ⽬录, 记为 scanned
  • 根据⽬录, 从数据库查, 看当前数据库⾥记录了哪些数据, 记为 saved
  • 对⽐看哪些⽂件是 scanned ⾥没有, saved ⾥有的, 就从数据库中删除. (说明该⽂件是已经被删了)
  • 对⽐看哪些⽂件是 scanned ⾥⾯有, saved ⾥没有的, 就添加到数据库中. (说明该⽂件是新来的)

具体的业务逻辑代码:

//针对单个目录的扫描
    /**
     *      scan针对一个目录进行处理(整个遍历过程中的基本操作)
     *      这个方法针对当前path对应的目录进行分析
     *      列出这个path包含的文件和子目录,并且把这些内容更新到数据库中
     *      此方法不考虑子目录里面的内容
     * @param path
     */
    private void scan(File path){
         /*
    具体的方法步骤:
    1.先把当前路径在文件系统上有哪些文件/目录,列出来.=>把真实情况的结果作为List,称为scanned(看看真实的情况)
    2.拿着这个path去数据库查,看看数据库里都包含哪些对应的结果=>把这个结果也作为一个List,称为saveed(数据库保存的情况)
    3.看看scaned里面哪些数据是saved不存在的,把这些数据插入数据库,看看saved里面的哪些数据是scaned不存在的,把这些从数据库删除
     */
        System.out.println("[FileManger] 扫描路径: "+path.getAbsolutePath());
        //1.累出文件系统的真实目录
        List<FileMeta> scanned=new ArrayList<>();
        File[] files=path.listFiles();
        if (files !=null){
            for (File f: files) {
                scanned.add(new FileMeta(f));
            }
        }
        //2.列出数据库里面的内容
        List<FileMeta> saved=fileDao.searchByPath(path.getPath());
        //3.根据数据库里面的内容与文件系统中的内容进行比对,如果数据库与文件系统比对,文件系统有的,数据库没有的,就增加
        //文件系统没有的,数据库有的,数据库就删除
        List<FileMeta> forDelete=new ArrayList<>();
        for (FileMeta fileMeta:saved) {
            if (!scanned.contains(saved)){
                forDelete.add(fileMeta);
            }
        }
        fileDao.delete(forDelete);
        //4.找出文件系统中有的,数据库没有的,把这些内容往数据库插入
        List<FileMeta> forInsert=new ArrayList<>();
        for (FileMeta fileMeta:scanned){
            if (!saved.contains(scanned)){
                forInsert.add(fileMeta);
            }
        }
        fileDao.add(forInsert);
    }

实现单线程扫描

具体的扫描步骤确定之后,我们来规定一下扫描的方式,我们先试用单线程扫描的方式来看看。
总体思路是:

1.扫描当前目录。
2.获取当前目录下所有文件。
3.递归扫描每个子目录。
4.递归出口是当前根目录下无任何文件或目录时返回。
具体代码如下:

 public void scanAllOneThread(File basPath){
        if(!basPath.isDirectory()){
            return;
        }
        //1.针对当前目录进行扫描
        scan(basPath);
        //2.列出当前目录的所有文件
        File[] files=basPath.listFiles();
        //4.递归出口是当前根目录下无任何文件或目录时返回。
        if (files == null || files.length==0){
            //当前目录下没有东西
            return;
        }
       // 3.递归扫描每个子目录。
        for (File f :files){
            if (f.isDirectory()){
                scanAllOneThread(f);
            }
        }
    }

整合扫描步骤

这个类是来整合扫描具体的步骤。

public class SearchService {
    private FileDao fileDao=new FileDao();
    private FileManger fileManger=new FileManger();
    //程序初始化
    //basePath 为进行搜索指定路径
    public void init(String basePath){
        //1.创建数据表
        fileDao.initDB();
        //2.针对指定的目录开始扫描,然后进行数据库存储
        fileManger.scanAll(new File(basePath));
        System.out.println("[SearchService] 初始化完成!");

    }
    }

接下来再建个测试类,来测试一下扫描的结果。

public class TestSearchService {
    public static void main(String[] args) {
        SearchService searchService=new SearchService();
        //
        searchService.init("D:\\Study\\javaSe");

    }
}

设计初级扫描方案二

扫描的具体步骤跟扫描方案一的一样,就是扫描的方式变成了,多线程扫描而已。
使用线程池来创建。
具体代码如下

  /**
     * 实现多线程扫描所有目录
     */
    //1.生成一个线程池
    private static ExecutorService executorService = Executors.newFixedThreadPool(8);
    private void scanAllByThreadPool(File basePath){
        if (!basePath.isDirectory()){
            return;
        }
        //2.扫描操作放在线程池里面完成
        executorService.submit(new Runnable() {
            @Override
            public void run() {
                scan(basePath);
            }
        });
        //3.继续递归其他目录
        File[] files=basePath.listFiles();
        if (files == null || files.length==0){
            //当前目录下没有东西
            return;
        }
        for (File f :files){
            if (f.isDirectory()){
                scanAllByThreadPool(basePath);
            }
        }
    }

到这里基本的多线程扫描方案已经基本构建完成,但实际上还是存在问题的。
存在什么样的问题呢?大家可以想一想,我在这里列出来。
我们的代码相当于把扫描工作交给线程池完成,主线程只负责遍历目录。但这里就有问题存在了。
1.遍历目录完成了,扫描工作还没完成。
2.扫描工作完成了,遍历目录还没完成。
了解了问题之后,我们开始解决这个问题。之前在多线程也遇到了相同的问题,我们使用json解决的。
现在具体的解决方案如下:
1.引入一个计数器,每次线程增加任务的时候,都让计数器+1.
2.线程每昨晚一个任务的时候,就让计数器-1.
3.当计数器为0时,所有任务就执行完了。
其实这样的方案是有问题的,不过在这个扫描问题上,没问题,因为增加任务的速度大于执行任务的速度。

 	//初始化选手数目为1 ,当线程所有任务完成之后,就立即调用一次countDown进行撞线
    private CountDownLatch countDownLatch=new CountDownLatch(1);
    //衡量任务结束的计数器操作
    private AtomicInteger taskCount=new AtomicInteger(0);
   //主体逻辑版本
   public void scanAll(File baseDir){
        long beg=System.currentTimeMillis();
        System.out.println("[FileManager] scanAll 开始!");
        scanAllByThreadPool(baseDir);
        try {
            //开始等待
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long end =System.currentTimeMillis();
        System.out.println("[FileManager] scanAll 结束!" +(end -beg) +"ms");

    }
// 线程池版本扫描,线程安全版
    private void scanAllByThreadPool(File basePath) {
        if (!basePath.isDirectory()) {
            return;
        }

        // 计数器自增
        taskCount.getAndIncrement(); // taskCount++

        // 扫描操作, 放到线程池里完成.
        executorService.submit(new Runnable() {
            @Override
            public void run() {
                try {

                    scan(basePath);
                } finally {
                    // 计数器自减
                    // 把这个自减逻辑放到 finally 中, 确保自减操作是肯定能执行到的.
                    taskCount.getAndDecrement(); // taskCount--
                    if (taskCount.get() == 0) {
                        // 如果计数器为 0 了, 就通知主线程停表了.
                        countDownLatch.countDown();
                    }
                }
            }
        });

        // 继续递归其他目录.
        File[] files = basePath.listFiles();
        if (files == null || files.length == 0) {
            return;
        }
        for (File f : files) {
            if (f.isDirectory()) {
                scanAllByThreadPool(f);
            }
        }
    }

周期性扫描

我们最后还要加入一个周期性扫描呢。因为我们在我们的主逻辑中,是项目启动时,才扫描一次,我们万一在工具的使用中,加入新文件和删除旧文件呢,我们就需要周期性的扫描一次。
这个问题的思路有几种方式,我列举一下思路。
思路一:
可以搞一个单独的线程,这个线程周期性的扫描当前设定的这个路径
(比如设定每隔 30s 扫描一遍)
这个思路的问题:
这个扫描周期,不好确定
周期太长用户的修改不会及时感知到
周期太短浪费系统资源的.
思路二:
让操作系统来感知文件的变动(添加/删除/修改/重命名…,一旦有变化就通知咱们的程序Java 标准库提供了一个 APl,WatchService APl,就是干这个事情的
行不通!! 只能监测指定的目录,不能感知到目录里面的子目录/孙子目录等里面的情况…
思路三:
有一些第三方库,也实现了类似的功能.
Apache Commons-l0 这个包 里就提供了类似的 API可以感知到文件的增加,删除,重命名…支持子录/孙子目录…
这个方案本质上还是思路一!!!
everything!!! 是咋做的呢?
windows 上主流使用的文件系统
思路四:
everything 利用了 NTFS 这个文件系统的特殊性质
这个文件系统内置了一个特殊的日志功能.
会把用户的每次的变动都会记录到这个日志中, USN 机制
只需要读取这个日志内容,就知道用户进行了哪些文件改动

我们这里实现的思路是思路一:

1.在init方法中,先进行数据库的初始化。
2.然后启动一个扫描线程t,在while循环中周期性调用fileManger的scanAll方法扫描指定目录。
3.scanAll方法扫描目录后,会把扫描结果存入数据库中。
4.通过sleep来控制扫描周期,当前代码设置为20秒扫描一次。
5.通过判断t.isInterrupted()来退出循环,当调用shutdown方法时,会interrupt扫描线程t,使其立即退出扫描循环。
代码如下:

   private  Thread t=null;
    //程序初始化
    //basePath 为进行搜索指定路径
    public void init(String basePath){
        //初始情况下,就是数据库初始化好,进行下一步操作
        fileDao.initDB();
        //把这个操作挪到扫描线程中
//        fileManger.scanAll(new File(basePath));
        t=new Thread(()->{

            while (!t.isInterrupted()){
                fileManger.scanAll(new File(basePath));
                try {
                    //
//                   Thread.sleep(60000);
                    Thread.sleep(20000);

                } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
                }

            }
        });
        t.start();
        System.out.println("[SearchService] 初始化完成");
    }
    //使用这个方法,让我们的扫描线程停止下来
    public void shutdown(){
        if (t!=null){
            t.interrupt();

        }
    }

总结

最后我来梳理一下这个文件扫描的总体逻辑,具体图片如下:
在这里插入图片描述

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

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

相关文章

Flask-[项目]-搭建短网址系统:flask实现短网址系统,短网址系统,构建短网址系统

一、项目下载地址 https://gitee.com/liuhaizhang/short-url-systemhttps://gitee.com/liuhaizhang/short-url-system 二、项目搭建 2.1、基本环境安装 1、安装好mysql数据库 2、安装好redis数据 3、安装好python解释器 2.2、项目依赖安装 1、切换到python解释器环境中 …

MES管理系统在制造业中的应用及其核心构成

在制造业的信息化进程中&#xff0c;车间级的信息化一直是其薄弱环节。为了提升车间的自动化水平&#xff0c;发展MES数字化技术成为了有效的途径。MES管理系统强调车间级的过程集成、控制和监控&#xff0c;合理地配置和组织所有资源&#xff0c;以满足车间的信息化需求。它提…

5G商企专网,助力打造城市生命线“安徽样板”

为扎实抓好重点领域安全监管&#xff0c;防范各类生产突发事故发生&#xff0c;近年来&#xff0c;安徽芜湖启动了城市生命线工程项目&#xff0c;致力于打造城市生命线“安徽样板”。 项目建设如火如荼&#xff0c;一些难题也不断涌现。比如&#xff0c;为提升城市安全保障能…

浅谈电动汽车智能充电桩及运营管理云解决方案

贾丽丽 安科瑞电气股份有限公司 上海嘉定 201801 摘要&#xff1a;电动汽车采用了电力作为发动能源&#xff0c;但是同样存在很大缺陷,即续航能力方面存在较大不足。因此如何利用现代技术进行电动汽车的智 能充电便十分重要。在电动汽车智能充 电的研究过程中需要用到的技术有…

Kubernetes概述架构与工作流程简述

文章目录 Kubernetes概述Kubernetes优势Kubernetes 集群组件控制平面组件Node 组件 Kubernetes工作流程下期预告 Kubernetes概述 Kubernetes 是一个可移植、可扩展的开源平台&#xff0c;用于管理容器化的工作负载和服务&#xff0c;可促进声明式配置和自动化。 Kubernetes 拥…

pyppeteer 基本用法和案例

特点 自带chromium 不用自己下载也可以下载&#xff0c;比较省事.比selenium好用 可异步调用 简介 一. pyppeteer介绍 Puppeteer是谷歌出品的一款基于Node.js开发的一款工具&#xff0c;主要是用来操纵Chrome浏览器的 API&#xff0c;通过Javascript代码来操纵Chrome浏览器&am…

Columbus:一个基于API实现的子域名发现服务工具

关于Columbus【点击领取安装包】 Columbus是一款功能强大的子域名发现与枚举工具&#xff0c;该工具基于API实现其功能&#xff0c;并且还提供了很多其他的高级功能。在该工具的帮助下&#xff0c;广大研究人员可以快速且高效地实现子域名枚举任务。 【点击领取安装包】 前端…

接口测试复习Requests PyMysql Dubbo

一。基本概念 接口概念&#xff1a;系统与系统之间 数据交互的通道。 接⼝测试概念&#xff1a;校验 预期结果 与 实际结果 是否⼀致。 特征&#xff1a; 测试⻚⾯测试发现不了的问题。&#xff08;因为&#xff1a;接⼝测试 绕过前端界⾯。 &#xff09; 符合质量控制前移理…

【开发篇】十八、SpringBoot整合ActiveMQ

文章目录 1、安装ActiveMQ2、整合3、发送消息到队列4、使用消息监听器对消息队列监听5、流程性业务消息消费完转入下一个消息队列6、发布订阅模型 1、安装ActiveMQ docker安装 docker pull webcenter/activemqdocker run -d --name activemq -p 61616:61616 -p 8161:8161 webce…

Pikachu靶场——跨站请求伪造(CSRF)

文章目录 1. 跨站请求伪造&#xff08;CSRF&#xff09;1.1 CSRF(get)1.2 CSRF(post)1.3 CSRF Token1.4 CSRF漏洞防御 1. 跨站请求伪造&#xff08;CSRF&#xff09; 还可以参考我的另一篇文章&#xff1a;跨站请求伪造(CSRF) 全称Cross-site request forgery&#xff0c;翻译…

DRM全解析 —— plane详解(1)

本文参考以下博文&#xff1a; Linux内核4.14版本——drm框架分析(5)——plane分析 特此致谢&#xff01; 1. 简介 一个plane代表一个image layer&#xff08;硬件图层&#xff09;&#xff0c;最终的image由一个或者多个plane(s)组成。plane和 Framebuffer 一样是内存地址。…

软信天成:医药企业数据整合难、共享难?这套企业级数据治理体系是关键

在数字化时代&#xff0c;数据已成为企业发展的核心资产。然而&#xff0c;对于拥有十余个业务系统的某大型国有医药企业&#xff08;下文简称案例企业&#xff09;来说&#xff0c;数据整合难、共享难等问题却一直存在。面对庞杂的数据来源和多样化的数据格式&#xff0c;传统…

Datawhale团队第十期录取名单!

Datawhale团队 公示&#xff1a;Datawhale团队成员 Datawhale成立四年了&#xff0c;从一开始的12个人&#xff0c;学习互助&#xff0c;到提议成立开源组织&#xff0c;做更多开源的事情&#xff0c;帮助更多学习者&#xff0c;也促使我们更好地成长。于是有了我们的使命&…

OpenAI重大更新!为ChatGPT推出语音和图像交互功能

原创 | 文 BFT机器人 OpenAI旗下的ChatGPT正在迎来一次重大更新&#xff0c;这个聊天机器人现在能够与用户进行语音对话&#xff0c;并且可以通过图像进行交互&#xff0c;将其功能推向与苹果的Siri等受欢迎的人工智能助手更接近的水平。这标志着生成式人工智能运动的一个显著…

Embedding技术与应用 (2) :神经网络的发展及现代Embedding方法简介

编者按&#xff1a;IDP开启Embedding系列专栏&#xff0c;详细介绍Embedding的发展史、主要技术和应用。 本文是《Embedding技术与应用系列》的第二篇&#xff0c;重点介绍 神经网络的发展历程及其技术架构&#xff0c;剖析了嵌入技术与这些神经网络&#xff08;Transformer、B…

通讯网关软件017——利用CommGate X2Modbus实现Modbus RTU访问MSSQL服务器

本文介绍利用CommGate X2Modbus实现Modbus RTU访问MS SQL数据库。CommGate X2MODBUS是宁波科安网信开发的网关软件&#xff0c;软件可以登录到网信智汇(http://wangxinzhihui.com)下载。 【案例】如下图所示&#xff0c;实现上位机通过Modbus RTU来获取MS SQL数据库的数据。 【…

解密人工智能:决策树 | 随机森林 | 朴素贝叶斯

文章目录 一、机器学习算法简介1.1 机器学习算法包含的两个步骤1.2 机器学习算法的分类 二、决策树2.1 优点2.2 缺点 三、随机森林四、Naive Bayes&#xff08;朴素贝叶斯&#xff09;五、结语 一、机器学习算法简介 机器学习算法是一种基于数据和经验的算法&#xff0c;通过对…

0基础学习VR全景平台篇 第104篇:720全景后期软件安装

上课&#xff01;全体起立~ 大家好&#xff0c;欢迎观看蛙色官方系列全景摄影课程&#xff01; 摄影进入数码时代&#xff0c;后期软件继承“暗房工艺”&#xff0c;成为摄影师表达内在情感的必备工具。 首先说明&#xff0c;全景摄影与平面摄影的一个显著的区别是全景图片需…

将表情存入数据库

概念&#xff1a; 表情是一种比较特殊的字符串&#xff0c;为unicode编码&#xff0c;unicode编码要存入数据库一般情况下&#xff0c;是存不了的&#xff0c;有两种解决方式&#xff0c;一种将数据表编码方式改为unicode编码方式&#xff0c;但是这种情况适用于功能刚开始设计…

TikTok+KOL:打造品牌种草的完美组合

随着社交媒体的崛起&#xff0c;品牌推广已经进入了全新的时代。在这个数字时代&#xff0c;消费者不再仅仅关注产品的质量和功能&#xff0c;他们也关注品牌的故事&#xff0c;情感共鸣以及社交影响力。 因此&#xff0c;品牌种草已经成为品牌营销策略中的一环&#xff0c;而…