java数据结构与算法基础-----字符串------KMP算法

news2024/11/25 8:23:46
java数据结构与算法刷题目录(剑指Offer、LeetCode、ACM)-----主目录-----持续更新(进不去说明我没写完):https://blog.csdn.net/grd_java/article/details/123063846

文章目录

  • 一、概述
  • 二、KMP思想
  • 三、代码实现

一、概述

什么是KMP算法
  1. 我们拿LeetCode28题为例,暴力解法时间复杂度O(m*n), 而KMP算法时间复杂度O(n + m),空间复杂度O(m),所以,KMP算法是一个提升字符串匹配效率很好的一个算法。但是我们要注意一点,不同的环境和不同的编程语言,KMP的效率未必好过其它的算法,例如优化后的BM算法拥有比KMP更好的平均时间复杂度(注意是平均时间复杂度更好,算法本身理论上的时间复杂度,KMP更好)
  1. 暴力解法的效率
    在这里插入图片描述
  2. KMP的效率提升
    在这里插入图片描述
  1. 上面这道28题,仅仅说明,在这道题所规定的场景下,KMP确实发挥出了它应有的实力。但是在实际工作场景中,有时候会遇到种种不太适配的场景,这些时候KMP的表现也确实会受到多方面的影响,从而导致综合下来看,此时其它算法的表现更加优秀。

KMP算法很难,如果拿下,可以很好的提升你的编程思维,但是实际工作中也确实很少有场景会使用到它。不过,虽然完整的KMP算法不常用,但是如果单拿出它其中某一个步骤的思想,却往往能在很多不同的场景中,发挥奇效。

总结起来就是,很多问题,其实只需要使用KMP算法中的某一个步骤的思想就可以很好的解决并提高效率。但是如果你只记住了代码,每次使用KMP都必须把完整代码写出来使用的话,就会陷入杀鸡焉用牛刀的困境,造成不必要的时间和空间上的浪费。

KMP的简单概括,以及暴力解法的不足
  1. KMP算法:重复出现的,不要重复比较,跳过它。如何实现这个效果呢?只需要使用一个数组来记录重复出现的次数。
  1. 解决模式串在文本串是否出现过,如果出现过,获取最早出现的位置的经典算法
  2. 常用于在一个文本串S内查找一个模式串P出现的位置,此算法由Donald Knuth、Vaughan Pratt、James H.Morris三人于1977年联合发表,故取三人姓氏命名此算法为Knuth-Morris-Pratt,简称KMP算法
  3. 利用之前判断过的信息,通过一个next数组,保存模式串中前后最常公共子序列的长度,每次回溯时,通过next数组找到,前面匹配过的位置,省去大量计算时间
  1. 暴力匹配存在的问题:例如ABCDABEABCDABD 匹配 ABCDABD
  1. ABCDABEABCDABD从头匹配子串ABCDABD,可以发现最后一个字母D不匹配E,也就是说ABCDABE 这个串 肯定不匹配ABCDABD
  2. 但是在未知的情况下,我们无法像人脑一样,知道这个串匹配不了,从而向后移一位,从E开始继续观察是否匹配,比如:后面的EABCDABD继续匹配子串ABCDABD, 此时我们发现E不匹配,再次向后移一位进行ABCDABD匹配ABCDABD ,此时发现成功匹配了。但是计算机可不像我们人脑一样,可以进行这样的思考
  3. 所以暴力匹配中,我们不确定是否后移一位就匹配了,因此,只能一个个后移比较
  1. 暴力匹配哪里可以优化呢?
  1. 我们先看简单的匹配:ABCDABE和ABCDABD发现E和D不匹配,使用暴力解法,需要后移,将剩余的BCDABE再次匹配ABCDABD
  2. 此时,我们发现,又不匹配,如果我们想要匹配,最起码应该A开头吧,也就是说,再往后面移动,也没用,直到下一个A开头的
  3. 但是计算机不是人,使用暴力解法,还是会按部就班的继续匹配。例如接下来它会用CDABE匹配 ABCDABD,然后又发现不匹配,再次进行DABE匹配 ABCDABD… 以此类推
  4. 那么我们可以发现,BCD这三个开头,已经确定不匹配,没必要重复匹配,应该跳过,直接匹配下一个A。例如:直接跳到ABE匹配 ABCDABD

二、KMP思想

各位注意KMP解决的最大的问题是:假设我们匹配时,发现匹配到某个字符匹配不下去了,此时我们究竟是将已匹配的字符全部直接跳过,还是说只能跳过一部分。

  1. 例如:ABABABD匹配ABABD,从头匹配时,我们发现红色的位置是匹配不上的
  2. 如果我们全部跳过,就只剩下BD匹配ABABD,显然不正确
  3. 此时我们应该跳过字符串ABABABD中前面两个标红的AB,进行剩下的ABABD匹配ABABD。此时成功匹配。
KMP的解决思路:例如ABCDABEABCDABD 匹配 ABCDABD
  1. 对匹配串进行处理。我们先对ABCDABD这个子串做文章,判断匹配串某个位置不匹配时,应该怎么跳过,最终存储到next数组中。
  1. ABCDABE匹配ABCDABD,如果到D不匹配,应该直接变成ABE匹配ABCDABD,因为我们要找到下一个AB开头的
  2. 再比如ABCDEFGTTTTT匹配ABCDABD,如果第一次到E不匹配,应该直接变成EFGTTTTT匹配ABCDEFG,因为前面的ABCD全部不匹配,直接跳过
  3. 总结一下,可以发现,ABCDABD这个子串,出现两次AB,那么如果一直匹配到ABCDABD才发现不对,就应该直接跳到后面的AB继续匹配
  4. 而如果没有匹配到出现重复字符,比如匹配到ABCD就发现不对了,那么就应该直接跳过ABCD
  1. Next数组,如何记录,如何生成数组:是否有重复缀,应该用一个数组记录。比如ABCDABD对应数组应该是[0,0,0,0,1,2,0],代表A重复,长度1,AB重复,长度为2. 这是怎么算出来的呢?-----用前缀,后缀共同匹配法,就是从左到右,依次把前几个字母所组成的字符串列出来,然后再将前缀和后缀列出来,看看前后缀共同拥有的元素长度
  1. A的前后缀都是空集,前后缀共有0
  2. AB前缀A,后缀B,共有0
  3. ABC前缀A,AB。后缀BC,C,共有0
  4. ABCD前缀A,AB,ABC,后缀BCD,CD,D,共有0
  5. ABCDA前缀A,AB,ABC,ABCD,后缀BCDA,CDA,DA,A,共有A,长度1
  6. ABCDAB前缀A,AB,ABC,ABCD,ABCDA,后缀BCDAB,CDAB,DAB,AB,B,共有AB,长度2
  7. ABCDABD前缀A,AB,ABC,ABCD,ABCDA,ABCDAB,ABCDABD后缀BCDABD,CDABD,DABD,ABD,BD,共有0
  1. 如何利用数组,跳过该跳过的:ABCDABCABCDABD 匹配ABCDABD,前面我们已经得出Next数组为:[0,0,0,0,1,2,0]
  1. ABCDABCABCDABD 匹配ABCDABD, C不匹配D,对应Next数组[0,0,0,0,1,2,0],发现是0. 也就是说,当前匹配串ABCDABD,匹配到D后不匹配了,然后Next数组记录的ABCDABD的前后缀重复缀个数为0.
  2. 这就代表着,前面已经匹配的这7个字符,没有重和部分,无需重复匹配。直接全部跳过。跳过公式为:已匹配成功字符个数-匹配不成功字符对应Next数组值。也就是7-0 = 7.
  3. 所以下一次直接跳过7个ABCDABCABCDABD(红色的为跳过的),下一次直接匹配后面的ABCDABD。也就是ABCDABD匹配ABCDABD。此时匹配成功
另一个例子

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

三、代码实现

大家可以简单浏览一下下面这个,如果不好理解,可以看再下面的多注释版本
     public int strStr(String haystack, String needle) {
        int n = haystack.length(),m = needle.length();//为了更快的速度
        if(m==0) return 0;
        //1、kmp数组
        int[] kmpArr = new int[m];
        kmpArr[0] = 0;
        for (int i = 1,j = 0; i < m; i++) {
            //"ABCDABD"
            //"i***i"两个i对应的匹配成功,那么匹配成功长度+1,j++,长度为1。说明如果匹配到ABCDA,那么下次可以把ABCD跳过,末尾的A不可以跳过
            //" i***i"然后两个i位置又成功,j++,长度为2,也就是说匹配到了ABCDAB,下次ABCD可以跳过,末尾的AB不可以,因为重复了
            //最后,j = 2,i = 6,C不匹配D
            //j = kmp[j-1] 看看后移后的缀匹配程度,一直到匹配成功,或者j = 0,从开头重新匹配
            //这个while循环,主要负责,如果当前不匹配了,是全跳过,还是只能跳过部分
            while(j>0 && needle.charAt(i)!=needle.charAt(j)) j = kmpArr[j-1];
            if(needle.charAt(i)==needle.charAt(j)) j++;//匹配成功,j长度+1
            kmpArr[i] = j;
        }
        //2、用kmp数组
        for (int i = 0,j = 0; i < n; i++) {
            //这里和上面代码一样,只不过原来是needle自己比,现在是haystack和needle比较
            while(j>0 && haystack.charAt(i)!=needle.charAt(j)) j = kmpArr[j-1];
            if(haystack.charAt(i)==needle.charAt(j)) j++;//匹配成功,j长度+1
            if(j==m) return i-j + 1;//匹配成功,返回子串起始下标
        }
        return -1;//没成功
    }
多注释版本
  1. 首先,我们需要一个数组,作为NEXT数组,表示匹配不成功后,应该跳过几个字符,进行下次匹配
    public int strStr(String haystack, String needle) {
        //先搞出kmpArr数组
        int[] kmpArr = kmpNext(needle);
        //用数组跳过没必要重复比较的
        return kmpSearch(haystack,needle,kmpArr);
    }
  1. NEXT数组:这里使用的方法是,记录当前位置匹配成功后,如果要跳,应该跳到哪里。这样的含义是,如果发现匹配到某字符匹配失败,那么证明前面都是匹配成功的,那么就访问失败字符的前一个字符的Next数组值,从而得知应该跳到哪里重新尝试匹配。例如:ABCDABD [0,0,0,0,1,2,0].C匹配失败,但是前面B匹配成功了,那么看看B成功,要跳,应该跳什么位置,我们发现Next数组中记录的是0,所以下次只能从头匹配。
  1. 首先用一个for循环,来处理从0位置开始,依次的每个子串,例如A,AB,ABC,ABCA,循环变量i代表每个子串的末尾。
  2. 然后通过while循环,来处理:如果当前的,前后缀不匹配,可以跳过几个字符。
  1. 如果匹配成功则很简单,例如:ABCDABD [0,0,0,0,1,2,0]。假设标红的下标为 i,标黄的下标为 j。我们发现标红的这个A,和开头标黄的A成功的进行了前后缀匹配。

那么我们可以让j++。它有两个作用

  1. 指向前缀的位置,也就是它在j++前,指向的就是开头的A。那么现在这个A匹配成功了,我们下个位置需要继续从A匹配吗?显然不用。下次直接尝试匹配B就好了。所以进行j++
  2. 代表当前,连续的情况下,成功匹配了多少。很明显我们现在仅仅连续匹配成功了这个标红的A。所以长度为1.
  1. 然后:ABCDABD [0,0,0,0,1,2,0]。我们发现标红的B,和开头的B成功的进行了前后缀匹配。

此时我们就可以再次进行j++;

  1. 刚刚 j 已经 =1 了。因为前面A匹配成功了,现在B也匹配成功了,那么ABCDA这个子串的前后缀,A与A匹配成功,现在末尾添加一个B,ABCDAB这个子串B与B也匹配成功了,那么代表AB与AB匹配成功了。所以下次应该匹配C
  2. A已经匹配成,然后紧跟着,B也匹配成功,所以连续情况下,连续匹配了AB,所以长度为2.
  1. 再然后:ABCDABD [0,0,0,0,1,2,0]。我们发现标红的D和标黄的C,没有成功进行匹配

也就是前缀ABC和后缀ABD没有成功匹配,我们知道KMP算法思想就是跳过没必要重复比较的

  1. 此时如果继续比较字符更多的前后缀已经没有意义了,因为ABC和ABD已经不匹配了,接下来的ABCD,和DABD就更不行了
  2. 所以我们此时就需要考虑,是全跳呢?还是只跳一部分,然后看看剩余部分是否前后缀还能匹配
  3. 因为前面的子串如果匹配失败,应该跳到哪个位置,都已经记录在NEXT数组中了,所以直接从next数组获取即可。
  4. 既然ABC和ABD匹配失败,但是我们的NEXT数组记录的是,字符匹配成功后,应该跳跃的位置,D和C匹配失败了。但是前面的都匹配成功了,所以我们访问NEXT[ j - 1],也就是B的位置,我们想看看B匹配成功,但是我们要跳跃,应该跳到哪里去。
  5. 我们发现AB这个前缀,压根没有公共前后缀,没办法,只能全跳。也就是回到0位置继续。
  1. 再举个例子,AACDAAA[0,1,0,0,1,2,2]

AAC和AAA没有匹配成功

  1. 此时访问[0,1,0,0,1,2,2]. NEXT[ j-1 ] = 1,我们发现前缀AA如果匹配成功,下次只需要跳到1位置继续匹配即可。同时j–。
  2. 此时再次匹配,我们发现AACDAAA匹配成功,则j++,代表公共前后缀长度为2.
  1. 最后通过一个if语句来处理,是否前后缀匹配,如果匹配,记录成功匹配的字符个数。
    //第一步:获取到一个字符串(子串) 的部分匹配值表,KMP数组
    public static  int[] kmpNext(String dest) {
        //ABCDABD [0,0,0,0,1,2,0]
        int[] kmpArr = new int[dest.length()];//kmp数组,记录前后缀共同匹配长度
        kmpArr[0]=0;//单个字符,例如A的前后缀都是空集,前后缀共有0
        //i代表后缀下标,j代表前缀下标和数组对应下标
        for (int i = 1,j = 0; i < dest.length(); i++) {
            //i = 0 代表A字符,前后缀都是空串
            //i = 3 代表ABCD的D,j = 0,最前面的A
            //不相等,依然是0
            //i = 4 代表ABCDA,的A。j = 0代表最前面的A
            //前后缀相等了,但此时j = 0,说明前面没有前后缀匹配的,那么直接记录本次
            //j++ = 1 , kmpArr[4] = 1
            //i = 5 代表ABCDAB,代表后缀B,j = 1代表前缀B
            //相等了,此时j>0,并且相等,那么直接j++即可
            //j = 2,kmpArr[5] = 2
            //i = 6 代表ABCDABD,代表后缀D,j = 2代表前缀C(ABC)
            //此时j>0,并且前后不相等,那么后缀D和C不匹配,需要尝试向前匹配
            //剩下他能匹配的,也就是,A和AB,对应j是0和1
            //j = kmpArr[j-1],向前移动前缀,kmpArr[1] = 0
            //j = 0,前面没有了,判断前后缀现在是否相等
            //D 不等于 A 因此D这个后缀,没的匹配
            while(j>0&&dest.charAt(j)!=dest.charAt(i)){//如果本次前后缀不匹配
                j=kmpArr[j-1];//看看可以跳到哪里,然后继续比较是否前后缀匹配
            }
            if(dest.charAt(i)==dest.charAt(j)){//如果匹配,长度+1,
                j++;
            }
            //j 就是 长度
            kmpArr[i] = j;
        }
        return kmpArr;
    }
  1. KMP搜索:和NEXT数组逻辑几乎一样,如果遇到匹配不成功的字符,那么就考虑NEXT数组中,前一个位置匹配成功后,应该跳到哪个位置重新匹配。
    /**第二步
     * kmp搜索
     *  依然进行匹配
     *  和上面一样,j就是匹配成功的前后缀共同长度
     *  
     */
    public static int kmpSearch(String str1, String str2, int[] kmpArr) {

        //遍历
        for(int i = 0, j = 0; i < str1.length(); i++) {
            //匹配不成功就跳过
            //需要处理 str1.charAt(i) != str2.charAt(j), 去调整j的大小
            //KMP算法核心点, 可以验证...
            //String str1 = "BBC ABCDAB ABCDABCDABDE";
            //    String str2 = "ABCDABD";
            //匹配表next=[0, 0, 0, 0, 1, 2, 0]
            //当i = 10,j = 6时,前面ABCDAB都匹配成功,但是现在空格比较D,不相等,此时说明不是子串
            //此时进入循环,j = 2,比较空格和C,不相等,再进入循环
            //j = 1,比较空格和B,不相等,再进入循环
            //j = 0,条件不满足,退出循环
            //下一次,将从空格后面继续匹配,避免前面比较过的,重复比较
            while( j > 0 && str1.charAt(i) != str2.charAt(j)) {
                j = kmpArr[j-1];
            }
            //匹配成功就继续
            if(str1.charAt(i) == str2.charAt(j)) {
                j++;
            }
            //和上面建立kmp数组一样,获得j,匹配成功的长度
            if(j == str2.length()) {//如果相等,匹配完全成功
                return i - j + 1;//返回子串起始下标
            }
            //否则,匹配不成功
        }
        return  -1;
    }

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

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

相关文章

43.1k star, 免费开源的 markdown 编辑器

简介 项目名&#xff1a; MarkText-- 简单而优雅的开源 Markdown 编辑器 Github 开源地址&#xff1a; https://github.com/marktext/marktext 官网&#xff1a; https://www.marktext.cc/ 支持平台&#xff1a; Linux, macOS 以及 Windows。 操作界面&#xff1a; 在操作界…

猫头虎分享已解决Bug || DNS解析问题(DNS Resolution Issue):DNSLookupFailure, DNSResolveError

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

猫头虎分享已解决Bug || Error: Minified React Error #130

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

Shell 学习笔记(一)-Shell脚本编程简介

一 什么是shell&#xff1f; shell是一个用 C 语言编写的程序&#xff0c;它是用户使用 Linux 的桥梁。Shell 既是一种命令语言&#xff0c;又是一种程序设计语言。 Shell 是指一种应用程序&#xff0c;这个应用程序提供了一个界面&#xff0c;用户通过这个界面访问操作系统内…

Go教程-什么是编程?

什么是编程&#xff0c;这是个有趣的话题。 编程是什么 编程&#xff0c;字面意思即编写程序&#xff0c;即通过既定的关键字&#xff0c;来描述你的想法&#xff0c;并让计算机的各个部件按照你的想法来做事。 这里计算机的各个部件通常来说&#xff0c;指的是CPU和IO设备。…

numpy 查漏补缺

1. iterating 2. 3. 4. 5. 6. 7. 8. 9.

Python一些可能用的到的函数系列124 GlobalFunc

说明 GlobalFunc是算网的下一代核心数据处理基础。 算网是一个分布式网络&#xff0c;为了能够实现真的分布式计算&#xff08;加快大规模任务执行效率&#xff09;&#xff0c;以及能够在很长的时间内维护不同版本的计算方法&#xff0c;需要这样一个对象/服务来支撑。Globa…

Apache httpd 换行解析漏洞复现(CVE-2017-15715)

Web页面&#xff1a; 新建一个一句话木马&#xff1a; 0.php <?php system($_GET[0]); ?> 上传木马&#xff0c; burpsuite 抓包。 直接上传是回显 bad file。 我们查看数据包的二进制内容&#xff08;hex&#xff09;&#xff0c;内容是以16进制显示的&#xff0c;…

每日OJ题_递归①_力扣面试题 08.06. 汉诺塔问题

目录 递归算法原理 力扣面试题 08.06. 汉诺塔问题 解析代码 递归算法原理 递归算法个人经验&#xff1a;给定一个任务&#xff0c;相信递归函数一定能解决这个任务&#xff0c;根据任务所需的东西&#xff0c;给出函数参数&#xff0c;然后实现函数内容&#xff0c;最后找出…

略谈新质生产力与数字经济、数据、数据要素

国家发展和改革委员会宏观经济杂志社中宏经济发展研究中心以研究报告的形式刊载了高泽龙的文章&#xff0c;“新质生产力与数字经济、数据、数据要素”&#xff0c;同时&#xff0c;这篇文章在中宏网首页头部重点位置给予推荐报道。 新质生产力与数字经济、数据、数据要素https…

如何书写一个标准JavaBean

前言&#xff1a;在学习Java类的三大特征之一的封装的时候&#xff0c;对封装的数据Java有着自己已经规定好的书写格式&#xff0c;我们需要按照对应的格式进行书写。 我们大致了解一下要学习的内容&#xff1a; 1.封装的概念 如图&#xff08;看不懂没关系&#xff0c;下面会…

Imgui(3) | 基于 imgui-SFML 的 mnist 数据集查看器

Imgui(3) | 基于 imgui-SFML 的 mnist 数据集查看器 文章目录 Imgui(3) | 基于 imgui-SFML 的 mnist 数据集查看器0. 介绍1. 处理 mnist 数据集2. 显示单张图像和label2.1 显示单张图像2.2 点选列表后更新显示的图像2.3 显示 label2.4 使用完整的列表 总结 0. 介绍 把mnist数据…

零基础学编程怎么入手,中文编程工具构件箱之多页面板构件用法教程,系统化的编程视频教程上线

零基础学编程怎么入手&#xff0c;中文编程工具构件箱之多页面板构件用法教程&#xff0c;系统化的编程视频教程上线 一、前言 今天给大家分享的中文编程开发语言工具资料如下&#xff1a; 编程入门视频教程链接 http://​ https://edu.csdn.net/course/detail/39036 ​ …

SpringCloud-Hystrix:服务熔断与服务降级

8. Hystrix&#xff1a;服务熔断 分布式系统面临的问题 复杂分布式体系结构中的应用程序有数十个依赖关系&#xff0c;每个依赖关系在某些时候将不可避免失败&#xff01; 8.1 服务雪崩 多个微服务之间调用的时候&#xff0c;假设微服务A调用微服务B和微服务C&#xff0c;微服…

洛谷_P1116 车厢重组_python写法

这道题看起来很高级其实就是冒泡排序执行的次数。 那对于python而言的话&#xff0c;这道题最大的难点在于如何实现数据输入既可以是以空格隔开的数据又可以是换行隔开的数据&#xff0c;那代码里面有了十分详细的解释。 n int(input()) l [] while len(l) < n: # 如果没…

CSS之画常见的图形

1.三角形 .shape {width: 0;height: 0;border-top: 100px solid rgba(0, 0, 0, 0);border-right: 100px solid rgba(0, 0, 0, 0);border-bottom: 100px solid blue;border-left: 100px solid rgba(0, 0, 0, 0);}使用border属性实现。宽高设置为0&#xff0c;border里其中一个方…

lv15 平台总线驱动开发——ID匹配 3

一、ID匹配之框架代码 id匹配&#xff08;可想象成八字匹配&#xff09;&#xff1a;一个驱动可以对应多个设备 ------优先级次低&#xff08;上一章名称匹配只能1对1&#xff09; 注意事项&#xff1a; device模块中&#xff0c;id的name成员必须与struct platform_device中…

从零开始学howtoheap:解题西湖论剑Storm_note

how2heap是由shellphish团队制作的堆利用教程&#xff0c;介绍了多种堆利用技术&#xff0c;后续系列实验我们就通过这个教程来学习。环境可参见从零开始配置pwn环境&#xff1a;从零开始配置pwn环境&#xff1a;从零开始配置pwn环境&#xff1a;优化pwn虚拟机配置支持libc等指…

统一功能处理----拦截器

拦截器 拦截器是Spring框架提供的核心功能之一&#xff0c;主要用来拦截用户的请求&#xff0c;在指定方法前后&#xff0c;根据业务需要执行预先设定的代码。 拦截器就像小区门口的保安一样&#xff0c;当有人&#xff08;外部请求&#xff09;想要进入小区&#xff0c;保安…

HTML5 Canvas与JavaScript携手绘制动态星空背景

目录 一、程序代码 二、代码原理 三、运行效果 一、程序代码 <!DOCTYPE html> <html> <head> <meta charset"UTF-8"> <title>星空背景</title> </head> <body style"overflow-x:hidden;"><canvas …