字符串的大小写转换 —— 高效方案

问题:输入一个长度为 N 的字符串 S(其中每个字符都是可打印的 ASCII 码),将其中的大写字母转换为小写字母。

方法1:直接能考虑的转换方法是依次比较 S 中的每个字符,然后对其进行转换。

for(int i = 0; i < N; i++) {
		if ((s[i] >= 'A') && (s[i] <= 'Z') ) {
			s[i] += 'a' - 'A';
		}
}

这样的操作方法存在以下问题: (1)每次仅仅对一个字节进行操作,不能发挥 32 位处理器全字操作的能力; (2)每个字符转换需要进行两次比较,并可能发生条件分支,可能导致处理器的流水线中断。


分析大小写字母的 ASCII 码规律,你会发现:同一个字母的大小写 ASCII 码差异仅仅在第 5 位。 例如, ’A’ 的 ASCII码表示为 0100_0001, ’a’ 的 ASCII 码表示为 0110_0001, 因此从大写字母到小写字母的转换仅仅只需要进行对大写字母的第 5 位或上 1,即 ‘A’ | 0x80 ==> ‘a’.

方法2:

int i;
/*将 char类型指针转换成 unsigned int类型的指针,
这样 p[i]就是一个4字节的整数了,即每次可以处理4字节了
*/
unsigned int *p = (unsigned int *)(s);

// 共有 N>>2 个完整的字
for (i = 0; i < (N>>2); i++) {
	unsigned int tA = 0x41414141;		// 0x41为'A'的ASCII码,故这里为 AAAA
	unsigned int tZ = 0x5A5A5A5A;;		// 0x5A为'Z'的ASCII码,故这里为 ZZZZ
	
	unsigned int d0 = p[i];
	
	// 将每个字节的最高位置为 1(注:ASCII最高位只能为 0)
	unsigned int d = d0 | 0x80808080;	// 0X80 ⇒ 1000_0000

	/*
	字 d 有4个字节,分别表示 4个字符(只算每个字节的低7位),将它们分别与 'A'做差,
	如果字符 ci >= 'A',则对应字节的最高位还是 1;反之,则为 0(次高位向最高位借位)。
	*/
	unsigned int cA = d - tA; 

	// 保留每个字节的最高位,其余位清零
	cA &= 0x80808080;

	/*
	首先,将tZ的每个字节的最高位设为1,
	然后,将它们分别与 d0 的4个字符做差,
	如果字符 'Z' >= ci,则tZ对应字节的最高位还是 1;反之,则为 0(次高位向最高位借位)。
	*/
	unsigned int cZ = (tZ | 0x80808080) - d0;
	
	// 保留每个字节的最高位,其余位清零
	cZ &= 0x80808080;
	
	// cA和cA每个字节的最高位同时为1时, 'A'<= ci <='Z'
	unsigned int c = cZ & cA;
	
	// 将 '1' 移至第5位(从右往左数,从0开始)
	c >>= 2;
	
	d = d0 | c;
	p[i] = d;
}

i--;
// 处理剩余字符 (小于4个字符)
for (i<<=2; i < N; i++) {	
	unsigned char tA = 0x41;
	unsigned char tZ = 0x5A;
	unsigned char d0 = s[i];
	unsigned char d = d0 | 0x80;
	unsigned char cA = d - tA; cA &= 0x80;
	unsigned int cZ = (tZ | 0x80808080) - d0; cZ &= 0x80;
	unsigned char c = cZ & cA;
	c >>= 2;
	d = d0 | c;
	s[i] = d;
}

上述代码中首先将 d0 中四个字节的每个字节的最高位设置位 1, 然后使用两次 32 位减法操作, 检查每个字节是否处于 ’A’ 和 ’Z’ 之间。其关键点是设置每个字节的最高位为 1,其主要目的是隔离每个字节减法之间的借位链。

N = 500000 时,测试 10000次,方法2和方法1所需时间如下:
在这里插入图片描述

扩展:小写字母转为大写字母时,也可利用上述思路,只是此时需要将字母的第5位与0,如:‘a’ & 0xDF ==> ‘A’。

猜你喜欢

转载自blog.csdn.net/fcku_88/article/details/88427573
今日推荐