从KMP算法到AC自动机

2023-11-06

本文档内容参考以下文档整理而成:

kpm算法:http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html

AC自动机:https://blog.csdn.net/bestsort/article/details/82947639

AC自动机应用:https://www.jb51.net/article/128711.htm

 

目录

 

字符串匹配的KMP算法

AC自动机

AC自动机应用

1、AC自动机树和fail指针的构造

2、AC自动机的运行过程

3、构造AC自动机的方法与原理

3.1构造的基本方法

3.2通过一个例子来理解构造AC自动机的原理

4.AC自动机的java代码实现


字符串匹配的KMP算法

字符串匹配是计算机的基本任务之一。

举例来说,有一个字符串"BBC ABCDAB ABCDABCDABDE",我想知道,里面是否包含另一个字符串"ABCDABD"?

许多算法可以完成这个任务,Knuth-Morris-Pratt算法(简称KMP)是最常用的之一。它以三个发明者命名,起头的那个K就是著名科学家Donald Knuth。

这种算法不太容易理解,网上有很多解释,但读起来都很费劲。下面,我用阮一峰的一篇文章,来比较好懂的解释一下KMP算法。

1.

首先,字符串"BBC ABCDAB ABCDABCDABDE"的第一个字符与搜索词"ABCDABD"的第一个字符,进行比较。因为B与A不匹配,所以搜索词后移一位。

2.

因为B与A不匹配,搜索词再往后移。

 

3.

就这样,直到字符串有一个字符,与搜索词的第一个字符相同为止。

4.

接着比较字符串和搜索词的下一个字符,还是相同。

5.

直到字符串有一个字符,与搜索词对应的字符不相同为止。

6.

这时,最自然的反应是,将搜索词整个后移一位,再从头逐个比较。这样做虽然可行,但是效率很差,因为你要把"搜索位置"移到已经比较过的位置,重比一遍。

7.

一个基本事实是,当空格与D不匹配时,你其实知道前面六个字符是"ABCDAB"。KMP算法的想法是,设法利用这个已知信息,不要把"搜索位置"移回已经比较过的位置,继续把它向后移,这样就提高了效率。

8.

怎么做到这一点呢?可以针对搜索词,算出一张《部分匹配表》(Partial Match Table)。这张表是如何产生的,后面再介绍,这里只要会用就可以了。

9.

已知空格与D不匹配时,前面六个字符"ABCDAB"是匹配的。查表可知,最后一个匹配字符B对应的"部分匹配值"为2,因此按照下面的公式算出向后移动的位数:

移动位数 = 已匹配的字符数 - 对应的部分匹配值

因为 6 - 2 等于4,所以将搜索词向后移动4位。

10.

因为空格与C不匹配,搜索词还要继续往后移。这时,已匹配的字符数为2("AB"),对应的"部分匹配值"为0。所以,移动位数 = 2 - 0,结果为 2,于是将搜索词向后移2位。

11.

因为空格与A不匹配,继续后移一位。

12.

逐位比较,直到发现C与D不匹配。于是,移动位数 = 6 - 2,继续将搜索词向后移动4位。

13.

逐位比较,直到搜索词的最后一位,发现完全匹配,于是搜索完成。如果还要继续搜索(即找出全部匹配),移动位数 = 7 - 0,再将搜索词向后移动7位,这里就不再重复了。

14.

下面介绍《部分匹配表》是如何产生的。

首先,要了解两个概念:"前缀"和"后缀"。 "前缀"指除了最后一个字符以外,一个字符串的全部头部组合;"后缀"指除了第一个字符以外,一个字符串的全部尾部组合。

15.

"部分匹配值"就是"前缀"和"后缀"的最长的共有元素的长度。以"ABCDABD"为例,

"A"的前缀和后缀都为空集,共有元素的长度为0;
"AB"的前缀为\[A\],后缀为\[B\],共有元素的长度为0;

"ABC"的前缀为\[A, AB\],后缀为\[BC, C\],共有元素的长度0;

"ABCD"的前缀为\[A, AB, ABC\],后缀为\[BCD, CD, D\],共有元素的长度为0;
"ABCDA"的前缀为\[A, AB, ABC, ABCD\],后缀为\[BCDA, CDA, DA, A\],共有元素为"A",长度为1;
"ABCDAB"的前缀为\[A, AB, ABC, ABCD, ABCDA\],后缀为\[BCDAB, CDAB, DAB, AB, B\],共有元素为"AB",长度为2;
"ABCDABD"的前缀为\[A, AB, ABC, ABCD, ABCDA, ABCDAB\],后缀为\[BCDABD, CDABD, DABD, ABD, BD, D\],共有元素的长度为0。

16.

"部分匹配"的实质是,有时候,字符串头部和尾部会有重复。比如,"ABCDAB"之中有两个"AB",那么它的"部分匹配值"就是2("AB"的长度)。搜索词移动的时候,第一个"AB"向后移动4位(字符串长度-部分匹配值),就可以来到第二个"AB"的位置。

 

AC自动机

要学AC自动机需要自备两个前置技能:KMP和trie树
其中,KMP是用于一对一的字符串匹配,而trie虽然能用于多模式匹配,但是每次匹配失败都需要进行回溯,如果模式串很长的话会很浪费时间,所以AC自动机应运而生,如同Manacher一样,AC自动机利用某些操作阻止了模式串匹配阶段的回溯,将时间复杂度优化到了 O ( n ) O(n) O(n)(n)为文本串长度

下面开始用图学习ac自动机吧
首先给定模式串`"ash","shex","bcd","sha"`,然后我们根据模式串建立如下trie树:

然后我们再了解下一步:  
ac自动机,就是在tire树的基础上,增加一个fail指针,如果当前点匹配失败,则将指针转移到fail指针指向的地方,这样就不用回溯,而可以路匹配下去了.(当前模式串后缀和fail指针指向的模式串部分前缀相同,如`abce`和`bcd`,我们找到`c`发现下一个要找的不是`e`,就跳到`bcd`中的`c`处,看看此处的下一个字符(`d`)是不是应该找的那一个)

一般,fail指针的构建都是用bfs实现的  
首先每个模式串的首字母肯定是指向根节点的(一个字母你瞎指什么指,指了也是头字母有什么用嘛)  

现在第一层bfs遍历完了,开始第二层  
(根节点为第0层)第二层`a`的子节点为`s`,但是我们还是要从`a-z`遍历,如果不存在这个子节点我们就让他指向根节点(如下图红色的`a`)  

当我们遍历到`s`的时候,由于存在`s`这个节点,我们就让他的fail指针指向他父亲节点(`a`)的fail指针指向的那个节点(`根`)的具有相同字母的子节点(`第一层的s`),也就是这样  

按照相同规律构建第二层后,到了第三层的`h`点,还是按照上面的规则,我们找到`h`的父亲节点(`s`)fail指针指向的那个位置(`第一层的s`)然后指向它所指向的相同字母`根->s->h`的这个链的`h`节点,如下图  

完全构造好后的树  

然后匹配就很简单了,这里以`ashe`为例  
我们先用`ash`匹配,到`h`了发现:诶这里`ash`是一个完整的模式串,好的`ans++`,然后找下一个`e`,可是`ash`后面没字母了啊,我们就跳到`h`fail指针指向的那个`h`继续找,还是没有?再跳,结果当前的`h`指向的是根节点,又从根节点找,然而还是没有找到`e`,程序END

过程如下图

 

AC自动机应用

1、AC自动机树和fail指针的构造

我们现在来看一个用目标字符串集合{abd,abdk,abchijn,chnit,ijabdf,ijaij}构造出来的AC自动机

上图是一个构建好的AC自动机,其中根结点不存储任何字符,根结点的fail指针为null。虚线表示该结点的fail指针的指向,所有表示字符串的最后一个字符的结点外部都用红圈表示,我们称该结点为这个字符串的终结结点。每个结点实际上都有fail指针,但为了表示方便,本文约定一个原则,即所有指向根结点的fail虚线都未画出。

从上图中的AC自动机,我们可以看出一个重要的性质:每个结点的fail指针表示由根结点到该结点所组成的字符序列的所有后缀和整个目标字符串集合(也就是整个Trie树)中的所有前缀两者中最长公共的部分。

比如图中,由根结点到目标字符串“ijabdf”中的‘d'组成的字符序列“ijabd”的所有后缀在整个目标字符串集{abd,abdk,abchijn,chnit,ijabdf,ijaij}的所有前缀中最长公共的部分就是abd,而图中d结点(字符串“ijabdf”中的这个d)的fail正是指向了字符序列abd的最后一个字符。

2、AC自动机的运行过程

1)表示当前结点的指针指向AC自动机的根结点,即curr=root

2)从文本串中读取(下)一个字符

3)从当前结点的所有孩子结点中寻找与该字符匹配的结点,

若成功:判断当前结点以及当前结点fail指向的结点是否表示一个字符串的结束,若是,则将文本串中索引起点记录在对应字符串保存结果集合中(索引起点=当前索引-字符串长度+1)。curr指向该孩子结点,继续执行第2步

若失败:执行第4步。

4)若fail==null(说明目标字符串中没有任何字符串是输入字符串的前缀,相当于重启状态机)curr=root,执行步骤2,

否则,将当前结点的指针指向fail结点,执行步骤3)

现在,我们来一个具体的例子加深理解,初始时当前结点为root结点,我们现在假设文本串text=“abchnijabdfk”。

图中的实曲线表示了整个搜索过程中的当前结点指针的转移过程,结点旁的文字表示了当前结点下读取的文本串字符。比如初始时,当前指针指向根结点时,输入字符‘a',则当前指针指向结点a,此时再输入字符‘b',自动机状态转移到结点b,……,以此类推。图中AC自动机的最后状态只是恰好回到根结点。

需要说明的是,当指针位于结点b(图中曲线经过了两次b,这里指第二次的b,即目标字符串“ijabdf”中的b),这时读取文本串字符下标为9的字符(即‘d')时,由于b的所有孩子结点(这里恰好只有一个孩子结点)中存在能够匹配输入字符d的结点,那么当前结点指针就指向了结点d,而此时该结点d的fail指针指向的结点又恰好表示了字符串“abc”的终结结点(用红圈表示),所以我们找到了目标字符串“abc”一次。这个过程我们在图中用虚线表示,但状态没有转移到“abd”中的d结点。

在输入完所有文本串字符后,我们在文本串中找到了目标字符串集合中的abd一次,位于文本串中下标为7的位置;目标字符串ijabdf一次,位于文本串中下标为5的位置。

3、构造AC自动机的方法与原理

3.1构造的基本方法

首先我们将所有的目标字符串插入到Trie树中,然后通过广度优先遍历为每个结点的所有孩子节点的fail指针找到正确的指向。

确定fail指针指向的问题和KMP算法中构造next数组的方式如出一辙。具体方法如下

1)将根结点的所有孩子结点的fail指向根结点,然后将根结点的所有孩子结点依次入列。

2)若队列不为空:

2.1)出列,我们将出列的结点记为curr,failTo表示curr的fail指向的结点,即failTo=curr.fail

2.2)a.判断curr.child[i]==failTo.child[i]是否成立,

成立:curr.child[i].fail=failTo.child[i],

不成立:判断failTo==null是否成立

成立:curr.child[i].fail==root

不成立:执行failTo=failTo.fail,继续执行2.2)

b.curr.child[i]入列,再次执行再次执行步骤2)

若队列为空:结束

3.2通过一个例子来理解构造AC自动机的原理

每个结点fail指向的解决顺序是按照广度优先遍历的顺序完成的,或者说层序遍历的顺序进行的,也就是说我们是在解决当前结点的孩子结点fail的指向时,当前结点的fail指针一定已指向了正确的位置。

为了说明问题,我们再次强调“每个结点的fail指针表示:由根结点到该结点所组成的字符序列的所有后缀和整个目标字符串集合(也就是整个Trie树)中的所有前缀两者中最长公共的部分”。

以上图所示为例,我们要解决结点x1的某个孩子结点y的fail的指向问题。已知x1.fail指向x2,依据x1结点的fail指针的含义,我们可知红色实线椭圆框内的字符序列必然相等,且表示了最长公共部分。依据y.fail的含义,如果x2的某个孩子结点和结点y表示的字符相等,那么y.fail就该指向它。

如果x2的孩子结点中不存在结点y表示的字符,这个时候该怎么办?由于x2.fail指向x3,根据x2.fail的含义,我们可知绿色方框内的字符序列必然相等。显然,如果x3的某个孩子结点和结点y表示的字符相等,那么y.fail就该指向它。

如果x3的孩子结点中不存在结点y表示的字符,我们可以依次重复这个步骤,直到xi结点的fail指向null,这时说明我们已经到了最顶层的根结点,这时,我们只需要让y.fail=root即可。

构造的过程的核心本质就是,已知当前结点的最长公共前缀的前提下,去确定孩子结点的最长公共前缀。这完全可以类比于KMP算法的next数组的求解过程。

3.2.1确定图中h结点fail指向的过程

现在我们假设我们要确定图中结点c的孩子结点h的fail指向。图中每个结点都应该有表示fail的虚线,但为了表示方便,按照本文约定的原则,所有指向根结点的fail虚线均未画出。

左图表示h.fail确定之前, 右图表示h.fail确定之后

左图中,蓝色实线框住的结点的fail都已确定。现在我们应该怎样找到h.fail的正确指向?由于且结点c的fail已知(c结点为h结点的父结点),且指向了Trie树中所有前缀与字符序列‘a'‘b'‘c'的所有后缀(“bc”和“c”)的最长公共部分。现在我们要解决的问题是目标字符串集合的所有前缀中与字符序列‘a'‘b'‘c'‘h'的所有后缀的最长公共部分。显然c.fail指向的结点的孩子结点中存在结点h,那么h.fail就应该指向c.fail的孩子结点h,所以右图表示了h.fail确定后的情况。

3.2.2确定图中i.fail指向的过程

左图表示i.fail确定之前, 右图表示i.fail确定之后

确定i.fail的指向时,显然h.fail(h指图中i的父结点的那个h)已指向了正确的位置。也就是说我们现在知道了目标字符串集合所有前缀中与字符序列‘a'‘b'‘c'‘h'的所有后缀在Trie树中的最长前缀是‘c'‘h'。显然从图中可知h.fail的孩子结点是没有i结点(这里h.fail只有一个孩子结点n)。字符序列‘c'‘h'的所有后缀在Trie树中的最长前缀可由h.fail的fail得到,而h.fail的fail指向root(依据本博客中画图的原则,这条fail虚线并未画出),root的孩子结点中存在表示字符i的结点,所以结果如右图所示。

在知道i.fail的情况下,大家可以尝试在纸上画出j.fail的指向,以加深AC自动机构造过程的理解。

4.AC自动机的java代码实现

package datastruct;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
public class AhoCorasickAutomation {
    /*本示例中的AC自动机只处理英文类型的字符串,所以数组的长度是128*/
    private static final int ASCII = 128;
    /*AC自动机的根结点,根结点不存储任何字符信息*/
    private Node root;
    /*待查找的目标字符串集合*/
    private List<String> target;
    /*表示在文本字符串中查找的结果,key表示目标字符串, value表示目标字符串在文本串出现的位置*/
    private HashMap<String, List<Integer>> result;
    /*内部静态类,用于表示AC自动机的每个结点,在每个结点中我们并没有存储该结点对应的字符*/
    private static class Node{
        /*如果该结点是一个终点,即,从根结点到此结点表示了一个目标字符串,则str != null, 且str就表示该字符串*/
        String str;
        /*ASCII == 128, 所以这里相当于128叉树*/
        Node[] table = new Node[ASCII];
        /*当前结点的孩子结点不能匹配文本串中的某个字符时,下一个应该查找的结点*/
        Node fail;
        public Boolean isWord(){
            return str != null;
        }
    }
    /*target表示待查找的目标字符串集合*/
    public AhoCorasickAutomation(List<String> target){
        root = new Node();
        this.target = target;
        buildTrieTree();
        build_AC_FromTrie();
    }
    /*由目标字符串构建Trie树*/
    private void buildTrieTree(){
        for (String targetStr : target){
            Node curr = root;
            for (int i = 0; i < targetStr.length(); i++){
                char ch = targetStr.charAt(i);
                if(curr.table[ch] == null){
                    curr.table[ch] = new Node();
                }
                curr = curr.table[ch];
            }
            /*将每个目标字符串的最后一个字符对应的结点变成终点*/
            curr.str = targetStr;
        }
    }
    /*由Trie树构建AC自动机,本质是一个自动机,相当于构建KMP算法的next数组*/
    private void build_AC_FromTrie(){
        /*广度优先遍历所使用的队列*/
        LinkedList<Node> queue = new LinkedList<Node>();
        /*单独处理根结点的所有孩子结点*/
        for (Node x : root.table){
            if(x != null){
                /*根结点的所有孩子结点的fail都指向根结点*/
                x.fail = root;
                queue.addLast(x);
                /*所有根结点的孩子结点入列*/
            }
        }
        while(!queue.isEmpty()){
            /*确定出列结点的所有孩子结点的fail的指向*/
            Node p = queue.removeFirst();
            for (int i = 0; i < p.table.length; i++){
                if(p.table[i] != null){
                    /*孩子结点入列*/
                    queue.addLast(p.table[i]);
                    /*从p.fail开始找起*/
                    Node failTo = p.fail;
                    while(true){
                        /*说明找到了根结点还没有找到*/
                        if(failTo == null){
                            p.table[i].fail = root;
                            break;
                        }
                        /*说明有公共前缀*/
                        if(failTo.table[i] != null){
                            p.table[i].fail = failTo.table[i];
                            break;
                        } else{
                            /*继续向上寻找*/
                            failTo = failTo.fail;
                        }
                    }
                }
            }
        }
    }
    /*在文本串中查找所有的目标字符串*/
    public HashMap<String, List<Integer>> find(String text){
        /*创建一个表示存储结果的对象*/
        result = new HashMap<String, List<Integer>>();
        for (String s : target){
            result.put(s, new LinkedList<Integer>());
        }
        Node curr = root;
        int i = 0;
        while(i < text.length()){
            /*文本串中的字符*/
            char ch = text.charAt(i);
            /*文本串中的字符和AC自动机中的字符进行比较*/
            if(curr.table[ch] != null){
                /*若相等,自动机进入下一状态*/
                curr = curr.table[ch];
                if(curr.isWord()){
                    result.get(curr.str).add(i - curr.str.length()+1);
                }
                /*这里很容易被忽视,因为一个目标串的中间某部分字符串可能正好包含另一个目标字符串,
         * 即使当前结点不表示一个目标字符串的终点,但到当前结点为止可能恰好包含了一个字符串*/
                if(curr.fail != null && curr.fail.isWord()){
                    result.get(curr.fail.str).add(i - curr.fail.str.length()+1);
                }
                /*索引自增,指向下一个文本串中的字符*/
                i++;
            } else{
                /*若不等,找到下一个应该比较的状态*/
                curr = curr.fail;
                /*到根结点还未找到,说明文本串中以ch作为结束的字符片段不是任何目标字符串的前缀,
         * 状态机重置,比较下一个字符*/
                if(curr == null){
                    curr = root;
                    i++;
                }
            }
        }
        return result;
    }
    public static void main(String[] args){
        List<String> target = new ArrayList<String>();
        target.add("abcdef");
        target.add("abhab");
        target.add("bcd");
        target.add("cde");
        target.add("cdfkcdf");
        String text = "bcabcdebcedfabcdefababkabhabk";
        AhoCorasickAutomation aca = new AhoCorasickAutomation(target);
        HashMap<String, List<Integer>> result = aca.find(text);
        System.out.println(text);
        for (Entry<String, List<Integer>> entry : result.entrySet()){
            System.out.println(entry.getKey()+" : " + entry.getValue());
        }
    }
}

运行结果如下,从结果中我们可以看出文本串中bcd出现了二次,分别是文本串下标为3和下标为13的位置,……。

bcabcdebcedfabcdefababkabhabk
bcd : [3, 13]
cdfkcdf : []
cde : [4, 14]
abcdef : [12]
abhab : [23]

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

从KMP算法到AC自动机 的相关文章

  • Gradle 构建错误:无法从 https://repo1.maven.org/maven2/io/fabric/tools/gradle/maven-metadata.xml 加载 Maven 元数据

    我在 Android studio 中遇到 gradle 构建错误 如下所示 Error A problem occurred configuring project MyApp Could not resolve all dependen
  • 在 Java 中克隆对象 [3 个问题]

    这样做会调用Asub的clone方法吗 或者Asub深度克隆是否正确 如果没有的话 有没有办法通过这种方法对Asub进行深度克隆呢 abstract class Top extends TopMost protected Object cl
  • Junit:如何测试从属性文件读取属性的方法

    嗨 我有课ReadProperty其中有一个方法ReadPropertyFile返回类型的Myclass从属性文件读取参数值并返回Myclass目的 我需要帮助来测试ReadPropertyFile方法与JUnit 如果可能的话使用模拟文件
  • 在内存中使用 byte[] 创建 zip 文件。 Zip 文件总是损坏

    我创建的 zip 文件有问题 我正在使用 Java 7 我尝试从字节数组创建一个 zip 文件 其中包含两个或多个 Excel 文件 应用程序始终完成 没有任何异常 所以 我以为一切都好 当我尝试打开 zip 文件后 Windows 7 出
  • .properties 中的通配符

    是否存在任何方法 我可以将通配符添加到属性文件中 并且具有所有含义 例如a b c d lalalala 或为所有以结尾的内容设置一个正则表达式a b c anything 普通的 Java 属性文件无法处理这个问题 不 请记住 它实际上是
  • 动态选择端口号?

    在 Java 中 我需要获取端口号以在同一程序的多个实例之间进行通信 现在 我可以简单地选择一些固定的数字并使用它 但我想知道是否有一种方法可以动态选择端口号 这样我就不必打扰我的用户设置端口号 这是我的一个想法 其工作原理如下 有一个固定
  • 如何使用assertEquals 和 Epsilon 在 JUnit 中断言两个双精度数?

    不推荐使用双打的assertEquals 我发现应该使用带有Epsilon的形式 这是因为双打不可能100 严格 但无论如何我需要比较两个双打 预期结果和实际结果 但我不知道该怎么做 目前我的测试如下 Test public void te
  • 过滤两次 Lambda Java

    我有一个清单如下 1 2 3 4 5 6 7 和 预期结果必须是 1 2 3 4 5 6 7 我知道怎么做才能到7点 我的结果 1 2 3 4 5 6 我也想知道如何输入 7 我添加了i gt i objList size 1到我的过滤器
  • 在 Jar 文件中运行 ANT build.xml 文件

    我需要使用存储在 jar 文件中的 build xml 文件运行 ANT 构建 该 jar 文件在类路径中可用 是否可以在不分解 jar 文件并将 build xml 保存到本地目录的情况下做到这一点 如果是的话我该怎么办呢 Update
  • Java 公历日历更改时区

    我正在尝试设置 HOUR OF DAY 字段并更改 GregorianCalendar 日期对象的时区 GregorianCalendar date new GregorianCalendar TimeZone getTimeZone GM
  • 无法创建请求的服务[org.hibernate.engine.jdbc.env.spi.JdbcEnvironment]-MySQL

    我是 Hibernate 的新手 我目前正在使用 Spring boot 框架并尝试通过 hibernate 创建数据库表 我知道以前也问过同样的问题 但我似乎无法根据我的环境找出如何修复错误 休眠配置文件
  • volatile、final 和synchronized 安全发布的区别

    给定一个带有变量 x 的 A 类 变量 x 在类构造函数中设置 A x 77 我们想将 x 发布到其他线程 考虑以下 3 种变量 x 线程安全 发布的情况 1 x is final 2 x is volatile 3 x 设定为同步块 sy
  • tomcat 中受密码保护的应用程序

    我正在使用 JSP Servlet 开发一个Web应用程序 并且我使用了Tomcat 7 0 33 as a web container 所以我的要求是tomcat中的每个应用程序都会password像受保护的manager applica
  • 如何访问JAR文件中的Maven资源? [复制]

    这个问题在这里已经有答案了 我有一个使用 Maven 构建的 Java 应用程序 我有一个资源文件夹com pkg resources 我需要从中访问文件 例如directory txt 我一直在查看各种教程和其他答案 但似乎没有一个对我有
  • 尝试将 Web 服务部署到 TomEE 时出现“找不到...的 appInfo”

    我有一个非常简单的项目 用于培训目的 它是一个 RESTful Web 服务 我使用 js css 和 html 创建了一个客户端 我正在尝试将该服务部署到 TomEE 这是我尝试部署时遇到的错误 我在这里做错了什么 刚刚遇到这个问题 我曾
  • 获取文件的总大小(以字节为单位)[重复]

    这个问题在这里已经有答案了 可能的重复 java 高效获取文件大小 https stackoverflow com questions 116574 java get file size efficiently 我有一个名为 filenam
  • 如何使用 jUnit 将测试用例添加到套件中?

    我有 2 个测试类 都扩展了TestCase 每个类都包含一堆针对我的程序运行的单独测试 如何将这两个类 以及它们拥有的所有测试 作为同一套件的一部分执行 我正在使用 jUnit 4 8 在 jUnit4 中你有这样的东西 RunWith
  • 我如何在java中读取二进制数据文件

    因此 我正在为学校做一个项目 我需要读取二进制数据文件并使用它来生成角色的统计数据 例如力量和智慧 它的设置是让前 8 位组成一个统计数据 我想知道执行此操作的实际语法是什么 是不是就像读文本文件一样 这样 File file new Fi
  • 找不到符号 NOTIFICATION_SERVICE?

    package com test app import android app Notification import android app NotificationManager import android app PendingIn
  • 双枢轴快速排序和快速排序有什么区别?

    我以前从未见过双枢轴快速排序 是快速排序的升级版吗 双枢轴快速排序和快速排序有什么区别 我在 Java 文档中找到了这个 排序算法是双枢轴快速排序 作者 弗拉基米尔 雅罗斯拉夫斯基 乔恩 本特利和约书亚 布洛赫 这个算法 在许多数据集上提供

随机推荐

  • canvas在图片上做标记,可以单一也可以多个

  • docker 安装mongo数据库

    1 pull镜像 docker pull mongo 4 2 创建目录 mkdir p mongodb datadb chmod 777 mongodb datadb 3 运行 准备好目录之后 就可以开始运行 Docker 镜像了 dock
  • AI,v3,百度人脸识别库上传---node

    config有必要的grant type client id client secret var https require https var request require request var qs require querystr
  • The mbstring extension is missing. Please check your PHP configuration.

    在安装完毕wamp程序后 启动后访问phpmyadmin 出现错误 The mbstring extension is missing Please check your PHP configuration 解决方案 在php ini中修改
  • LRU算法的详细介绍与实现

    1 背景 LRU least recently used 最近最少使用算法 是一种内存数据淘汰策略 使用常见是当内存不足时 需要淘汰最近最少使用的数据 LRU常用语缓存系统的淘汰策略 2 LRU原理 LRU最早实在操作系统接触到这个算法的
  • Python Day6-元组-操作-拷贝

    Python元组 操作 拷贝 Python的元组与列表类似 不同之处在于元组的元素不能修改 元组中的元素也不能被删除 但可以删除整个元组 元组使用小括号 列表使用方括号 元组创建 只需要在括号中添加元素 并使用逗号隔开即可 1 元组的定义
  • 五人合伙最佳股份分配_老板要懂的股权合伙,懂股权者懂人心,合理分配得人心...

    1 合伙人股份分配协议分红 两人合伙 出钱不出力 出钱又出力 股份这样算 两人合伙开公司 A出资60万 不干活 B出资40万 全职无休 他们的股份该怎么计算 如果按资金占总股比例为60 人力占总股比例为40 来分配股权 A的资金股就是60万
  • Xmrig挖矿木马排查过程,xmrig占用大量CPU

    1 通过top发现xmrig占用了大量cpu 2 通过网上搜索发现是挖矿木马 3 尝试直接kill发现杀死之后又会自动重启 4 查找find name xmrig 文件 或者程序信息 ps aux grep xmrig 找到安装目录并将其删
  • Caffe:CPU模式下使用Intel MKL

    转自 https blog csdn net 10km article details 52724477 下载安装Intel MKL 打开这里Intel Math Kernel Library Intel MKL 点击 Get This L
  • MySQL索引机制(详细+原理+解析)

    MySQL索引机制 永远年轻 永远热泪盈眶 一 索引的类型与常见的操作 前缀索引 MySQL 前缀索引能有效减小索引文件的大小 提高索引的速度 但是前缀索引也有它的坏处 MySQL 不能在 ORDER BY 或 GROUP BY 中使用前缀
  • SDL教程零基础入门 简单操作 day1

    1 0 SDL 简介 文章目录 1 0 SDL 简介 1 1 什么是 SDL 1 2 SDL 可以做什么 2 在VS上获取和安装 SDL 2 1 SDL2库下载 2 2 安装SDL2 2 3 在Visual Studio中使用SDL 3 S
  • 浏览器播放rtsp视频流:2、ffmpeg转hls播放(go后端利用hls做简单视频直播)

    浏览器播放rtsp视频流 2 ffmpeg转hls播放 go后端利用hls做简单视频直播 文章目录 浏览器播放rtsp视频流 2 ffmpeg转hls播放 go后端利用hls做简单视频直播 1 前言 2 wsl安装ffmpeg并转换rtsp
  • Android性能优化之应用瘦身(APK瘦身)

    关于作者 CSDN内容合伙人 技术专家 从零开始做日活千万级APP 专注于分享各领域原创系列文章 擅长java后端 移动开发 人工智能等 希望大家多多支持 目录 一 导读 二 概览 2 1 apk组成 三 优化方向 3 1 源代码 3 1
  • Leetcode[数组] 买卖股票的最佳时机 II - 贪心算法

    0 题目描述 leetcode原题链接 买卖股票的最佳时机 II 1 贪心算法 class Solution def maxProfit self prices List int gt int maxprofit 0 for i in ra
  • C# 委托监控属性变量

    C 委托监控属性变量 一 委托 二 示例 三 参考 四 总结 一 委托 C 的委托类似函数指针 可以用于定义回调方法 一个委托可以代理多个被调的方法 二 示例 using System using System Collections Ge
  • QT 在Windows下显示中文乱码的问题

    前一段时间刚刚使用qt在显示表格数据的时候 发现输出中文是乱码的 比如在显示字符串加上变量输出的的时候一般在MFC方法有很多 但是在QT上初始化字符串加上变量的话就得这么写 QString 第 1跟 arg 16 i 但是这样的话 中文就会
  • js for in遍历对象_JS中轻松遍历对象属性的几种方式

    原文 https dmitripavlutin com how to iterate easily over object properties in javascript 译者 前端小智 为了保证的可读性 本文采用意译而非直译 想阅读更多
  • Docker存储卷

    文章目录 Docker存储卷 1 COW机制 2 什么是存储卷 3 使用存储卷的好处 4 为什么要使用存储卷 5 存储卷管理方式 6 存储卷的分类 7 容器数据管理 7 1 在容器中使用数据卷 7 2 数据卷容器 7 3 利用数据卷容器迁移
  • [导入]开通微软网络硬盘的方法

    看到我帅谁爱发的帖子 转来和大家看看 现在只要进入Live Skydrive 主页 http skydrive live com 用自己hotmail MSN 或Live 帐号登录之后 就可以看到系统的注册提示了 接受协议之后立马即可开通W
  • 从KMP算法到AC自动机

    本文档内容参考以下文档整理而成 kpm算法 http www ruanyifeng com blog 2013 05 Knuth E2 80 93Morris E2 80 93Pratt algorithm html AC自动机 https