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

简介:

/*

*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. }

拿到代码之后, 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. }

测试报告

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

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

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. }

不到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实现文字过滤

目录
相关文章
|
存储 缓存 NoSQL
HyperLogLog——用户日活(dau)、月活(mau)统计
HyperLogLog——用户日活(dau)、月活(mau)统计
335 1
|
4月前
|
传感器 API Android开发
雷电模拟器防检测工具, 模拟器防检测 伪装手机,安卓模拟器防检测工具
硬件特征检测通过CPUID指令和显卡信息判断虚拟环境110 系统环境检测通过查找模拟器特有文件和进程112
|
9月前
|
SQL 关系型数据库 MySQL
MySQL进阶突击系列(07) 她气鼓鼓递来一条SQL | 怎么看执行计划、SQL怎么优化?
在日常研发工作当中,系统性能优化,从大的方面来看主要涉及基础平台优化、业务系统性能优化、数据库优化。面对数据库优化,除了DBA在集群性能、服务器调优需要投入精力,我们研发需要负责业务SQL执行优化。当业务数据量达到一定规模后,SQL执行效率可能就会出现瓶颈,影响系统业务响应。掌握如何判断SQL执行慢、以及如何分析SQL执行计划、优化SQL的技能,在工作中解决SQL性能问题显得非常关键。
|
9月前
|
人工智能 Java 测试技术
本地玩转 DeepSeek 和 Qwen 最新开源版本(入门+进阶)
本文将介绍如何基于开源工具部署大模型、构建测试应用、调用大模型能力的完整链路。
1672 126
|
9月前
|
存储 人工智能 自然语言处理
AI 剧本生成与动画创作解决方案体验报告
AI 剧本生成与动画创作解决方案体验报告
397 40
|
9月前
|
存储 弹性计算 关系型数据库
【赵渝强老师】达梦数据库的产品系列
达梦数据库是达梦公司推出的新一代自研数据库,融合分布式、弹性计算与云计算优势,支持超大规模并发事务处理和HTAP混合业务。产品体系包括DM8、DMDSC、DM DataWatch、DMMPP和DMRWC,分别适用于通用关系型数据库、共享存储集群、数据守护集群、大规模数据分析及读写分离场景,满足不同需求并保障高可用性和安全性。
441 36
|
Kotlin
Kotlin中的When表达式:灵活、强大且直观的条件控制工具
Kotlin中的When表达式:灵活、强大且直观的条件控制工具
382 0
|
12月前
|
C语言 开发者
C语言中的模块化编程思想,介绍了模块化编程的概念、实现方式及其优势,强调了合理划分模块、明确接口、保持独立性和内聚性的实践技巧
本文深入探讨了C语言中的模块化编程思想,介绍了模块化编程的概念、实现方式及其优势,强调了合理划分模块、明确接口、保持独立性和内聚性的实践技巧,并通过案例分析展示了其应用,展望了未来的发展趋势,旨在帮助读者提升程序质量和开发效率。
594 5
|
存储 安全 数据管理
深入测评:ONLYOFFICE 8.1 桌面编辑器究竟有多强大?
深入测评:ONLYOFFICE 8.1 桌面编辑器究竟有多强大?
300 1
|
分布式计算 Hadoop Linux
Hadoop节点IP地址和子网掩码配置
【5月更文挑战第1天】
318 5