Java文档搜索引擎总结

news2024/10/7 16:22:45

Java文档搜索引擎总结

  • 项目介绍
  • 项目使用的技术栈
  • 前端页面展示
  • 后端逻辑部分
    • 索引部分
    • 搜索模块部分
    • Web模块部分

项目介绍

Java文档搜索引擎项目是一个SSM项目,该项目的前端界面部分是由搜索页面和展示页面组成,后端部分索引模块(ScanAnalysis、index)、搜索模块(Searcher)、Web模块(SearcherController)。该项使用ansj第三方分词库进行分词,该项目并没有使用爬虫程序来获取Java文档,而是直接将Java文档下载下来,将Java文档里面的内容进行分词保存到正排索引文件和倒排索引文件中。

项目使用的技术栈

HTML、CSS、JS、Ajax、SpringBoot、SpringMVC

前端页面展示

搜索页面:
在这里插入图片描述
显示页面:
在这里插入图片描述

后端逻辑部分

索引部分

索引部分底层实现了两个类:ScanAnalysis类、Index类
***ScanAnalysis类:***用来扫描Java文档中的所有HTML文件,将HTML文件的标题、url路径、正文保存到正排索引文件和倒排索引文件中。
***Index类:***底层实现了正排索引结构和倒排索引结构,Index类是配合ScanAnalysis类一起使用的,Index将HTML文件内容保存到正排索引和倒排索引结构中,最终保存到正排索引文件和倒排索引文件中。

ScanAnalysis类的底层代码:

public class ScanAnalysis {

    //要扫描的根路径
    private static final String PATH_ROOT = "D:\\知识复习思维导图(Java)和Java笔记\\project-warehouse\\jdk-8u351-docs-all\\docs\\api";

    //Java文档的网络地址 不同部分
    private static final String JAVA_PATN = "https://docs.oracle.com/javase/8/docs/api/";

    //索引对象
    private static Index index = new Index();
    /**
     * 启动方法
     * 我们在进行扫描的时候,我们会发现在进行扫描的时候效率是比较低的。
     * 该方法使用的是单线程的方式
     * 我们可以使用多线程的方式来提高效率
     */
    public void run() {
        long ben1 = System.currentTimeMillis();
        //保存每一个文档的路径
        ArrayList<String> arrayList = new ArrayList<>();
        //1.获取每一个文档的路径
        scanPath(PATH_ROOT,arrayList);
        long ben = System.currentTimeMillis();
        //2.对每一个html文件进行解析
        for (String pathChild:arrayList) {
            analysis(pathChild);
        }
        long end = System.currentTimeMillis();
        System.out.println("解析所花费的时间:"+(end - ben)+"ms");
        //3.将索引保存的索引文档中
        index.saveFile();
        long end1 = System.currentTimeMillis();

        System.out.println("整个程序的时间:"+(end1 - ben1) +"ms");
    }

    /**
     * 启动方法2:我们对解析这个步骤使用多线程的方式来提高效率
     *
     */
    public void run2() {
        long ben1 = System.currentTimeMillis();
        //保存每一个文档的路径
        ArrayList<String> arrayList = new ArrayList<>();
        //1.获取每一个文档的路径
        scanPath(PATH_ROOT,arrayList);
        long ben = System.currentTimeMillis();
        //2.对每一个html文件进行解析
        //我们创建一个有时光线程的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(15);
        //这个CountDownLatch对象,是用来表明需要等待多少个任务才结束
        //因为我们要等到解析这个过程完成了在执行下一步
        CountDownLatch countDownLatch = new CountDownLatch(arrayList.size());
        for (String pathChild:arrayList) {
            //将解析的工作提交倒线程池中
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    analysis(pathChild);
                    //完成一次解析任务就减一
                    countDownLatch.countDown();
                }
            });
        }


        try {
            //等待任务结束,如果没结束,就阻塞等待
            countDownLatch.await();
            //关闭线程池
            executorService.shutdown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("解析所花费的时间:"+(end - ben)+"ms");
        //3.将索引保存的索引文档中
        index.saveFile();
        long end1 = System.currentTimeMillis();

        System.out.println("整个程序的时间:"+(end1 - ben1) +"ms");
    }

    /**
     * 对 HTML文件进行解析
     * 获取到题目、正文、url
     * @param pathChild
     */
    private void analysis(String pathChild) {
        File file = new File(pathChild);
        //1.获取标题
        String title = getTitle(file);
//        System.out.println(title);
        //2.获取正文
        String content = getContents(file);
        //3.获取url
        String url = getUrl(file);
        System.out.println(url);
        //4.将标题、正文、url保存到索引中
        index.saveIndex(title,content,url);

    }

    /**
     * 获取url
     * @param file
     * @return
     */
    private String getUrl(File file) {
        StringBuilder stringBuilder = new StringBuilder();
        String str = file.getAbsolutePath().substring(PATH_ROOT.length()+1);
        for (int i = 0; i < str.length(); i++) {
            char ch = str.charAt(i);
            if (ch != '\\') {
                stringBuilder.append(ch);
            } else {
                stringBuilder.append('/');
            }
        }
        return JAVA_PATN+stringBuilder.toString();
    }

    /**
     * 获取正文,这个比较麻烦,我们需要去除标签,和<script></script>里面的内容
     * 这里我们需要使用正则表达式
     * @param file
     * @return
     */
    public String getContents(File file) {
        //获取到HTML里面的内容
        String content = getcontentHtml(file);
        //使用正则表达式,将<script></script>标签和里面的内容都替换掉
        //字符串中的replaceAll方法是支持正则表达式的
        content = content.replaceAll("<script.*?>(.*?)</script>"," ");
        //使用正则表达式,去除其他标签
        content = content.replaceAll("<.*?>"," ");
        //使用正则表达式,去除连续的空格
        content = content.replaceAll("\\s+"," ");
        return content ;
    }

    /**
     * 获取到HTML文件的内容,这人进行文件读取操作,
     * 使用字符流,进行读取
     * @param f
     * @return
     */
    private String getcontentHtml(File f) {

        try(BufferedReader bufferedReader = new BufferedReader(new FileReader(f),1024*1024)) {
            StringBuilder content = new StringBuilder();
            while (true) {
                int ret = bufferedReader.read();
                if (ret == -1) {
                    break;
                }
                char ch = (char) ret;
                //去除换行
                if(ch == '\n' || ch == '\r') {
                    ch = ' ';
                }
                content.append(ch);
            }
            return content.toString();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取标题
     * @param file
     * @return
     */
    private String getTitle(File file) {
        return file.getName().replace(".html","");
    }


    /**
     * 扫描根路径,获取该目录下的索引HTML文件的路径
     * 这里要使用的递归 和 文件操作
     * @param pathRoot
     * @param arrayList
     */
    private void scanPath(String pathRoot, ArrayList<String> arrayList) {
        File file = new File(pathRoot);

        //获取到该目录的以及文件对象
        File[] files = file.listFiles();
        //遍历
        for (File file1:files) {
            if (file1.isFile()) {
                //是普通文件
                //我们要的是html文件,所以还要进行处理
                if (file1.getAbsolutePath().endsWith("html")) {
                    arrayList.add(file1.getAbsolutePath());
                    System.out.println(file1.getAbsolutePath());
                }
            } else {
                //是目录,进行递归
                scanPath(file1.getAbsolutePath(),arrayList);
            }
        }
    }

    public static void main(String[] args) {
        ScanAnalysis scanAnalysis = new ScanAnalysis();
        //程序的入口
        scanAnalysis.run2();

    }
}

Index类的底层代码:

public class Index {
    //正排索引的底层,使用顺序表
    public ArrayList<JavaDocModel> arrayList = new ArrayList<>();
    //倒排索引的底层,使用HashMap
    public HashMap<String,ArrayList<Weight>> map = new HashMap<>();

    //创建两个锁
    private Object lock1 = new Object();
    private Object lock2 = new Object();
    //正排索引文件 和倒排索引文件保存的 根目录
    private static final String INDEX_SAVE_PATH =
            "D:\\知识复习思维导图(Java)和Java笔记\\project-warehouse\\jdk-8u351-docs-all\\";
    //线上环境 正排索引文件 和倒排索引文件保存的 根目录
//    private static final String INDEX_SAVE_PATH =
//            "/project/java_doc_searcher_ssm/";
    //进行JSON格式化的 对象
    private ObjectMapper objectMapper = new ObjectMapper();
    /**
     * 1.正排索引:通过文档Id来获取文档对象
     * @param docId
     * @return
     */
    public JavaDocModel getForwardIndex(Integer docId) {
        return arrayList.get(docId);
    }

    /**
     * 2.通过分词来获取相对应的一组文档的id,这里不仅仅获取到了id,还有权重,有利于进行排序
     * @param terim
     * @return
     */
    public ArrayList<Weight> getReverseIndex(String terim) {
        return map.get(terim);
    }

    /**
     * 3.将标题,正文,url
     * 保存到正排索引,和倒排索引中
     */
    public void saveIndex(String title,String content,String url){
        JavaDocModel javaDocModel = new JavaDocModel();
        javaDocModel.setContent(content);
        javaDocModel.setTitle(title);
        javaDocModel.setUrl(url);

        //1.建立正排索引
        buildForwardIndex(javaDocModel);
        //2.建立倒排索引
        buildReverseIndex(javaDocModel);
    }

    /**
     * 建立倒排索引
     * 我们需要对文档的标题,正文 进行分词
     * @param javaDocModel
     */
    private void buildReverseIndex(JavaDocModel javaDocModel) {
        //统计一个分词在标题和内容中出现多少次
        class Count{
            public Integer titleCount;
            public Integer contentCount;
        }
        //1.对文档标题 进行分词
        List<Term> terms = ToAnalysis.parse(javaDocModel.getTitle()).getTerms();
        //用来统计词频
        HashMap<String,Count> hashMap = new HashMap<>();//记录总的分词
        synchronized (lock1) {
            //遍历分词terms
            for (Term term:terms) {
                //获取到分词结果
                String termName = term.getName();

                Count myCount = hashMap.get(termName);
                if (myCount == null) {
                    //没有
                    Count newCount = new Count();
                    newCount.titleCount = 1;
                    newCount.contentCount = 0;
                    hashMap.put(termName,newCount);
                } else {
                    //有,titleCount加一
                    myCount.titleCount += 1;
                }
            }


            //2.对文档对象的正文进行分词
            terms = ToAnalysis.parse(javaDocModel.getContent()).getTerms();
            //遍历分词terms
            for (Term term:terms) {
                //获取到分词结果
                String termName = term.getName();

                Count myCount = hashMap.get(termName);
                if (myCount == null) {
                    //没有
                    Count newCount = new Count();
                    newCount.contentCount = 1;
                    newCount.titleCount = 0;
                    hashMap.put(termName,newCount);
                } else {
                    //有,contentCount加一
                    myCount.contentCount += 1;
                }
            }

            //3.将hashMap 里的数据整合到 map 里面
            //遍历hashMap

            for (Map.Entry<String,Count> entry:hashMap.entrySet()) {
                String key = entry.getKey();
                Count val = entry.getValue();
                //从倒排索引中获取value值
                ArrayList<Weight> weights = map.get(key);

                if (weights == null) {
                    //没有,创建新的
                    ArrayList<Weight> newWeights = new ArrayList<>();
                    Weight weight = new Weight();
                    //设置文档Id
                    weight.setDocId(javaDocModel.getDocId());
                    //设置权重,titleCount*20+contentCount
                    weight.setWeight(val.contentCount + val.titleCount*20);
                    newWeights.add(weight);
                    map.put(key,newWeights);
                } else {
                    //有的话,直接添加
                    Weight weight = new Weight();
                    //设置文档Id
                    weight.setDocId(javaDocModel.getDocId());
                    //设置权重,titleCount*20+contentCount
                    weight.setWeight(val.contentCount + val.titleCount*20);
                    weights.add(weight);
                }
            }
        }

    }

    /**
     * 建立正排索引,以顺序表的下标作为文档ID
     * 直接插入顺序表就行
     * @param javaDocModel
     */
    private void buildForwardIndex(JavaDocModel javaDocModel) {

        synchronized (lock2) {
            //插入docId
            javaDocModel.setDocId(arrayList.size());
            //直接插入顺序表尾部
            arrayList.add(javaDocModel);
        }

    }

    /**
     * 4.将正排索引结构  和 倒排索引结构 保存到 正排索引文件 和倒排索引文件中
     * 序列化的方法:以JSON的格式保存
     */
    public void saveFile() {
        //正排索引 和 倒排索引保存的目录
        File filePath = new File(INDEX_SAVE_PATH);
        if (!filePath.exists()) {
            //创建目录
            filePath.mkdirs();
        }
        //正排索引文件对象
        File fileForwardIndex = new File(INDEX_SAVE_PATH+"forward.txt");
        //倒排索引文件对象
        File fileReverseIndex = new File(INDEX_SAVE_PATH+"reverse.txt");

        if (!fileForwardIndex.exists()) {
            //不存在,创建正排索引文件
            try {
                fileForwardIndex.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        if (!fileReverseIndex.exists()) {
            //不存在,创建倒排索引文件
            try {
                fileReverseIndex.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        try {
            //将正排索引结构转成JSON格式,保存到正排索引文件中
            objectMapper.writeValue(fileForwardIndex,arrayList);
            //将倒排索引结构转成JSON格式,保存到倒排索引文件中
            objectMapper.writeValue(fileReverseIndex,map);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 5.加载正排 和 倒排 文件 ,将内容加载倒内存中
     * 反序列
     */
    public void load() {
        long ben = System.currentTimeMillis();
        //正排索引文件对象
        File fileForwardIndex = new File(INDEX_SAVE_PATH+"forward.txt");
        //倒排索引文件对象
        File fileReverseIndex = new File(INDEX_SAVE_PATH+"reverse.txt");

        try {
            //这里的 readValue方法用法要注意
            // 第二个参数是一个匿名内部类,实现了TypeReference,目的就是 我们想要把JSON格式的字符串转成什么类型 告诉了 readValue方法
            //正排
            arrayList = objectMapper.readValue(fileForwardIndex, new TypeReference<ArrayList<JavaDocModel>>() {
            });
            //倒排
            map = objectMapper.readValue(fileReverseIndex, new TypeReference<HashMap<String,ArrayList<Weight>>>() {
            });
        } catch (IOException e) {
            e.printStackTrace();
        }

        long end = System.currentTimeMillis();
        System.out.println("加载文档的时间:"+(end - ben) +"ms");
    }
}

搜索模块部分

搜索模块部分底层实现了Searcher类,提供了searcher方法来搜索相关的文档。
Searcher类的底层代码:

public class Searcher {
    //索引类
    private Index index = new Index();
    //保存停用词表的数据结构
    private Set<String> stopWordsSet = new HashSet<>();
    //停用词表的存放路径
    private static final String STOP_WORDS =
            "D:\\知识复习思维导图(Java)和Java笔记\\project-warehouse\\jdk-8u351-docs-all\\stop_words.txt";
    //线上环境 停用词表的存放路径
//    private static final String STOP_WORDS =
//            "/project/java_doc_searcher_ssm/stop_words.txt";
    public Searcher() {
        //1.创建该类的时候,加载一些索引文档
        index.load();
        //2.创建该类的时候,加载停用词表
        loadStopWords();

    }

    /**
     * 加载停用词表
     */
    private void loadStopWords() {
        long ben = System.currentTimeMillis();
        //进行读操作
        try(BufferedReader bufferedReader = new BufferedReader(new FileReader(STOP_WORDS)) ){

            while (true) {
                String str = bufferedReader.readLine();
                if (str == null) {
                    break;
                }
                stopWordsSet.add(str);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("加载停用词表的时间:"+(end - ben) + "ms");
    }


    public List<ResultReturnModenl> searcher(String word) {
        //将查询词进行分词、
        List<Term> terms = ToAnalysis.parse(word).getTerms();
        //我们通过分词结果可以得出,有些分词是不合理的
        //我们要排除一些不合理的分词结果
        //这里我们使用停用词表进行过滤
        List<Term> newTerms = new ArrayList<>();//保存过滤后的term
        for (Term term:terms) {
            //分词内容
            String wordName = term.getName();
            if (!stopWordsSet.contains(wordName)) {
                //不是停用词
                newTerms.add(term);
            }
        }

        //遍历newTerms,获取要返回的数据
        List<ArrayList<Weight>> listList = new ArrayList<>();
        for (Term term:newTerms) {
            //获取倒分词的内容
            String wordName = term.getName();
            //通过倒排索引,来获取倒相对应的文档对象
            ArrayList<Weight> reverseIndex = index.getReverseIndex(wordName);
            //判断是否拿到
            if (reverseIndex == null) {
                //没有拿到
                continue;
            }
            //将reverseIndex保存到 listList中
            listList.add(reverseIndex);
        }
        //合并listList中的数组,并且进行去重
        //类似于合并多个有序数组,并且最后的结果要有序
        List<Weight> list = sortArray(listList);

        //对list进行排序,按照权重的大小由高到低排序
        Collections.sort(list, new Comparator<Weight>() {
            @Override
            public int compare(Weight o1, Weight o2) {
                //降序
                return o2.getWeight() - o1.getWeight();
            }
        });
        //保存返回的数据
        List<ResultReturnModenl> results = new ArrayList<>();
        //将数据进行封装
        for (Weight weight:list) {
            //通过正排索引找到文档对象
            JavaDocModel forwardIndex = index.getForwardIndex(weight.getDocId());

            ResultReturnModenl resultReturnModenl = new ResultReturnModenl();
            //设置标题
            resultReturnModenl.setTitle(forwardIndex.getTitle());
            //设置url
            resultReturnModenl.setUrl(forwardIndex.getUrl());
            //设置摘要
            resultReturnModenl.setDesc(getDesc(forwardIndex.getContent(),newTerms));

            results.add(resultReturnModenl);
        }
        return results;
    }

    /**
     生成正文摘要
     *    由于docInfo对象里面是正文,所以还要做一些处理
     *    摘要要包含 查询词 或者 查询词的一部分
     *    生成摘要的思路:可以遍历查询词的分词,找到对应位置
     *   就针对这个位置,往前截取60个字符,作为描述的开始,然后从描述开始在截取160个字符
     * @param content
     * @param newTerms
     * @return
     */
    public String getDesc(String content, List<Term> terms) {
        //记录分词出现的位置
        int termIndex = -1;
        for (Term term:terms) {
            //获取到分词内容
            String wordName = term.getName();
            //将正文转成小写 使用toLowerCase()
            //此处需要的是全词匹配,在word前后都加一个空 在进行查找
            //这里的匹配不严谨,更严谨的方法是使用 正则表达式
            //indexOf不支持正则表达式
            //Java提供了 Pattern 和 Matcher 这两个类 来实现正则表达式,自己学习一下
            //Pattern : 描述一个匹配规则
            //Matcher 负责进行具体的匹配工作
            //这里的做法:把不是空格的转成空格
            content = content.toLowerCase().replaceAll("\\b"+wordName+"\\b"," " + wordName + " ");
            termIndex = content.toLowerCase().indexOf(" "+wordName+" ");

            if (termIndex != -1 ) {
                //存在
                break;
            }
        }

        if (termIndex == -1) {
            //所有的分词结果都不存在
            //返回正文的前160个字符
            if (content.length() <=160) {
                return content;
            }
            return content.substring(0,160)+"...";
        }
        //程序如果到这里,说明正文中有分词结果
        //判断是否要往前60个字符
        termIndex = termIndex - 60 >=0?termIndex-60:0;
        String desc = "";//保存正文摘要
        if (termIndex+160 >= content.length()) {
            //从termIndex这个位置截到尾
            desc = content.substring(termIndex);
        } else {
            desc = content.substring(termIndex,160+termIndex)+"...";
        }
        //在此处加上替换操作,把描述中的 和 分词结果相同的部分,
        //加上依次<i>标签,可以使用 replaceAll 的方法来实现
        //者样在前端显示的时候,可以标红
        //遍历分词结果
        for (Term term:terms) {
            //获取到结果
            String word = term.getName();
            //注意此处要进行全字匹配,不区分大小写替换
            desc = desc.replaceAll("(?i) "+word +" ","<i> "+word+" </i>");
        }
        return desc;
    }

    /**
     * 合并listList中的数组,并且进行去重
     * 类似于合并多个有序数组,并且最后的结果要有序
     * @param listList
     * @return
     */
    private List<Weight> sortArray(List<ArrayList<Weight>> listList) {
        class Pos{
            public Integer row = 0;//行
            public Integer col = 0;//列

            public Pos(Integer row, Integer col) {
                this.row = row;
                this.col = col;
            }
        }
        //使用优先级队列,来解决该问题
        //创建优先级队列
        PriorityQueue<Pos> pos = new PriorityQueue<>(new Comparator<Pos>() {
            @Override
            public int compare(Pos o1, Pos o2) {
                //小根堆
                return listList.get(o1.row).get(o1.col).getDocId() - listList.get(o2.row).get(o2.col).getDocId();
            }
        });
        //将每一个数组,按docId的大小,升序排序
        for (ArrayList<Weight> weights:listList) {
            Collections.sort(weights, new Comparator<Weight>() {
                @Override
                public int compare(Weight o1, Weight o2) {
                    return o1.getDocId() - o2.getDocId();
                }
            });
        }
        //将每一个数组的第一个元素的位置放进来
        for (int i = 0; i < listList.size(); i++) {
            pos.offer(new Pos(i,0));
        }
        List<Weight> listResult = new ArrayList<>();//保存最后返回的结果
        while (!pos.isEmpty()) {
            //从优先级队列出来的队首元素
            Pos pos1 = pos.poll();
            if (listResult.size() == 0) {
                //插入第一个元素
                listResult.add(listList.get(pos1.row).get(pos1.col));
            } else {
                //不是第一个,要判断是否于前一个相同,相同权重相加
               if (listResult.get(listResult.size() - 1).getDocId() == listList.get(pos1.row).get(pos1.col).getDocId()) {
                   //文档相同,权重相加
                   listResult.get(listResult.size() - 1).setWeight(listResult.get(listResult.size() - 1).getWeight()+listList.get(pos1.row).get(pos1.col).getWeight());
               } else {
                   //不相同,添加到listResult中
                   listResult.add(listList.get(pos1.row).get(pos1.col));
               }
            }

            if (pos1.col + 1 >= listList.get(pos1.row).size()) {
                //这一行处理完了
                continue;
            }
            pos.offer(new Pos(pos1.row, pos1.col+1));
        }
        return listResult;
    }

    public static void main(String[] args) {
        Searcher searcher = new Searcher();

    }
}

Web模块部分

Web模块部分实现前后端的交互。
Web模块的代码:

@RestController
public class SearcherController {

    @Autowired
    Searcher searcher ;
    @RequestMapping("/searcher")
    public Object searcher(String word) {
        if (word == null || word.trim().equals("")) {
            return -1;
        }
        return searcher.searcher(word);
    }
    @RequestMapping("/getword")
    public String getWord(String word) {
        System.out.println(word);
        return word;
    }
}

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

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

相关文章

UNET 对 CARVANA 数据集的分割

目录 1. 介绍 2. UNET 网络 3. dataset 数据加载 4. utils 工具模块 4.1 get_loaders 函数 4.2 check_accuracy 函数 4.3 save_predictions_as_imgs 函数 4.4 完整代码 5. train 函数 5.1 关于导入的库文件 5.2 设置超参数 5.3 train_fn 训练一个epoch函数 5.4 m…

Redis是单线程还是多线程?Redis的10种数据类型,有哪些应用场景?

目录专栏导读一、同样是缓存&#xff0c;用map不行吗&#xff1f;二、Redis为什么是单线程的&#xff1f;三、Redis真的是单线程的吗&#xff1f;四、Redis优缺点1、优点2、缺点五、Redis常见业务场景六、Redis常见数据类型1、String2、List3、Hash4、Set5、Zset6、BitMap7、Bi…

【Java基础】30分钟Git 从入门到精通

一、 版本控制工具1、什么是版本控制系统&#xff1f;版本控制系统&#xff08;Version Control System&#xff09;:是一种记录一个或若干文件内容变化&#xff0c;以便将来查阅特定版本修订情况的系统。版本控制系统不仅可以应用于软件源代码的文本文件&#xff0c;而且可以对…

主成分分析(PCA)方法 和协方差 相关系数

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言1.主成分分析&#xff08;PCA&#xff09;方法2.算法步骤前言 主成分分析&#xff08;Principal Components Analysis&#xff0c;PCA&#xff09;是一种数据降…

Android工厂模式

工厂模式分为三种 :简单工厂模式 、工厂方法模式 、抽象工厂模式 。 目录 简单工厂模式 UML图 实现 使用场景&#xff1a; 优点 &#xff1a; 缺点&#xff1a; 工厂方法模式 UML图 实现 使用场景&#xff1a; 优点&#xff1a; 缺点&#xff1a; 抽象工厂模式 UM…

SpringCloud-负载均衡-Ribbon

文章目录1. 作用&#xff1a;负载均衡2. 应用实战2.1 provider-a代码2.2 provider-b代码2.3 consumer代码2.4 api工具向consumer发送请求查看对provider的调用情况3. ribbon总结3.1 Ribbon 源码核心3.2 如何实现负载均衡的呢&#xff1f;1. 作用&#xff1a;负载均衡 2. 应用实…

演唱会总是抢不到票?教你用Python制作一个自动抢票脚本

人生苦短 我用python 这个大家应该都知道吧&#xff1f; 是中国综合类现场娱乐票务营销平台&#xff0c; 业务覆盖演唱会、 话剧、音乐剧、体育赛事等领域。 如何快速抢票&#xff1f; 那么&#xff0c; 今天带大家用Python来制作一个自动抢票的脚本小程序 本文源码python安…

使用Java对稀疏数组的压缩与还原

稀疏矩阵的压缩与还原 稀疏数组中元素个数很少或者有大量的重复值&#xff0c;如果直接保存保存&#xff0c;会浪费很多空间&#xff0c;这时&#xff0c;就可以考虑对数组进行压缩存储。 先定义一个稀疏数组 //创建一个二维数组 11 * 11 int[][] array1 new int[11][11]; /…

Window 编辑、删除、新增右键菜单

关于 Window 右键菜单 右键菜单可以在注册表编辑器中新增和修改 建议先下载 registry-finder&#xff0c;查找速度更快&#xff01; 使用管理员模式打开 registry-finder 后&#xff0c;点击 HKEY_CLASSES_ROOT &#xff0c;修改注册表右键菜单的子路径如下表所示 类型路径…

49.在ROS中实现local planner(2)- 实现Purepersuit(纯跟踪)算法

48.在ROS中实现local planner&#xff08;1&#xff09;- 实现一个可以用的模板实现了一个模板&#xff0c;接下来我们将实现一个简单的纯跟踪控制&#xff0c;也就是沿着固定的路径运动&#xff0c;全局规划已经规划出路径点&#xff0c;基于该路径输出相应的控制速度 1. Pur…

Linux系列学习(三) - 进程和库文件

目录 引言&#xff1a; 学习&#xff1a; 基本命令补充&#xff1a; wc命令&#xff1a; more命令&#xff1a; less命令&#xff1a; cat ps命令&#xff1a; kill命令&#xff1a; bg命令&#xff1a; fg命令&#xff1a; 查看系统运行级别&#xff1a; 库文件&a…

unity UGUI系统梳理 - 常用可视化控件

作为一名合格的UI仔>.<&#xff0c;我发现很多UI很久没有使用了&#xff0c;所以我决定做一个UGUI系列博客重新梳理一下 1、Image 在没有放入图片下&#xff0c;image控件长这样 注意 我一般没交互需求的情况下都会把RaycastTarget给点掉&#xff0c;这个不单单是从提…

CAPL脚本DBLookup函数动态访问CAN 报文的属性

&#x1f345; 我是蚂蚁小兵&#xff0c;专注于车载诊断领域&#xff0c;尤其擅长于对CANoe工具的使用&#x1f345; 寻找组织 &#xff0c;答疑解惑&#xff0c;摸鱼聊天&#xff0c;博客源码&#xff0c;点击加入&#x1f449;【相亲相爱一家人】&#x1f345; 玩转CANoe&…

学习周报3.5

文章目录前言文献阅读摘要介绍方法总结相关性总结前言 本周阅读文献《Multi-step ahead probabilistic forecasting of multiple hydrological》&#xff0c;文献主要提出一种基于三维卷积神经网络、卷积最小门记忆神经网络和变分贝叶斯神经网络的混合深度学习模型&#xff08…

【博学谷学习记录】超强总结,用心分享|狂野大数据课程【Spark SQL函数定义】的总结分析

5.1 如何使用窗口函数 回顾: 窗口函数格式:分析函数 over(partition by xxx order by xxx [asc|desc] [rows between xxx and xxx])学习的相关分析函数有那些? 第一类: row_number() rank() dense_rank() ntile()第二类: 和聚合函数组合使用 sum() avg() max() min() count…

西电软件体系结构核心考点汇总(期末真题+核心考点)

文章目录前言一、历年真题二、核心考点汇总2.1 什么是软件体系架构?(软件体系结构的定义)2.2 架构风格优缺点2.3 质量属性2.4 质量评估前言 主要针对西安电子科技大学《软件体系结构》的核心考点进行汇总。 【期末期间总结资料如下】 针对西电计科院软件工程专业大三《软件体…

【QT】使用QML构建一个简易的计算器界面(三)

前面两篇对计算器界面的布局和显示以及实现功能做了相关优化&#xff0c;但是对输入显示那一块还没有具体的处理步骤&#xff0c;包括对输入表达式的合法性检查&#xff0c;显示框的多行历史显示等功能还需要添加&#xff0c;接下来将从这几个方面对这些功能进行添加。 1、对输…

概率论 1.3 古典概型与几何概型

1.3.1 排列与组合排列从n个不同元素任取r(r<n)个元素排成一列(考虑元素出现的先后次序)&#xff0c;称此为一个排列&#xff0c;此种排列的总数为n(n-1)....(n-r1)n!/(n-r)&#xff01;&#xff0c;若rn,则称为全排列&#xff0c;2.重复排列从n个不同元素中每次取出一个,放回…

GPIO输入和输出以及八种工作模式

一.GPIO的简介 GPIO &#xff08;general purpose input output&#xff09;是通用输入输出端口的简称&#xff0c;简单来说就是软件可控制的引脚&#xff0c;STM32芯片的GPIO引脚与外部传感器连接起来&#xff0c;从而实现与外部通讯、控制以及数据采集的功能。 1.引脚全是GP…

[2.1.1]进程管理——进程的概念、组成、特征

文章目录第二章 进程管理进程的概念、组成、特征&#xff08;一&#xff09;进程的概念&#xff08;二&#xff09;进程的组成——PCB&#xff08;三&#xff09;进程的组成——程序段、数据段补充&#xff1a;程序是如何运行的&#xff1f;&#xff08;四&#xff09;进程的特…