用 JavaScript 取得一个字条串的左边长度为 n 的子串(比如用于显示标题、摘要等),大多数情况下,直接用 substring 等原生方法就可以,但这些方法把汉字和英文字母的长度都算作 1 ,而有时为了排版需要,我们需要把汉字等全角字符的长度算作 2 ,这时就只能自己实现方法了。
首先我们需要做的是判断哪些字符是半角,哪些是全角,还好,用正则能比较方便地判断,比如:
如果给定一个字符串 s ,最容易想到的办法大概是从左到右一个字符一个字符地扫描,每次判断一下当前字符是半角还是全角,如果是半角,则长度加 1 ,如果是全角,则长度加 2 ,直到长度达到 n 。比如:
这个函数基本上可以工作,但是效率上却有那么点低,因为要取多少个字符,就要循环多少次、做多少次正则判断,而且当 n 远大于 s 的长度时,它也不会适时地跳出循环。
注意到我们要取的字串一定是包含在 s.substr(0, n) 中的,我们可以换一种思路,先取出 s.substr(0, n) ,再计算它的长度(汉字长度算 2 ),如果这个长度大于 n 则抛掉最右边的那个字符,重复这个过程直到长度小于等于 n 。即:
这个在 s 的总长与 n 都比较小时性能已经好多了,但是如果 s.length 、 n 都很大时,效率上就会存在比较大的瓶颈。比如,在一篇混杂着全角与半角的 10000 字的帖子中取左边长度为 5000 的子串,第一步取出来的 5000 个字符中可能有一小半都要去掉,这个循环的量还是比较大的。
基本上,常见的方法主要就这两种,一种是从头开妈扫描,另一种是先取出 n 个字符,再把尾部超出的部分去掉。当然,上面两种写法在效率上都还可以再进行优化。不过除此之外,我也想到另外一种不同的方法。
第一步,和第二种方法一样,也是先取出 s.slice(0, n) ,接下来,算一下这个小字符串中全角字符有多少个,比如说有 i 个,那就再在这个小字符串中取前 n’ – i 个字符(n’ 为小字符串的长度)。当然,具体处理上还有一些细节,简单来说,如下图所示:
代码如下:
前两种算法的时间复杂度都是 O(n) ,这个算法的时间复杂度为 O(log(n))。与前两个方法相比,在输入的 s 及 n 规模都较小时效率差不多或稍快一些,不过在处理超长字符串时(比如在长度为 10000 的包含中英文的字符串中截取左边长度为 5000 的子串)它非常快,经我在 Firefox 和 IE 上的测试,基本上可以比前两个方法快出一个数量级甚至更多。
另外,还有一个不大不小的问题,即当 n 比 s.length 大出很多时,其实第一步 s.slice(0, n) 就已经是最终结果了,但上面的代码中没有做判断,还会再继续判断中间有多少双字节字符,在这种情况下会有一定的性能损失。经过综合考虑之后,上面的代码还能再优化为:
这样,这段代码基本上在所有情况下都能表现良好了,呵呵。
关于截取字符串的效率目前就探索到这里了,不过实际应用中,还会遇到一些具体的需求问题,主要是半个字符的处理。比如,如果要求截取字符串“123汉字”左边长度为 4 的子串,应该返回什么呢?返回“123”还是“123汉”?一般的处理是返回“123”,我上面的方法三也是返回“123”,但如果万一遇到变态的需求方,那就得改动代码特殊处理了。
PS:
———-
网上流传着一种看似非常简单的方法,代码如下:
function wrongFunc(s, n) {
return s.slice(0, n – s.slice(0, n).replace(/[x00-xff]/g, “”).length);
}
我没有找到这个方法的原始出处,它看似很简单很酷,但实际上是错误的。比如,求“汉字123测试456”的左边长度为7的子串,按理应该返回“汉字123”,但它会返回“汉字1”,原因是它多减去了后面的“测试”两个字符的长度。
PS 2:
———-
在网上看到另一种非常简单的方法,代码如下:
function func(s, n) {
return s.replace(/([^x00-xff])/g, “$1a”).slice(0, n).replace(/([^x00-xff])a/g, “$1″);
}
这个方法非常巧妙,而且基本上是正确的。说“基本上”是因为它在取“123汉字测试”左边长度为 6 的子串时,它返回的是“123汉字”,而不是“123汉”。当然,这也并不一定就是问题,某些情况下需求可能就是这样。这个方法还可以再改进一下,如下:
function func(s, n) {
return s.slice(0, n).replace(/([^x00-xff])/g, “$1a”).slice(0, n).replace(/([^x00-xff])a/g, “$1″);
}
不过即使这个改进后的版本,还是比上面我提出的方法三的效率要低,因为它虽然写法简单,但有时也会多做一些不必要的计算。
原文来自:oldj