也谈KMP算法

转眼间,下个学期我们专业就要开《数据结构》这门课程了。想当初我自学数据结构的时候可谓是困难重重,现在我想到有可能我身边的同学们也会遇到和我一样的问题。于是我打算尽我的能力,把我在学习算法的过程中的一点感悟,或者是一些我自认为的难点,用我自己的话描述出来。希望可以给朋友们有所帮助。

我记得我遇到的第一个难题就是KMP算法。

KMP算法是一种改进的字符串匹配算法,由Knuth、Morris、Pratt三位前辈提出来的,取了这三个人的名字的头一个字母。KMP算法采用一种预处理,在不匹配发生的时候这个字符串自身包含足够的信息来确定下一个匹配将在哪里开始,以此避免对以前已匹配的字符的重新检查。

首先,我们简单介绍一下KMP算法的主要思路,然后小戴跟大家详细分析一下KMP中预处理待匹配串的过程。

这里我们举一个例子:假设主字符串Text = “abababababababc”,待查找字符串Pattern=” abababc”。我们用两个指针i和j分别表示Text[i – j + 1…i]与Pattern[1…j]匹配。也就是说,i是不断增长的,随着i的增长j相应的变化以达到Text[i]以前的j个字符与Pattern字符串开头的前j个字符相等。当然,j 越大越好了。现在开始匹配Text[i + 1]和Pattern[j + 1],如果他们相等,那当然最好了,我们继续i++并且j++。如果不匹配的话,我们的策略是减少j的值使得Text[i – j + 1…i]与Pattern[1…j]相等且Text[i + 1]与Pattern[j + 1]也匹配。当j == Pattern串长度的时候,我们就说Pattern串是Text的子串。

我们来看一下KMP算法具体的处理过程:

i = 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
T = a b a b a b a b a b  a  b  a  b  c
P = a b a b a b c
j = 1 2 3 4 5 6 7

当i = j = 6时,Text[7] != Pattern[7],那么我们就要减小j以使得串重新匹配。仔细观察一下我们发现,如果我们要避免重复比较之前已经匹配的字符串,我们需要找到一个最大的j,使得Pattern串的前j个字符与Text[6]之前的j个字符相等。而我们能匹配到这一步,就必定已经有Pattern[6]之前的字符与Text[6]之前的字符相等。换句话说,我们就是要找到一个最大的j,使得Pattern[1…6]的前j个字符与后j个字符相等。

在这里,Pattern[1…4] = “abab”,Pattern[3…6] = “abab”,于是,新的j = 4:

i = 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
T = a b a b a b a b a b  a  b  a  b  c
P =     a b a b a b c
j =     1 2 3 4 5 6 7

以此类推,当匹配到i = 9, j = 7的时候,我们将会需要再一次地改变j。

在我们刚才的讨论中,j在每次失去匹配的时候需要作如何处理其实跟Text串无关,我们仅仅通过考虑Pattern串就能得出。这就是之前我们说的预处理,通过预处理我们可以得到一个数组,其实也就是严蔚敏老奶奶的《数据结构》里说的Next函数。当我们匹配到j,而j + 1不能继续匹配时,这时新的j应该是所有满足Pattern[1…next[j]] == Pattern[next[j]…j]的最大值。

下面是KMP算法的主过程:

void kmpMatch() {
	int j = -1;
	for(int i = 0; i < TEXT_LENGTH; i++) {
		while(j > -1 && text[i] != pattern[j + 1]) {
			j = next[j];
		}
		if(text[i] == pattern[j + 1]) {
			j++;
		}
		if(j == (PATTERN_LENGTH - 1)) {
			printf("Pattern occurs with shift %d\n", i - (PATTERN_LENGTH - 1));
			j = next[j];
		}
	}
}

那么,如果快速而有效地进行这个预处理呢?我个人认为这是理解KMP算法的一大难点。几乎所有的资料或者教科书上都会说求得next数组的过程其实就是对Pattern串的一次自我匹配,然后说由于代码和KMP算法主过程代码类似,于是就一笔带过了。

可是很惭愧啊,我当时学习的时候偏偏就是不能理解这个过程。现在终于对它有一点儿明白了,在这里我把我的理解说出来。

我尽量给它阐述清楚,如有不对的地方欢迎大家拍砖。

     -1 0  1 2 3 4 5  6
P =     a  b a b a b  c
next = -1 -1 0 1 2 3 -1

我们先来观察一下。为了下面表述方便,我们把Pattern串起始的0位的前一位,也就是我们标记出来的-1位显式地表示出来。

我们在这里仍然虚拟出两个指针i和j。其中i是持续增长的,用来遍历整个Pattern串,而j则标识当前Pattern[i – j + 1…i]与Pattern[1…j]能匹配的最长串。

我们从左边开始,当Pattern串连第一个元素”a”,即Pattern[0],都不与Text串匹配时,我们别无它法,只能将整个Pattern串右移一位后再和Text串匹配,所以我们令next[0] = -1。此时j也应该被初始化为-1。

我们继续向前,检查Pattern[1] = b。因为它不等于第一个元素”a”,亦即Pattern[1] != Pattern[next[0] + 1],所以要将j向后退,得到next[1] = -1。

到了Pattern[2] = a时,因为有Pattern[2] == Pattern[0],于是j不用退,而是增加1。next[2] = 0。

同样,到了Pattern[3]时,由Pattern[3] == Pattern[1],next[3] = 1。

以此类推,得到next[4] = 2,next[5] = 3。

好了,当我们考虑Pattern[6]时,我们发现Pattern[6] != Pattern[next[5] + 1],再一次考虑j向后退。此时的j等于3。而前面已经算得next[3] = 1,于是我们令j倒退到1,即从next[5]倒退到next[3]。

然而Pattern[6]居然仍然不等于Pattern[j + 1],我们只好把j继续向后退到next[j]即next[1],这样一来j的值又变为-1。

很明显的,Pattern[6] != Pattern[next[1] + 1]。于是j不会再往后面退了,最终,我们得到next[6] = -1。

下面给出预处理过程的代码:

void getNext() {
	int j = -1;
	next[0] = -1;
	for(int i = 1; i < PATTERN_LENGTH; i++) {
		while(j > -1 && pattern[j + 1] != pattern[i]) {
			j = next[j];
		}
		if(pattern[j + 1] == pattern[i]) {
			j++;
		}
		next[i] = j;
	}
}

KMP算法是一个很有研究价值的问题,它可以在线性时间内解决字符串的匹配,我听说正则表达式的解析器在解决字符串匹配的过程中也是用的KMP算法。

当然,我们解决字符串匹配还有很多其他的方法,比如自动机等。以后我们有可能会提到。

发表在 技术 | 标签为 , , | 留下评论

腾讯的一道面试题(截取字符串)

题目如下:
假设有“123<em>abc</em>456<em>def</em>789”这么一个字符串,写一个函数,可以传入一个字符串,和一个要截取的长度。返回截取后的结果。
要求:
1、<em>和</em>标记不得计算在长度之内。
2、截取后的字符串,要保留原有<em>标签,不过如果最后有一个标签没有闭合,则去掉其开始标签。
示例:
题中的字符串,要截取长度5,则返回的字符串应该为:123ab;要截取长度8,应返回123<em>abc</em>45。

看到题目后,脑海中首先想到的就是用栈来解决。
代码写得很快,实现起来很简单。但是仔细琢磨一下的话,很容易发现还是有很多问题的。 比如对于一些不需要关闭的标签(如:<br />),这段代码会出现问题。
我先把今天写的代码贴出来,等清闲了再容我仔细推敲一下。

function getSubstr($str, $length) {

	$result = '';
	$tag = '';
	$stack = array();

	for($i = 0; $i < $length; $i++) {

		$result .= $str[$i];

		if ($str[$i] == '<') {	// 是 HTML 标签

			// 获取完整的HTML标签,保存到$tag
			for ($j = $i; $str[$j] != '>'; $j++, $length++) {
				$tag .= $str[$j];
			}
			$length++;
			$tag .= $str[$j];

			// 匹配
			if(preg_match('/<([^\/]+)?>/i', $tag, $tmp)) { // 如果是开始标签,则入栈
				array_push($stack, $tmp[1]);
			} else if(preg_match('/' . $stack[count($stack) - 1] . '/', $tag)) { // 如果是结束标签,则出栈
				array_pop($stack);
			}

			$tag = '';
		}
	}

	if (empty($stack)) { // 如果栈为空,则此时 $result 即为要求的串
		return $result;
	}

	while (!empty($stack)) { // 如果栈不为空则清除没有被关闭的开始标签

		$tag = array_pop($stack);
		$pos = strrpos($result, $tag);

		for ($i = $pos - 1; $result[$i] != '>'; $i++) {
			$result[$i] = '';
		}
		$result[$i++] = '';
	}

	return $result;
}
发表在 技术 | 标签为 , , | 留下评论

2010网易编程挑战赛:最大和子序列

原题地址:http://poj.youdao.com/nanti1/C/

两个dp函数。

#include <cstdio>
#include <iostream>

using namespace std;

#define INF 1000000000
int a[50000], sum[50000];
int n;

void init() {
	scanf("%d",&n);
	for(int i = 0; i < n; i++) {
		scanf("%d", &a[i]);
		sum[i] = (i ? sum[i - 1] + a[i] : a[i]);
	}
} // init

int threeSegments() {
	if(n < 3)
		return -INF;
	int f1[50000][2];
	// f1[i][i是否是最后一段的末尾,1:是,0:无限制]
	for(int i = 0; i < n; i++) {

		if(i == 0) {
			f1[i][1] = a[i];
			f1[i][0] = a[i];
		} else {
			f1[i][1] = f1[i - 1][1] + a[i];
			f1[i][0] = max( max(f1[i - 1][1], f1[i - 1][0]), f1[i][1] );
		} // i IF
	} // i FOR

	int f2[50000][2];
	for(int i = 1; i < n; i++) {
		if(i == 1) {
			f2[i][1] = a[0] + a[1];
			f2[i][0] = a[0] + a[1];
		} else {
			f2[i][1] = max(f2[i - 1][1] + a[i], f1[i - 1][0] + a[i]);
			f2[i][0] = max(f2[i - 1][0], f2[i][1]);
		} // i IF
	} // i FOR

	int f3[50000];
	int rt = -INF;
	for(int i = 2; i < n; i++) {
		f3[i] = f2[i - 1][0] + sum[n - 1] - sum[i - 1];
		if(rt < f3[i]) {
			rt = f3[i];
		} // rt IF
	} // i FOR
	return rt;
} // threesegments

int twoSegments() {

	int f1[50000][2];
	for(int i = 0; i < n; i++) {
		if(i) {

			f1[i][1] = max(f1[i - 1][1] + a[i], a[i]);
			f1[i][0] = max(f1[i - 1][0], f1[i][1]);
        } else {

			f1[i][1] = a[i];
			f1[i][0] = a[i];
		} // i IF
	} // i FOR

	int f2[50000][2];
	for(int i = 1; i < n; i++) {
		if(i == 1) {

			f2[i][1] = a[0] + a[1];
			f2[i][0] = a[0] + a[1];
		} else {

			f2[i][1] = max(f2[i - 1][1] + a[i], f1[i - 1][0] + a[i]);
			f2[i][0] = max(f2[i - 1][0], f2[i][1]);
        } // i IF
    } // i FOR

    return f2[n - 1][0];
} // twosegments

int main() {
	int t;
	cin >> t;
	while(t--) {
		init();
		cout << max(threeSegments(), twoSegments()) << "\n";
	} // t WHILE
	return 0;
}
发表在 技术 | 标签为 , , , , | 4 条评论

棋盘覆盖问题的递归实现

问题描述
在一个2^k x 2^k个方格组成的棋盘中,若恰有一个方格与其他方格不同,则称该方格为一特殊方格,且称该棋盘为一特殊棋盘。显然特殊方格在棋盘上出现的位置有4^k种情形。要用下图所示的4种不同形态的L型骨牌覆盖一个给定的特殊棋盘上除特殊方格外的所有方格,且任何2个L型骨牌不得重叠覆盖。易知,在任何一个2^k x 2^k的棋盘覆盖中,用到的L型骨牌个数恰为(4^k-1)/3。

棋盘覆盖问题的递归实现

算法分析
用递归的方法(也称分治法)可以设计解棋盘覆盖问题的一个简捷算法。当k>0时,将2^k x 2^k棋盘分割为4个2^(k-1) x 2^(k-1)子棋盘。特殊方格必位于4个较小子棋盘之一中,其余3个子棋盘中无特殊方格。为了将这3个无特殊方格的子棋盘转化为特殊棋盘,可以用一个L型骨牌覆盖这3个较小棋盘的会和处,则这3个子棋盘上被L型骨牌覆盖的方格就成为该棋盘上的特殊方格,从而将原问题转化为4个较小规模的棋盘覆盖问题。递归地使用这种分割,直至棋盘简化为1×1棋盘。

算法复杂度分析
设T(k)是算法覆盖一个2^k x 2^k棋盘所需的时间,则从算法的分治策略可知,T(k)满足如下递归关系:当k=0时,T(k)=O(1);当k>0时,T(k)=4T(k-1)+O(1)。解此递归关系得T(k)=O(4^k)。由于覆盖一个2^k x 2^k棋盘所需的L型骨牌个数为(4^k-1)/3,故该算法是一个在渐近意义下最优的算法。

#include <iostream>
using namespace std;

int tile = 1;	// L型骨牌的编号(递增)
int board[100][100];	// 棋盘 

/*****************************************************
 *	递归方式实现棋盘覆盖算法
 *	输入参数:
 *	currentRow:当前棋盘左上角的行号
 *	currentCol:当前棋盘左上角的列号
 *	targetRow:当前特殊方格所在的行号
 *	targetCol:当前特殊方格所在的列号
 *	size:当前棋盘的:2^k
 *****************************************************/
void chessBoard(int currentRow, int currentCol, int targetRow, int targetCol, int size) {

	if(size == 1)	// 棋盘方格大小为1,说明递归到最里层
		return;
	int t = tile++;	// 每次递增1
	int s = size / 2;	// 棋盘中间的行、列号(相等的)

	//检查特殊方块是否在左上角子棋盘中
	if(targetRow < currentRow + s && targetCol < currentCol + s) {	// 在

		chessBoard(currentRow, currentCol, targetRow, targetCol, s);

	} else { // 不在,将该子棋盘右下角的方块视为特殊方块

		board[currentRow + s - 1][currentCol + s - 1] = t;
		chessBoard(currentRow, currentCol, currentRow+s-1, currentCol+s-1, s);
	}
	// 检查特殊方块是否在右上角子棋盘中
	if(targetRow < currentRow + s && targetCol >= currentCol + s) {	// 在

		chessBoard(currentRow, currentCol + s, targetRow, targetCol, s);

	} else {	// 不在,将该子棋盘左下角的方块视为特殊方块

		board[currentRow + s - 1][currentCol + s] = t;
		chessBoard(currentRow, currentCol+s, currentRow+s-1, currentCol+s, s);
	}
	//检查特殊方块是否在左下角子棋盘中
	if(targetRow >= currentRow + s && targetCol < currentCol + s) {	// 在

		chessBoard(currentRow + s, currentCol, targetRow, targetCol, s);

	} else {	// 不在,将该子棋盘右上角的方块视为特殊方块

		board[currentRow + s][currentCol + s - 1] = t;
		chessBoard(currentRow+s, currentCol, currentRow+s, currentCol+s-1, s);
	}
	//检查特殊方块是否在右下角子棋盘中
	if(targetRow >= currentRow + s && targetCol >= currentCol + s) {	// 在

		chessBoard(currentRow + s, currentCol + s, targetRow, targetCol, s);

	} else {	// 不在,将该子棋盘左上角的方块视为特殊方块

		board[currentRow + s][currentCol + s] = t;
		chessBoard(currentRow+s, currentCol+s, currentRow+s, currentCol+s, s);
	}
}

int main() {

	int size;
	cout << "输入棋盘的 size (大小必须是2的n次幂):";
	cin >> size;
	int indexX, indexY;
	cout << "输入特殊方格位置的坐标: ";
	cin >> indexX >> indexY;
	chessBoard(0, 0, indexX, indexY, size);

	for(int i = 0; i < size; i++) {
		for(int j = 0; j < size; j++) {
			cout << board[i][j] << "\t";
		}
		cout << endl;
	}
	return 0;
}
发表在 技术 | 标签为 , , , , , | 留下评论

2010网易编程挑战赛:有“道”难题

原题地址:http://poj.youdao.com/nanti2/A/

Description
‘道’是中国古代哲学的重要范畴。用以说明世界的本原、本体、规律或原理。在不同的哲学体系中,其涵义有所不同。老子所写的《道德经》是关于‘道’的经典著作。

道的原始涵义指道路、坦途,以后逐渐发展为道理,用以表达事物的规律性。这一变化经历了相当长的历史过程。《易经》中有“复自道,何其咎”(《小畜》),“履道坦坦”(《履》),“反复其道,七日来复”(《复》),都为道路之义。

《尚书•洪范》中说:“无有作好,遵王之道;无有作恶,遵王之路。无偏无党,王道荡荡;无党无偏,王道平平;无反无侧,王道正直”。这里的道,已经有正确的政令、规范和法度的意思,说明“道”的概念已向抽象化发展。

—- 节选自有道词典(http://dict.youdao.com)

Base64是网络上最常见的用于传输8Bit字节代码的编码方式之一。它把每三个8Bit的字节转换为四个6Bit的字节(3*8 = 4*6 = 24),然后把6Bit再添两位高位0,组成四个8Bit的字节,也就是说,转换后的字符串理论上将要比原来的长1/3。

有道的工程师闲暇之余,将Base64编码改成了Base4编码,即把每个8Bit的字节转换成4个2Bit的字节,然后以4个字符来代替。编码和字符的对应方案如下:
00 —- a
01 —- o
10 —- d
11 —- 空格
这样,编码后的字符串就只会含有字符‘d’,'a’,'o’和空格。

例如字符y ,其ASCII码是121,对应的二进制串是01111001,这样分成 01 11 10 01四块后,用Base4编码后的字符串为”o do”。

有道的工程师很好奇,按照这种编码方式,编码后的字符串中含有多少个完整的dao呢?一个完整的dao由连续的‘d’,'a’,'o’三个字符组成。

Input
第一行有一个正整数n,代表接下来有n个待编码的原串。(1 <= n <= 10)
接下来有n行,每行有一个长度不超过106的原串,原串中的字符可能为ASCII码中除换行符以外的任意可见字符。

Output
共有n行,每行为一个整数k, 表示输入数据中对应的原串用Base4编码后含有多少个完整的dao。

Sample Input
2
www.youdao.com
dict.youdao.com

Sample Output
1
1

Hint
Java时限是标准时限的3倍,而且对于每个输入文件都有100ms的额外IO时间

继续阅读

发表在 技术 | 标签为 , , , , | 留下评论

2010网易编程挑战赛:有道搜索框

原题地址:http://poj.youdao.com/nanti1/B/

Description
在有道搜索框中,当输入一个或者多个字符时,搜索框会出现一定数量的提示,如下图所示:

现在给你N个单词和一些查询,请输出提示结果,为了简化这个问题,只需要输出以查询词为前缀的并且按字典序排列的最前面的8个单词,如果符合要求的单词一个也没有请只输出当前查询词。

Input
第一行是一个正整数N,表示词表中有N个单词。
接下来有N行,每行都有一个单词,注意词表中的单词可能有重复,请忽略掉重复单词。所有的单词都由小写字母组成。
接下来的一行有一个正整数Q,表示接下来有Q个查询。
接下来Q行,每行有一个单词,表示一个查询词,所有的查询词也都是由小写字母组成,并且所有的单词以及查询的长度都不超过20,且都不为空
其中:N<=10000,Q<=10000

Output
对于每个查询,输出一行,按顺序输出该查询词的提示结果,用空格隔开。

Sample Input
10
a
ab
hello
that
those
dict
youdao
world
your
dictionary
6
bob
d
dict
dicti
yo
z

Sample Output
bob
dict dictionary
dict dictionary
dictionary
youdao your
z

继续阅读

发表在 技术 | 标签为 , , , , | 留下评论

2010网易编程挑战赛:另类的异或

原题地址:http://poj.youdao.com/nanti1/A/

Description
对于普通的异或,其实是二进制的无进位的加法
这里我们定义一种另类的异或A op B, op是一个仅由^组成的字符串,如果op中包含n个^,那么A op B表示A和B之间进行n+1进制的无进位的加法。
下图展示了3 ^ 5 和 4 ^^ 5的计算过程
另类的异或

Input
第一行有一个正整数T, 表示下面共有T组测试数据。
接下来T行,每行有一组测试数据,是由空格隔开的三个部分组成:
A B C
A和C是两个十进制整数,B是一个字符串,由n个^组成
1 <= T <= 100, 0<=A,B<2^30, 1<=n<=1000

Output
每个测试数据输出一行,包含一个数字,即该数据的结果,用十进制表示。

Sample Input
2
3 ^ 5
4 ^^ 5

Sample Output
6
6

继续阅读

发表在 技术 | 标签为 , , , , | 留下评论

快速上手监控宝

Gmail 的产品经理Todd Jackson 在一次接受采访时谈到了 Gmail 的监控系统。那是一整面墙的仪表,通过它可以检测到整个系统的健康状况。负责站点的工程师们还佩戴了寻呼装置,哪怕系统只是出现了极小的问题,工程师们也会得到通知。

当然,以目前我们站点的规模来说,还用不上 Google 那么豪华的“监控墙”,但我们也丝毫不能在监控方面有所懈怠。重视监控,才能让我们及时了解到潜在的危险。

当我们要管理一个网站或者是一台服务器的时候,我们至少需要了解目前运行的站点是否可以通过正常的方式访问。试想一下,当用户正开始对你的网站逐渐报以好感的时候突然有一天让他怎么刷新也打不开想要的页面,虽然他还不至于因此而落泪但其沮丧之情是可想而知的。也许只是短短几个小时的宕机,都有可能在用户心中留下不好的印象。从此,给你网站今后用户量的流失埋下了伏笔。因此我们认为,无论您的站点目前正处于多大的规模,你都有必要为自己的网站部署一个最基本的可用性监控策略,并辅以有效的运行状况报告机制。只有这样,才能让你第一时间知晓网站目前的“健康状况”,让一切都在你的运筹帷幄之中。

那么,就让我们从这个最简单的需求开始,逐步深入地探究监控宝为我们提供的各项监控策略解决方案。 继续阅读

发表在 监控宝 | 标签为 , | 留下评论

从“失败鲸”到监控宝

失败鲸

每当 Twitter 因为服务器过载而宕掉的时候,我们就能看到这幅著名的,名为“失败鲸(Fail Whale)”的图片。Twitter 作为微博客这种新型社交模式的鼻祖,自2006年成立以来其用户量正在以每年1 382%的速度高速增长。然而在2007到2008年期间 Twitter 的多次大宕机,也一度让早期的用户们感到非常沮丧。在那一段时间里,和“失败鲸”相关的报道相继被美国各大知名媒体刊载,如此频繁的曝光让“失败鲸”成为了家喻户晓的明星,甚至还有属于自己的网站(http://www.failwhale.com/)。

当然,我们要讨论话题的关键并不是“失败鲸”。

我们经常在新闻中看到每当苹果公司推出新产品的时候,苹果的 Fans 们会在专卖店门前彻夜排队期待次日能第一时间体验到那宛如艺术般的产品。但我们很少看到有用户会对一个运行缓慢,甚至经常宕机的网站给予太多的宽容。

你也许已经习惯了在每次登陆自己运营的网站时那种行云流水般顺畅的感觉。因此,你也认为来自大江南北的所有用户都能像你一样获得如此愉悦的体验,同时你也许会相信即便你站点的访问量再剧增10倍,你的服务器依然可以为用户们提供健壮的服务。这些看起来都是事实,但是,我在此不得不善意地提醒你,也许你正在运营的站点不能像 Twitter 一样赢得如此多用户的青睐和谅解。所以一旦你的网站在某一天因为流量带来的压力过大开始出现访问速度下降,甚至偶尔给访客一张“404的臭脸”时,用户往往会毫不犹豫地选择转向其他的同类站点,更有甚者或许会到讨论区羞辱你的产品。与此同时你还得清楚,当你的竞争对手得知这一消息的时候,他们已经在筹划用户交接仪式了。

这听起来像是居安思危,当然我们也不得不警示自己,凡事只有部署好完备的解决方案才能做到临危不乱。我们只有做好充分的准备,才能阻止噩梦的发生。当我们知道发生了什么事情,才能明白如何解决。

我们应该让自己的服务器提供最高效的系统性能。富有经验的 webmasters 通常会为自己管理的服务器配置一套完备的监控策略,当服务器的性能突然低于它应有的水平的时候,问题可能来自正在执行的进程、内存的使用率、磁盘的性能、服务器I/O吞吐量、CPU的压力等等。在预算有限的今天,理解如何优化系统性能比以往任何时候都重要。要实现它的前提是,你必须充分了解自己的服务器的运行情况,从而找到真正的瓶颈所在。

监控宝

我们接下来将要讨论到的内容将主要围绕着对站点和服务器的监控策略展开。我们首先要讨论一些普遍的方法和基础的工具,来帮助读者对如何处理基本的服务器性能问题有个基本的了解。然后我们要着重介绍一个更高效的解决方案——通过使用监控宝(www.jiankongbao.com)来为站点部署更简单但实用的监控策略。

在敏捷思想盛行的今天,对于大部分的中小型网站来说自建一套完备的监控系统无论从人力或财力上来说都不会是科学的策略。而监控宝则为我们提供了一个降低监控成本的最佳选择。

监控宝以云服务的方式,为各种部署形式的网站提供服务,无论你使用的是虚拟主机、托管主机还是虚拟化主机(VPS)都能体验到监控宝强大的功能。监控宝使用包括HTTP、Ping、DNS、FTP、SMTP、POP、IMAP、TCP等在内的各种网络协议进行站点监控,确保符合用户更多的个性化需求。此外我们还可以使用 SNMP 协议监控服务器性能和容量,支持各种服务器包括Linux、Windows、BSD、Mac等。更让人欣喜的是,现在监控宝已支持服务层监控,可监控的对象包括Apache、Lighttpd、Nginx、MySQL。如此丰富的功能,可以帮助我们更好地提升站点可用性,并对设备的运行状况进行监控。即便是大型网站,也可以利用监控宝来作为自身监控系统之外的有效补充。除此之外监控宝简单而友好的界面可以让稍欠经验的用户也能快速上手,清晰易读的监控报告则让我们得以从纷繁复杂的数据报告中抽身而出。

接下来的几天,让我们一起来讨论如何从基础应用开始,一步一步使用监控宝打造我们的服务器监控解决方案。

发表在 监控宝 | 标签为 , | 留下评论

解决Visual C++ 编译器中混合 .c 文件时收到 C1853 预编译头错误的方法

当 Visual C++ 项目启用了预编译头 (Precompiled header) 功能时,如果项目中同时混合有 .c 和 .cpp 源文件,则可能收到 C1853 编译器错误:fatal error C1853: ‘pjtname.pch’ precompiled header file is from a previous version of the compiler, or the precompiled header is C++ and you are using it from C (or vice versa)(致命错误C1853: “filename.pch”预编译头文件来自编译器的早期版本,或者预编译头为C++ 而在C 中使用它(或相反))。

该错误是因为当项目中混合了 .cpp 和 .c 文件时,编译器会对它们采取不同的编译方式(主要是因为对函数声明的处理方式不同),因而不能共用一个预编译头文件。在 VC++ 中,默认的预编译头文件是针对 C++ 的 (stdafx.h 和 stdafx.cpp),当然也可以创建针对 C 的预编译头。有趣的是,在旧版的 VC++ 中,这个错误的提示很具有误导性:fatal error C1853: ‘xxx.pch’ is not a precompiled header file created with this compiler. 常常让人摸不着头脑。应该说,在新版中的这个提示是有所改进的。不过在网上搜索一番,对这个问题往往都是建议对整个项目取消预编译头的设置。这显然不是一个好的解决方案。对于一个比较大的工程来说,使用预编译头可以使总的编译时间大大减少。因而保留预编译头的设置才是比较好的解决方案。搜索 MSDN,针对不同的情况,可以有不同的解决方案:

继续阅读

发表在 学习, 技术 | 标签为 , , | 2 条评论