当前位置: 首页 > 编程日记 > 正文

grep之字符串搜索算法Boyer-Moore由浅入深(比KMP快3-5倍)

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

1. 简单介绍

在用于查找子字符串的算法当中,BM(Boyer-Moore)算法是目前被认为最高效的字符串搜索算法,它由Bob Boyer和J Strother Moore设计于1977年。 一般情况下,比KMP算法快3-5倍。该算法常用于文本编辑器中的搜索匹配功能,比如大家所熟知的GNU grep命令使用的就是该算法,这也是GNU grep比BSD grep快的一个重要原因,具体推荐看下我最近的一篇译文“为什么GNU grep如此之快?”作者是GNU grep的编写者Mike Haertel。

2. 主要特征

假设文本串text长度为n,模式串pattern长度为m,BM算法的主要特征为:

  • 从右往左进行比较匹配(一般的字符串搜索算法如KMP都是从从左往右进行匹配);

  • 算法分为两个阶段:预处理阶段和搜索阶段;

  • 预处理阶段时间和空间复杂度都是是O(m+sigma),sigma是字符集大小,一般为256;

  • 搜索阶段时间复杂度是O(mn);

  • 当模式串是非周期性的,在最坏的情况下算法需要进行3n次字符比较操作;

  • 算法在最好的情况下达到O(n / m),比如在文本串bn中搜索模式串am-1b ,只需要n/m次比较。

这些特征先让大家对该算法有个基本的了解,等看懂了算法再来看这些特征又会有些额外的收获。

3.算法基本思想

常规的匹配算法移动模式串的时候是从左到右,而进行比较的时候也是从左到右的,基本框架是:

1
2
3
4
5
6
7
8
9
10
while (j <= strlen (text) - strlen (pattern)){
     for (i = 0; i < strlen (pattern) && pattern[i] == text[i + j]; ++i);
     if (i == strlen (pattern)) {
         Match;
         break ;
     }
     else
         ++j;
}

而BM算法在移动模式串的时候是从左到右,而进行比较的时候是从右到左的,基本框架是:

1
2
3
4
5
6
7
8
9
10
while (j <= strlen (text) - strlen (pattern)){
     for (i = strlen (pattern); i >= 0 && pattern[i] == text[i + j]; --i);
     if (i < 0)) {
         Match;
         break ;
     }
     else
         j += BM();
}

BM算法的精华就在于BM(text, pattern),也就是BM算法当不匹配的时候一次性可以跳过不止一个字符。即它不需要对被搜索的字符串中的字符进行逐一比较,而会跳过其中某些部分。通常搜索关键字越长,算法速度越快。它的效率来自于这样的事实:对于每一次失败的匹配尝试,算法都能够使用这些信息来排除尽可能多的无法匹配的位置。即它充分利用待搜索字符串的一些特征,加快了搜索的步骤。

BM算法实际上包含两个并行的算法(也就是两个启发策略):坏字符算法(bad-character shift)和好后缀算法(good-suffix shift)。这两种算法的目的就是让模式串每次向右移动尽可能大的距离(即上面的BM()尽可能大)。

下面不直接书面解释这两个算法,为了更加通俗易懂,先用实例说明吧,这是最容易接受的方式。

4. 字符串搜索头脑风暴

大家来头脑风暴下:如何加快字符串搜索?举个很简单的例子,如下图所示,navie表示一般做法,逐个进行比对,从右向左,最后一个字符c与text中的d不匹配,pattern右移一位。但大家看一下这个d有什么特征?pattern中没有d,因此你不管右移1、2、3、4位肯定还是不匹配,何必花这个功夫呢?直接右移5(strlen(pattern))位再进行比对不是更好吗?好,就这样做,右移5位后,text中的b与pattern中的c比较,发现还是不同,这时咋办?b在pattern中有所以不能一下右移5位了,难道直接右移一位吗?No,可以直接将pattern中的b右移到text中b的位置进行比对,但是pattern中有两个b,右移哪个b呢?保险的办法是用最右边的b与text进行比对,为啥?下图说的很清楚了,用最左边的b太激进了,容易漏掉真正的匹配,图中用最右边的b后发现正好所有的都匹配成功了,如果用最左边的不就错过了这个匹配项吗?这个启发式搜索就是BM算法做的。

BM-math

But, 如果遇到下面这样的情况,开始pattern中的c和text中的b不匹配,Ok,按上面的规则将pattern右移直至最右边的b与text的b对齐进行比对。再将pattern中的c与text中的c进行比对,匹配继续往左比对,直到位置3处pattern中的a与text中的b不匹配了,按上面讲的启发式规则应该将pattern中最右边的b与text的b对齐,可这时发现啥了?pattern走了回头路,干吗?当然不干,才不要那么傻,针对这种情况,只需要将pattern简单的右移一步即可,坚持不走回头路!

BM-math02

好了,这就是所谓的“坏字符算法”,简单吧,通俗易懂吧,上面用红色粗体字标注出来的b就是“坏字符”,即不匹配的字符,坏字符是针对text的。

BM难道就这么简单?就一个启发式规则就搞定了?当然不是了,大家再次头脑风暴一下,有没有其他加快字符串搜索的方法呢?比如下面的例子

BM-math03

一开始利用了坏字符算法一下移了4位,不错,接下来遇到了回头路,没办法只能保守移一位,但真的就只能移一位吗?No,因为pattern中前面其他位置也有刚刚匹配成功的后缀ab,那么将pattern前面的ab右移到text刚匹配成功的ab对齐继续往前匹配不是更好吗?这样就可以一次性右移两位了,很好的有一个启发式搜索规则啊。有人可能想:要是前面没已经匹配成功的后缀咋办?是不是就无效了?不完全是,这要看情况了,比如下面这个例子。

BM-math04

cbab这个后缀已经成功匹配,然后b没成功,而pattern前面也没发现cbab这样的串,这样就直接保守移一位?No,前面有ab啊,这是cbab后缀的一部分,也可以好好利用,直接将pattern前面的ab右移到text已经匹配成功的ab位置处继续往前匹配,这样一下子就右移了四位,很好。当然,如果前面完全没已经匹配成功的后缀或部分后缀,比如最前面的babac,那就真的不能利用了。

好了,这就是所谓的“好后缀算法”,简单吧,通俗易懂吧,上面用红色字标注出来的ab(前面例子)和cbab(上面例子)就是“好后缀”,好后缀是针对pattern的。

下面,最后再举个例子说明啥是坏字符,啥是好后缀。

主串  :  mahtavaatalomaisema omalomailuun

模式串: maisemaomaloma

坏字符:主串中的“t”为坏字符。

好后缀:模式串中的aloma为“好后缀”。

BM就这么简单?是的,容易理解但并不是每个人都能想到的两个启发式搜索规则就造就了BM这样一个优秀的算法。那么又有个问题?这两个算法怎么运用,一下坏字符的,一下好后缀的,什么时候该用坏字符?什么时候该用好后缀呢?很好的问题,这就要看哪个右移的位数多了,比如上面的例子,一开始如果用好后缀的话只能移一位而用坏字符就能右移三位,此时当然选择坏字符算法了。接下来如果继续用坏字符则只能右移一位而用好后缀就能一下右移四位,这时候你说用啥呢?So,这两个算法是“并行”的,哪个大用哪个。

光用例子说明当然不够,太浅了,而且还不一定能完全覆盖所有情况,不精确。下面就开始真正的理论探讨了。

5. BM算法理论探讨

(1)坏字符算法

当出现一个坏字符时, BM算法向右移动模式串, 让模式串中最靠右的对应字符与坏字符相对,然后继续匹配。坏字符算法有两种情况。

Case1:模式串中有对应的坏字符时,让模式串中最靠右的对应字符与坏字符相对(PS:BM不可能走回头路,因为若是回头路,则移动距离就是负数了,肯定不是最大移动步数了),如下图。

BM-math05

Case2:模式串中不存在坏字符,很好,直接右移整个模式串长度这么大步数,如下图。

BM-math06

(2)好后缀算法

如果程序匹配了一个好后缀, 并且在模式中还有另外一个相同的后缀或后缀的部分, 那把下一个后缀或部分移动到当前后缀位置。假如说,pattern的后u个字符和text都已经匹配了,但是接下来的一个字符不匹配,我需要移动才能匹配。如果说后u个字符在pattern其他位置也出现过或部分出现,我们将pattern右移到前面的u个字符或部分和最后的u个字符或部分相同,如果说后u个字符在pattern其他位置完全没有出现,很好,直接右移整个pattern。这样,好后缀算法有三种情况,如下图所示:

Case1:模式串中有子串和好后缀完全匹配,则将最靠右的那个子串移动到好后缀的位置继续进行匹配。

BM-math07

Case2:如果不存在和好后缀完全匹配的子串,则在好后缀中找到具有如下特征的最长子串,使得P[m-s…m]=P[0…s]。

BM-math08

Case3:如果完全不存在和好后缀匹配的子串,则右移整个模式串。

(3)移动规则

BM算法的移动规则是:

将3中算法基本框架中的j += BM(),换成j += MAX(shift(好后缀),shift(坏字符)),即

BM算法是每次向右移动模式串的距离是,按照好后缀算法和坏字符算法计算得到的最大值。

shift(好后缀)和shift(坏字符)通过模式串的预处理数组的简单计算得到。坏字符算法的预处理数组是bmBc[],好后缀算法的预处理数组是bmGs[]。

6. BM算法具体执行

BM算法子串比较失配时,按坏字符算法计算pattern需要右移的距离,要借助bmBc数组,而按好后缀算法计算pattern右移的距离则要借助bmGs数组。下面讲下怎么计算bmBc[]和bmGs[]这两个预处理数组。

(1)计算坏字符数组bmBc[]

这个计算应该很容易,似乎只需要bmBc[i] = m – 1 – i就行了,但这样是不对的,因为i位置处的字符可能在pattern中多处出现(如下图所示),而我们需要的是最右边的位置,这样就需要每次循环判断了,非常麻烦,性能差。这里有个小技巧,就是使用字符作为下标而不是位置数字作为下标。这样只需要遍历一遍即可,这貌似是空间换时间的做法,但如果是纯8位字符也只需要256个空间大小,而且对于大模式,可能本身长度就超过了256,所以这样做是值得的(这也是为什么数据越大,BM算法越高效的原因之一)。

BM-math09

如前所述,bmBc[]的计算分两种情况,与前一一对应。

Case1:字符在模式串中有出现,bmBc['v']表示字符v在模式串中最后一次出现的位置,距离模式串串尾的长度,如上图所示。

Case2:字符在模式串中没有出现,如模式串中没有字符v,则BmBc['v'] = strlen(pattern)。

写成代码也非常简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void PreBmBc( char *pattern, int m, int bmBc[])
{
     int i;
     for (i = 0; i < 256; i++)
     {
         bmBc[i] = m;
     }
     for (i = 0; i < m - 1; i++)
     {
         bmBc[pattern[i]] = m - 1 - i;
     }
}

计算pattern需要右移的距离,要借助bmBc数组,那么bmBc的值是不是就是pattern实际要右移的距离呢?No,想想也不是,比如前面举例说到利用bmBc算法还可能走回头路,也就是右移的距离是负数,而bmBc的值绝对不可能是负数,所以两者不相等。那么pattern实际右移的距离怎么算呢?这个就要看text中坏字符的位置了,前面说过坏字符算法是针对text的,还是看图吧,一目了然。图中v是text中的坏字符(对应位置i+j),在pattern中对应不匹配的位置为i,那么pattern实际要右移的距离就是:bmBc['v'] – m + 1 + i。

BM-math10

(2)计算好后缀数组bmGs[]

这里bmGs[]的下标是数字而不是字符了,表示字符在pattern中位置。

如前所述,bmGs数组的计算分三种情况,与前一一对应。假设图中好后缀长度用数组suff[]表示。

Case1:对应好后缀算法case1,如下图,j是好后缀之前的那个位置。

BM-math11

Case2:对应好后缀算法case2:如下图所示:

BM-math13

Case3:对应与好后缀算法case3,bmGs[i] = strlen(pattern)= m

BM-math14

这样就更加清晰了,代码编写也比较简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void PreBmGs( char *pattern, int m, int bmGs[])
{
     int i, j;
     int suff[SIZE]; 
     // 计算后缀数组
     suffix(pattern, m, suff);
     // 先全部赋值为m,包含Case3
     for (i = 0; i < m; i++)
     {
         bmGs[i] = m;
     }
     // Case2
     j = 0;
     for (i = m - 1; i >= 0; i--)
     {
         if (suff[i] == i + 1)
         {
             for (; j < m - 1 - i; j++)
             {
                 if (bmGs[j] == m)
                     bmGs[j] = m - 1 - i;
             }
         }
     }
     // Case1
     for (i = 0; i <= m - 2; i++)
     {
         bmGs[m - 1 - suff[i]] = m - 1 - i;
     }
}

So easy? 结束了吗?还差一步呢,这里的suff[]咋求呢?

在计算bmGc数组时,为提高效率,先计算辅助数组suff[]表示好后缀的长度。

suff数组的定义:m是pattern的长度

a. suffix[m-1] = m;

b. suffix[i] = k

for [ pattern[i-k+1] ….,pattern[i]] == [pattern[m-1-k+1],pattern[m-1]]

看上去有些晦涩难懂,实际上suff[i]就是求pattern中以i位置字符为后缀和以最后一个字符为后缀的公共后缀串的长度。不知道这样说清楚了没有,还是举个例子吧:

i     : 0 1 2 3 4 5 6 7
pattern: b c  a b a b a b

当i=7时,按定义suff[7] = strlen(pattern) = 8

当i=6时,以pattern[6]为后缀的后缀串为bcababa,以最后一个字符b为后缀的后缀串为bcababab,两者没有公共后缀串,所以suff[6] = 0

当i=5时,以pattern[5]为后缀的后缀串为bcabab,以最后一个字符b为后缀的后缀串为bcababab,两者的公共后缀串为abab,所以suff[5] = 4

以此类推……

当i=0时,以pattern[0]为后缀的后缀串为b,以最后一个字符b为后缀的后缀串为bcababab,两者的公共后缀串为b,所以suff[0] = 1

这样看来代码也很好写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void suffix( char *pattern, int m, int suff[])
{
     int i, j;
     int k;
     suff[m - 1] = m;
     for (i = m - 2; i >= 0; i--)
     {
         j = i;
         while (j >= 0 && pattern[j] == pattern[m - 1 - i + j]) j--;
         suff[i] = i - j;
     }
}

这样可能就万事大吉了,可是总有人对这个算法不满意,感觉太暴力了,于是有聪明人想出一种方法,对上述常规方法进行改进。基本的扫描都是从右向左,改进的地方就是利用了已经计算得到的suff[]值,计算现在正在计算的suff[]值。具体怎么利用,看下图:

i是当前正准备计算suff[]值的那个位置。

f是上一个成功进行匹配的起始位置(不是每个位置都能进行成功匹配的,  实际上能够进行成功匹配的位置并不多)。

g是上一次进行成功匹配的失配位置。

如果i在g和f之间,那么一定有P[i]=P[m-1-f+i];并且如果suff[m-1-f+i] < i-g, 则suff[i] = suff[m-1-f+i],这不就利用了前面的suff了吗。

BM-math15

PS:这里有些人可能觉得应该是suff[m-1-f+i] <= i – g,因为若suff[m-1-f+i] = i – g,还是没超过suff[f]的范围,依然可以利用前面的suff[],但这是错误的,比如一个极端的例子:

i      :0 1 2 3 4 5 6 7 8 9
pattern:a  a a a a b a a a  a

suff[4] = 4,这里f=4,g=0,当i=3是,这时suff[m-1=f+i]=suff[8]=3,而suff[3]=4,两者不相等,因为上一次的失配位置g可能会在这次得到匹配。

好了,这样解释过后,代码也比较简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void suffix( char *pattern, int m, int suff[]) {
    int f, g, i;
    suff[m - 1] = m;
    g = m - 1;
    for (i = m - 2; i >= 0; --i) {
       if (i > g && suff[i + m - 1 - f] < i - g)
          suff[i] = suff[i + m - 1 - f];
       else {
          if (i < g)
             g = i;
          f = i;
          while (g >= 0 && pattern[g] == pattern[g + m - 1 - f])
             --g;
          suff[i] = f - g;
       }
    }
}

结束了?OK,可以说重要的算法都完成了,希望大家能够看懂,为了验证大家到底有没有完全看明白,下面出个简单的例子,大家算一下bmBc[]、suff[]和bmGs[]吧。

举例如下:

BM-math16

 PS:这里也许有人会问:bmBc['b']怎么等于2,它不是最后出现在pattern最后一个位置吗?按定义应该是0啊。请大家仔细看下bmBc的算法:

1
2
3
4
for (i = 0; i < m - 1; i++)
   {
       bmBc[pattern[i]] = m - 1 - i;
   }

这里是i < m – 1不是i < m,也就是最后一个字符如果没有在前面出现过,那么它的bmBc值为m。为什么最后一位不计算在bmBc中呢?很容易想啊,如果记在内该字符的bmBc就是0,按前所述,pattern需要右移的距离bmBc['v']-m+1+i=-m+1+i <= 0,也就是原地不动或走回头路,当然不干了,前面这种情况已经说的很清楚了,所以这里是m-1。

好了,所有的终于都讲完了,下面整合一下这些算法吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#include <stdio.h>
#include <string.h>
#define MAX_CHAR 256
#define SIZE 256
#define MAX(x, y) (x) > (y) ? (x) : (y)
void BoyerMoore( char *pattern, int m, char *text, int n);
int main()
{
     char text[256], pattern[256];
     while (1)
     {
         scanf ( "%s%s" , text, pattern);
         if (text == 0 || pattern == 0) break ;
         BoyerMoore(pattern, strlen (pattern), text, strlen (text));
         printf ( "\n" );
     }
     return 0;
}
void print( int *array, int n, char *arrayName)
{
     int i;
     printf ( "%s: " , arrayName);
     for (i = 0; i < n; i++)
     {
         printf ( "%d " , array[i]);
     }
     printf ( "\n" );
}
void PreBmBc( char *pattern, int m, int bmBc[])
{
     int i;
     for (i = 0; i < MAX_CHAR; i++)
     {
         bmBc[i] = m;
     }
     for (i = 0; i < m - 1; i++)
     {
         bmBc[pattern[i]] = m - 1 - i;
     }
/*  printf("bmBc[]: ");
     for(i = 0; i < m; i++)
     {
         printf("%d ", bmBc[pattern[i]]);
     }
     printf("\n"); */
}
void suffix_old( char *pattern, int m, int suff[])
{
     int i, j;
     suff[m - 1] = m;
     for (i = m - 2; i >= 0; i--)
     {
         j = i;
         while (j >= 0 && pattern[j] == pattern[m - 1 - i + j]) j--;
         suff[i] = i - j;
     }
}
void suffix( char *pattern, int m, int suff[]) {
    int f, g, i;
    suff[m - 1] = m;
    g = m - 1;
    for (i = m - 2; i >= 0; --i) {
       if (i > g && suff[i + m - 1 - f] < i - g)
          suff[i] = suff[i + m - 1 - f];
       else {
          if (i < g)
             g = i;
          f = i;
          while (g >= 0 && pattern[g] == pattern[g + m - 1 - f])
             --g;
          suff[i] = f - g;
       }
    }
//   print(suff, m, "suff[]");
}
void PreBmGs( char *pattern, int m, int bmGs[])
{
     int i, j;
     int suff[SIZE]; 
     // 计算后缀数组
     suffix(pattern, m, suff);
     // 先全部赋值为m,包含Case3
     for (i = 0; i < m; i++)
     {
         bmGs[i] = m;
     }
     // Case2
     j = 0;
     for (i = m - 1; i >= 0; i--)
     {
         if (suff[i] == i + 1)
         {
             for (; j < m - 1 - i; j++)
             {
                 if (bmGs[j] == m)
                     bmGs[j] = m - 1 - i;
             }
         }
     }
     // Case1
     for (i = 0; i <= m - 2; i++)
     {
         bmGs[m - 1 - suff[i]] = m - 1 - i;
     }
//  print(bmGs, m, "bmGs[]");
}
void BoyerMoore( char *pattern, int m, char *text, int n)
{
     int i, j, bmBc[MAX_CHAR], bmGs[SIZE];
     // Preprocessing
     PreBmBc(pattern, m, bmBc);
     PreBmGs(pattern, m, bmGs);
     // Searching
     j = 0;
     while (j <= n - m)
     {
         for (i = m - 1; i >= 0 && pattern[i] == text[i + j]; i--);
         if (i < 0)
         {
             printf ( "Find it, the position is %d\n" , j);
             j += bmGs[0];
             return ;
         }
         else
         {
             j += MAX(bmBc[text[i + j]] - m + 1 + i, bmGs[i]);
         }
     }
     printf ( "No find.\n" );
}

运行效果如下:

BM-math17


转载于:https://my.oschina.net/u/1388024/blog/190802

相关文章:

多线程threading

threading用于提供线程相关的操作&#xff0c;线程是应用程序中工作的最小单元。python当前版本的多线程库没有实现优先级、线程组&#xff0c;线程也不能被停止、暂停、恢复、中断。 1. threading模块提供的类&#xff1a; Thread, Lock, Rlock, Condition, [Bounded]Sem…

一个简单的程序来使用WiredTiger 存储引擎

前言 WiredTiger 自 mongodb3.0 集成进来之后为mongodb拉回了大量的口碑&#xff0c;从而在mongodb-3.2 版本直接代替了in-memory存储引擎&#xff0c;作为了mongodb的默认存储引擎。其 通过支持Append-only btree lsm-tree 以及 针对磁盘/内存数据结构上的多核和无锁优化&am…

Java项目:网上商城系统(java+SSM+jsp+mysql+maven)

源码获取&#xff1a;博客首页 "资源" 里下载&#xff01; 一、项目简述功能 javaweb 网上商城系统&#xff0c;前台&#xff0b;后台管理&#xff0c;用户注册&#xff0c;登录&#xff0c;上哦展示&#xff0c;分组展示&#xff0c;搜索&#xff0c;收货地址管理&…

Linux 启动详解之init

1.init初探 init是Linux系统操作中不可缺少的程序之一。init进程&#xff0c;它是一个由内核启动的用户级进程&#xff0c;然后由它来启动后面的任务&#xff0c;包括多用户环境&#xff0c;网络等。 内核会在过去曾使用过init的几个地方查找它&#xff0c;它的正确位置&#x…

mysql 相关命令

mysqladmin versionmysqladmin statusmysqlshow -u帐号 -p密码 mysqlshow -u帐号 -p密码 库名mysql -u帐号 -p密码 -e SELECT Host,Db,User From db mysqlmysqldump --quick mysql | gzip > /root/mysql.gzmysqladmin create dbtestgunzip < /root/mysql.gz | mysql…

maven 添加数据库驱动

1.电脑上需要安装 apache maven2.下载oracle的jar包 例如我下载的是ojdbc7-12.jar3.cmd执行命令 mvn install:install-file -DgroupIdcom.oracle -DartifactIdojdbc7 -Dversion12 -Dpackagingjar -Dfiled:\jar\ojdbc7-12.jar-Dfile jar包所存放的位置4.pom文件添加&#xff1…

Rocksdb 的 BlobDB key-value 分离存储插件

前言 还是回到传统的 LSM-tree 中&#xff0c;我们key-value 写入时以append形态存放到一个data-block中&#xff0c;多个data-blockmetablock 之类的数据组织成一个sst。当我们读数据以及compaction的时候读到key 之后则很方便得读取到对应的value&#xff0c;一次I/O能够将k…

Java项目:(前端vue后台java微服务)在线考试系统(java+vue+springboot+mysql+maven)

源码获取&#xff1a;博客首页 "资源" 里下载&#xff01; 考试流程&#xff1a; 用户前台注册成为学生 管理员后台添加老师&#xff0c;系统将该用户角色上升为老师 老师登录&#xff0c;添加考试,添加题目&#xff0c;发布考试 考生登录前台参加考试&#xff0c…

C++实现stack【栈】

要求&#xff1a; //****file: stack.h/*对stack进行初始化检查stack为空&#xff0c;或已满将整数压入到stack中从stack里弹出整数 不移除任何袁术&#xff0c;讲过stack的内容输出到标准输出Stack类的私有成员如下&#xff1a;一个用于打印错误信息的私有哦成员函数三个私有数…

c#操作Excel整理总结

大家好&#xff0c;这是我在工作中总结的关于C#操作Excel的帮助类&#xff0c;欢迎大家批评指正&#xff01; using System; using System.Collections.Generic; using System.Data; using System.Data.OleDb; using System.IO; using Aspose.Cells;namespace MusicgrabTool {p…

C++ std::function<void(int)> 和 std::function<void()> 作为函数参数的注意事项

前言 std::function 作为标准库提供的函数指针&#xff0c;使用起来还是比较方便的&#xff0c;不过在使用过程中有一些需要注意的细节&#xff0c;这里做一个简单的记录。 基本使用 头文件: #include <functional>语法&#xff1a;std::function<return_type(args…

Java项目:网上电商系统(java+SSM+mysql+maven+tomcat)

源码获取&#xff1a;博客首页 "资源" 里下载&#xff01; 一、项目简述 功能&#xff1a;本系统分用户前台和管理员后台。 前台展示后台管理&#xff0c;前台界面可实现用户登录&#xff0c;用户注 册&#xff0c;商品展示&#xff0c;商品明细展示&#xff0c;用户…

C# SQLiteHelper

1 public class SQLiteHelpers2 {3 /// <summary> 4 /// ConnectionString样例&#xff1a;DatasourceTest.db3;Poolingtrue;FailIfMissingfalse 5 /// </summary> 6 public static string ConnectionStri…

[Git] 拉开发分支的代码报错

Git拉开发分支的代码报错&#xff1a; fatal: The remote end hung up unexpectedly fatal: early EOF fatal: index-pack failed 解决办法&#xff1a; git config --global core.compression -1 转载于:https://www.cnblogs.com/MasterMonkInTemple/p/10754596.html

C++ 通过模版工厂实现 简单反射机制

前言 我们知道Java/Python这种语言能够很好得 支持反射。反射机制 就是一种用户输入的字符串到对应实现方法的映射&#xff0c;比如http接口中 用户传入了url&#xff0c;我们需要调用该url对应的方法/函数对象 从而做出对应的操作。 而C 并没有友好得支持这样的操作&#xf…

计算机世界的“十六进制”为什么如此重要

在计算机世界中,十六进制扮演着不可或缺的角色。它以其紧凑的表示形式、与二进制的天然对应关系以及在各个领域的广泛应用,成为了计算机科学中的一把重要工具。总体而言,计算机需要十六进制并非偶然,它是一种为了更好地满足人类理解和处理数据的需求而产生的工具,为计算机科学的发展和应用提供了便利和支持。

面试官:如何实现10亿数据判重?

以 Java 中的 int 为例,来对比观察 BitMap 的优势,在 Java 中,int 类型通常需要 32 位(4 字节*8),而 BitMap 使用 1 位就可以来标识此元素是否存在,所以可以认为 BitMap 占用的空间大小,只有 int 类型的 1/32,所以有大数据量判重时,使用 BitMap 也可以实现。所以数据库去重显然是不行的。而使用集合也是不合适的,因为数据量太大,使用集合会导致内存不够用或内存溢出和 Full GC 频繁等问题,所以此时我们的解决方案通常是采用布隆过滤器来实现判重。

Java项目:校园二手市场系统(java+SSM+mysql+maven+tomcat)

源码获取&#xff1a;博客首页 "资源" 里下载&#xff01; 一、项目简述&#xff08; IW文档&#xff09; 功能&#xff1a;本系统分用户前台和管理员后台。 本系统用例模型有三种&#xff0c;分别是游客、注册用户和系统管 理员。下面分别对这三个角色的功能进行描…

php中$_REQUEST、$_POST、$_GET的区别和联系小结

php中$_REQUEST、$_POST、$_GET的区别和联系小结 作者&#xff1a; 字体&#xff1a;[增加 减小] 类型&#xff1a;转载php中有$_request与$_post、$_get用于接受表单数据&#xff0c;当时他们有何种区别&#xff0c;什么时候用那种最好。1. $_REQUEST php中$_REQUEST可以获取以…

uva 315 (poj 1144 求割点)

题意&#xff1a;给你一张无向图&#xff0c;求割点的个数。 思路&#xff1a;输入稍微处理一下接着直接套模版。 1 #include <iostream>2 #include <cstdio>3 #include <cstring>4 #include <cstdlib>5 #include <cmath>6 #include <algorit…

SQL学习之计算字段的用法与解析

一、计算字段 1、存储在数据库表中的数据一般不是应用程序所需要的格式。大多数情况下,数据表中的数据都需要进行二次处理。下面举几个例子。 (1)、我们需要一个字段同时显示公司名和公司地址&#xff0c;但这两个信息存储在不同表的列中。 (2)、省份、城市、邮政编码存储在不同…

手把手教你 用C++实现一个 可持久化 的http_server

前言 本文介绍一个有趣的 通过C实现的 持久化的http_server demo&#xff0c;这样我们通过http通信之后的数据可以持久化存储&#xff0c;即使server挂了&#xff0c;数据也不会丢失。我们的http_sever 也就能够真正得作为一个后端server了。 本身持久化这个能力是数据库提供…

【SVN多用户开发】代码冲突解决办法

SVN是一款集中式的代码存储工具&#xff0c;可以帮助多个用户协同开发同一应用程序。 但是SVN不能完全代替人工操作&#xff0c;有时也需要程序员自己进行沟通确认有效的代码。 下面就简单的看一下&#xff0c;常见的代码冲突以及解决方法。 总结起来&#xff0c;无非是&#x…

Java项目:在线宠物商店系统(java+SSM+mysql+maven+tomcat)

源码获取&#xff1a;博客首页 "资源" 里下载&#xff01; 一、项目简述 功能&#xff1a;本系统分用户前台和管理员后台。 系统包括用户的注册登录&#xff0c;狗狗的展示购物车添加以及下 单支付购买&#xff0c;后台有管理员用户&#xff0c;可以操作狗狗的品种&…

字符串中的数字排序

2019独角兽企业重金招聘Python工程师标准>>> public static String getBusiScope(String busiScope){ String regex "\\d{1,2}"; String busiStr""; Pattern pattern Pattern.compile(regex); Matcher matcher pattern.matcher(busiScope…

oo第二单元总结

第二单元总结 第一次作业 一、设计策略 本次作业采用FAFS算法&#xff0c;可直接用输入线程与电梯线程交互&#xff0c;调度器暂时不需要参与&#xff0c;故一共设计三个类三线程&#xff1a;Main类、elevator类及input类&#xff0c;main线程、elevator线程及input线程。main线…

Rocksdb iterator 的 Forward-scan 和 Reverse-scan 的性能差异

前言 最近在读 MyRocks 存储引擎2020年的论文&#xff0c;因为这个存储引擎是在Rocksdb之上进行封装的&#xff0c;并且作为Facebook 内部MySQL的底层引擎&#xff0c;用来解决Innodb的空间利用率低下 和 压缩效率低下的问题。而且MyRocks 在接入他们UDB 之后成功达成了他们的…

Java知多少(29)覆盖和重载

在类继承中&#xff0c;子类可以修改从父类继承来的方法&#xff0c;也就是说子类能创建一个与父类方法有不同功能的方法&#xff0c;但具有相同的名称、返回值类型、参数列表。如果在新类中定义一个方法&#xff0c;其名称、返回值类型和参数列表正好与父类中的相同&#xff0…

Java项目:清新论坛系统(java+SSM+mysql+maven+tomcat)

源码获取&#xff1a;博客首页 "资源" 里下载&#xff01; 一、项目简述 功能&#xff1a;本系统分用户前台和管理员后台。 用户前台主要功能有&#xff1a; 用户注册 用户登录 浏览帖子 回复帖子 修改个人资料 管理员后台的功能有&#xff1a; 管理论坛版块 用户管…

JUnit4.11 理论机制 @Theory 完整解读

最近在研究JUnit4&#xff0c;大部分基础技术都是通过百度和JUnit的官方wiki学习的&#xff0c;目前最新的发布版本是4.11&#xff0c;结合代码实践&#xff0c;发现官方wiki的内容或多或少没有更新&#xff0c;Theory理论机制章节情况尤为严重&#xff0c;不知道这章wiki对应的…