过滤字符的性能调优?挤一挤还是有的

/*

*author: ahuaxuan(张荣华)

*date: 2010-05-28

*/

起因

前一段时间和其他系统集成, 另外一个系统对某个参数有一个限制,需要将字符串中的特殊字符过滤掉, 由于需要过滤的字符是对方定义的, 所以对方直接把他们系统中的过滤的代码给我了, 代码如下:

Java代码

  1. private String escape(String s) {
  2. if (s == null) {
  3. return null;
  4. }
  5. StringBuilder sb = new StringBuilder(s.length());
  6. for (int i = 0; i < s.length(); i++) {
  7. char c = s.charAt(i);
  8. if (c == '\\' || c == '+' || c == '-' || c == '!' || c == '('
  9. || c == ')' || c == '^' || c == '[' || c == ']' || c == ':'
  10. || c == '{' || c == '}' || c == '~' || c == '*' || c == '?'
  11. || c == '|' || c == '&' || c == '>' || c == '<') {
  12. sb.append(“ ”);
  13. } else {
  14. sb.append(c);
  15. }
  16. }
  17. return sb.toString();
  18. }
private String escape(String s) {
        if (s == null) {
            return null;
        }

		StringBuilder sb = new StringBuilder(s.length());

		for (int i = 0; i < s.length(); i++) {
			char c = s.charAt(i);

            if (c == '\\' || c == '+' || c == '-' || c == '!' || c == '('
					|| c == ')' || c == '^' || c == '[' || c == ']' || c == ':'
					|| c == '{' || c == '}' || c == '~' || c == '*' || c == '?'
					|| c == '|' || c == '&' || c == '>' || c == '<') {
				sb.append(“ ”);
			} else {
                sb.append(c);
            }

		}

		return sb.toString();
	}

拿到代码之后, ahuaxuan没有作过多的思考, 而是直接把这段代码贴到自己的代码中, 事实证明它运行”良好”.今天重新审视这段代码的时候,发现还是有改进的余地.

分析

特征分析, 上面这段代码的主要作用是将字符串中出现的固定字符替换成空格, 这些需要替换的字符也是我们常见的字符.

面对这样一个需求, 有的程序员会想到replace方法,有的程序员会想到上面的多次||的方法(但是多次||的问题在于如果你的字符不是需要过滤的字符,那么程序就一直会执行到最后一个||,这绝对性能上的浪费阿), 也有的程序员会想到hash的方式. 其实我们的最终目的其实就是:

给定一个字符, 你怎么判断这个字符是否在某个常见的字符列表中.

你肯定不想用迭代, 你也肯定不想用if else if, 你可能比较喜欢用hash, 但是注意这里的”常见字符”这几个字.

是因为它们在ascii 码里所以它们才常见, 还是因为他们常见,所以放到ascii 码里呢, 但不管怎么样, 事实上它们确实是在ascii码里, 也就是说他们之中最大的都不会超过255.

所以这个时候, 我们的方案就浮出水面了, 由于我们只要检查每个char是否==这些常见的char就行了. 说到这里, 你是不是又想起了if else if, 这个只是思维的惯性, 如果你再把这些char想像成一个个数字呢.

本质和方案

于是需求就转换成了---在个数字段中, 找到某些我们预先定义的需要过滤的数字, 并将起替换成空格,而我们定义的数字都是小于255的.

在这样一个需求中, 最快的方式是啥咧, 数组是不, 比如说我们定义的需要过滤的数字是[2, 5], 我们的数字序列是1 2 3 4 5 10 11 110......

那么我们只要遍历这个数字, 然后判断这个数字在我们的过滤数组中是否存在. 我们的过滤数组怎么组织呢?

filterchars = [0, 0, 1, 0, 0, 1],

然后我们只需要判断filterchars[k] > 1就知道是个字符是否是需要过滤的字符了(当然这里要注意数组越界了).

代码如下:

Java代码

  1. public int[] dic = new int[256];
  2. private String filterChars = "\\+-!()^[]:{}~*?|&><";
  3. /**
  4. * User: ahuaxuan
  5. * Date: 2010-5-28
  6. * Time: 13:17:11
  7. */
  8. public CharFilter() {
  9. for (int k = 0; k < filterChars.length(); k++) {
  10. //这里浪费了一些byte.
  11. char c = filterChars.charAt(k);
  12. if (c < 256) {
  13. dic[c] = 1;
  14. }
  15. }
  16. }
  17. public String newEscape(String abc) {
  18. if (abc == null) {
  19. return null;
  20. }
  21. StringBuilder rs = new StringBuilder(abc.length());
  22. for (int k = 0; k < abc.length(); k++) {
  23. char c = abc.charAt(k);
  24. if (c < 256 && dic[c] > 0) {
  25. rs.append(" ");
  26. } else {
  27. rs.append(c);
  28. }
  29. }
  30. return rs.toString();
  31. }
public int[] dic = new int[256];
    private String filterChars = "\\+-!()^[]:{}~*?|&><";

    /**
 * User: ahuaxuan
 * Date: 2010-5-28
 * Time: 13:17:11
 */
    public CharFilter() {
        for (int k = 0; k < filterChars.length(); k++) {
            //这里浪费了一些byte.
            char c = filterChars.charAt(k);
            if (c < 256) {
                dic[c] = 1;
            }
        }
    }

    public String newEscape(String abc) {
        if (abc == null) {
            return null;
        }

        StringBuilder rs = new StringBuilder(abc.length());
        for (int k = 0; k < abc.length(); k++) {
            char c = abc.charAt(k);
            if (c < 256 && dic[c] > 0) {
                rs.append(" ");
            } else {
                rs.append(c);
            }
        }

        return rs.toString();
    }

测试报告

下面我们来看看这段代码:

相同功能的两段代码, 运行一下测试, 测试方法如下:

Java代码

  1. /**
  2. * User: ahuaxuan
  3. * Date: 2010-5-28
  4. * Time: 13:17:11
  5. */
  6. public static void main(String [] args) {
  7. for (int n = 0; n < 10; n++) {
  8. System.out.println("------------------------" + n);
  9. StringBuilder txt = new StringBuilder(1000);
  10. for (int k = 0; k < 100; k++) {
  11. txt.append(UUID.randomUUID().toString());
  12. }
  13. CharFilter pt = new CharFilter();
  14. String abc = txt.toString();
  15. long begin = System.currentTimeMillis();
  16. for (int k = 0; k < 10000; k++) {
  17. pt.escape(abc);
  18. }
  19. System.out.println("escape : " + (System.currentTimeMillis() - begin) + "ms");
  20. begin = System.currentTimeMillis();
  21. for (int k = 0; k < 10000; k++) {
  22. pt.newEscape(abc);
  23. }
  24. System.out.println("new escape : " + (System.currentTimeMillis() - begin) + "ms");
  25. }
  26. }
/**
 * User: ahuaxuan
 * Date: 2010-5-28
 * Time: 13:17:11
 */
    public static void main(String [] args) {
        for (int n = 0; n < 10; n++) {
            System.out.println("------------------------" + n);
            StringBuilder txt = new StringBuilder(1000);
            for (int k = 0; k < 100; k++) {
                txt.append(UUID.randomUUID().toString());
            }

            CharFilter pt = new CharFilter();
            String abc = txt.toString();
            long begin = System.currentTimeMillis();
            for (int k = 0; k < 10000; k++) {
                pt.escape(abc);
            }
            System.out.println("escape : " + (System.currentTimeMillis() - begin) + "ms");

            begin = System.currentTimeMillis();
            for (int k = 0; k < 10000; k++) {
                pt.newEscape(abc);
            }
            System.out.println("new escape : " + (System.currentTimeMillis() - begin) + "ms");
        }

    }

不到1k的一个文本, 每种方法调用100次, 最终得到的结果是:

ahuaxuan 写道

------------------------0
escape : 103ms
new escape : 67ms
------------------------1
escape : 60ms
new escape : 42ms
------------------------2
escape : 61ms
new escape : 48ms
------------------------3
escape : 58ms
new escape : 39ms
------------------------4
escape : 56ms
new escape : 39ms
------------------------5
escape : 80ms
new escape : 39ms
------------------------6
escape : 55ms
new escape : 40ms
------------------------7
escape : 55ms
new escape : 38ms
------------------------8
escape : 55ms
new escape : 38ms
------------------------9
escape : 59ms
new escape : 41ms

如果我们把对每个不到1K的文本的过滤调用上升到10000次, 那么测试结果是:

ahuaxuan 写道

------------------------0
escape : 627ms
new escape : 433ms
------------------------1
escape : 564ms
new escape : 390ms
------------------------2
escape : 624ms
new escape : 390ms
------------------------3
escape : 561ms
new escape : 385ms
------------------------4
escape : 564ms
new escape : 388ms
------------------------5
escape : 600ms
new escape : 391ms
------------------------6
escape : 559ms
new escape : 382ms
------------------------7
escape : 564ms
new escape : 394ms
------------------------8
escape : 573ms
new escape : 395ms
------------------------9
escape : 566ms
new escape : 406ms

文体大小和过滤次数上升之后, 优化之后的方法比原先的方法快了近200ms.

总结:

性能就像女人的胸, 挤一挤还是有的.

当然如果你觉得文中的手段是太入门级了, 那你可以看看ahuaxuan的这篇文章:

使用DFA实现文字过滤

时间: 2024-11-03 22:03:44

过滤字符的性能调优?挤一挤还是有的的相关文章

ANDROID性能调优

http://www.trinea.cn/android/android-performance-demo/#comment-115 本文主要分享自己在appstore项目中的性能调优点,包括同步改异步.缓存.Layout优化.数据库优化.算法优化.延迟执行等.   性能优化专题已完成五部分: 性能优化总纲--性能问题及性能调优方式性能优化第三篇--Java(Android)代码优化性能优化第二篇--布局优化性能优化第一篇--数据库性能优化 性能优化实例    一.性能瓶颈点 整个页面主要由6个

性能调优攻略

关于性能优化这是一个比较大的话题,在<由12306.cn谈谈网站性能技术>中我从业务和设计上说过一些可用的技术以及那些技术的优缺点,今天,想从一些技术细节上谈谈性能优化,主要是一些代码级别的技术和方法.本文的东西是我的一些经验和知识,并不一定全对,希望大家指正和补充. 在开始这篇文章之前,大家可以移步去看一下以前发表的<代码优化概要>,这篇文章基本上告诉你--要进行优化,先得找到性能瓶颈!但是在讲如何定位系统性能瓶劲之前,请让我讲一下系统性能的定义和测试,因为没有这两件事,后面的定

web性能优化之javascript性能调优_javascript技巧

JavaScript 是一个比较完善的前端开发语言,在现今的 web 开发中应用非常广泛,尤其是对 Web 2.0 的应用.随着 Web 2.0 越来越流行的今天,我们会发现:在我们的 web 应用项目中,会有大量的 JavaScript 代码,并且以后会越来越多.JavaScript 作为一个解释执行的语言,以及它的单线程机制,决定了性能问题是 JavaScript 的软肋,也是 web 软件工程师们在写 JavaScript 需要高度重视的一个问题,尤其是针对 Web 2.0 的应用.绝大多

oracle数据库性能调优技术:深入理解单表执行计划

一.概述 这篇文章是数据库性能调优技术的第二篇.上一篇讲解的索引调优是数据库性能调优技术的基础.这篇讲解的深入理解单表执行计划,是数据库性能调优的有力工具. 查询语句可以有多种可选执行计划,如何选择效率最高的执行计划?达梦数据库.oracle数据库.sql server数据库都是采用基于成本的查询优化,对备选执行计划进行打分,选择大家最小的执行计划进行执行.这些内容,我会在后续的几篇文章中进行详细的描述.在此之前,我们首先需要掌握如何理解数据库执行计划.这篇文章讲解只涉及单表操作的执行计划. 达

数据库性能调优技术

一.概述 随着数据库在各个领域的使用不断增长,越来越多的应用提出了高性能的要求.数据库性能调优是知识密集型的学科,需要综合考虑各种复杂的因素:数据库缓冲区的大小.索引的创建.语句改写等等.总之,数据库性能调优的目的在于使系统运行得更快. 调优需要有广泛的知识,这使得它既简单又复杂. 说调优简单,是因为调优者不必纠缠于复杂的公式和规则.许多学术界和业界的研究者都在尝试将调优和查询处理建立在数学基础之上. 称调优复杂,是因为如果要完全理解常识所依赖的原理,还需要对应用.数据库管理系统.操作系统以及硬

oracle数据库性能调优技术:索引调优

一.概述 随着数据库在各个领域的使用不断增长,越来越多的应用提出了高性能的要求.数据库性能调优是知识密集型的学科,需要综合考虑各种复杂的因素:数据库缓冲区的大小.索引的创建.语句改写等等.总之,数据库性能调优的目的在于使系统运行得更快. 调优需要有广泛的知识,这使得它既简单又复杂. 说调优简单,是因为调优者不必纠缠于复杂的公式和规则.许多学术界和业界的研究者都在尝试将调优和查询处理建立在数学基础之上. 称调优复杂,是因为如果要完全理解常识所依赖的原理,还需要对应用.数据库管理系统.操作系统以及硬

《T-SQL性能调优秘笈——基于SQL Server 2012 窗口函数》——1.1 窗口函数的背景

1.1 窗口函数的背景 T-SQL性能调优秘笈--基于SQL Server 2012 窗口函数 在开始学习具体的窗口函数之前,先了解其背景和内涵,会对后续的学习有所帮助.本节先谈谈窗口函数的背景,解释基于集合方式和基于游标/迭代方式进行查询的不同,以及窗口函数如何对二者的差异进行弥补.最后,本节也提到了窗口函数的替代方法,以及为什么窗口函数会优于其替代方法.注意,尽管窗口函数能非常高效地解决很多问题,但在某些案例中,替代方法会好于窗口函数.第4章会具体谈论对窗口函数的优化,解释在什么情况下,计算

Android界面性能调优手册

注:本文是我在 Android 界面性能调优知识的系统性总结,纯属个人碎碎念.秉持开源分享的原则发布本文出来,各位看官有需则取.原文见:https://androidtest.org/android-graphics-performance-pattens/ 界面是 Android 应用中直接影响用户体验最关键的部分.如果代码实现得不好,界面容易发生卡顿且导致应用占用大量内存. 我司这类做 ROM 的公司更不一样,预装的应用一定要非常流畅,这样给客户或用户的第一感觉就是快.又卡又慢的应用体验,会

PHP 性能分析(三): 性能调优实战

在本系列的 第一篇 中,我们介绍了 XHProf .而在 第二篇 中,我们深入研究了 XHGui UI, 现在最后一篇,让我们把 XHProf /XHGui 的知识用到工作中! 性能调优 不用运行的代码才是绝好的代码.其他只是好的代码.所以,性能调优时,最好的选择是首先确保运行尽可能少的代码. OpCode 缓存 首先,最快且最简单的选择是启用 OpCode 缓存.OpCode 缓存的更多信息可以在 这里 找到. 在上图,我们看到启用 Zend OpCache 后发生的情况.最后一行是我们的基准