java也能写出点点算法-像C++一样去优化核心并发的代码例子1

简介:

java其实更多用来写业务代码,代码写得好不好,关键看抽象能力如何,不过如果你要用java写很核心的插件和高并发的片段,那么可能还是需要注意一些写法,那种写法可能会更好,才能使得并发量提高,而且更少的使用CPU和内存;我最近在一段采集系统访问的java代码,通过过滤器切入到应用中,遇到的一些小细节的调整,感觉还有点意思,以下为收集信息中碰到的两个需要判定的地方(对java优化没有任何要求的,本文纯属扯淡,呵呵):


原始代码片段1(用于判定是否为静态资源):

if(servletPath == null
                || servletPath.endsWith(".gif")
                || servletPath.endsWith(".png")
                || servletPath.endsWith(".jpg")
                || servletPath.endsWith(".bmp")
                || servletPath.endsWith(".js")
                || servletPath.endsWith(".css")
                || servletPath.endsWith(".ico")) return true;


原始代码片段2(用于判定浏览器类型):

        

        if(StringUtils.isEmpty(userAgent)) return null;
        if(userAgent.contains("MetaSr")) return "metasr";
        if(userAgent.contains("Chrome")) return "chrome";
        if(userAgent.contains("Firefox")) return "firefox";
        if(userAgent.contains("Maxthon")) return "maxthon";
        if(userAgent.contains("360SE")) return "360se";
        if(userAgent.contains("Safari")) return "safari";
        if(userAgent.contains("opera")) return "opera";
        if(userAgent.contains("MSIE")) return "ie";



貌似一眨眼的样子,这个代码没啥优化的余地,代码片段1无非是将出现概率高的放在前面,就尽快判定成功,片段2也是这样,但是片段1里头其实有很多请求都不是静态资源,不是静态资源的话就会将7个endwith全部执行一遍下来了,也就是有很多很多的请求都将遍历所有的内容。


endWith做什么?看看源码,就是将字符串最后那部分截取下来(截取和对比串一样长的,然后和对比串对比),那么7个就会发生7次substring,每个substring操作将会生成一个新的java对象(这个大家应该清楚),每个字符串进行对比是按照字符对比的,所以按照4个四个字符进行计算,也就是会发生20多次对比操作(最坏情况),当然说最好的情况就是进行一次substring,4次对比操作(因为对比成功是每个字符对比成功才算成功)。


那么怎么优化呢,这个貌似除了调试顺序,没有太多优化的空间,根据数据你会发现一个规律,对比的结束字符有5种可能性,那么通过结束字符,就可以定位到一到两个字符串上面,所以我们第一个考虑就是取出要对比的那个字符串的结束符,看下结束符是那个字符串的结束符,然后就在一两个字符串上使用endWith,那么概率就很低了,于是乎,我们对代码片段1,做第一步优化就是:

        

if(servletPath == null) return false;
int length = servletPath.length();
char last = servletPath.charAt(length - 1);
        if((last == 'f' && servletPath.endsWith(".gif"))
                || (last == 'g' && (servletPath.endsWith(".png") || servletPath.endsWith(".jpg")))
                || (last == 'p' && servletPath.endsWith(".bmp"))
                || (last == 's' && (servletPath.endsWith(".js")|| servletPath.endsWith(".css")))
                || (last == 'o' && servletPath.endsWith(".ico"))) return true;


此时判定完首字母后,就使用最多2个字符串进行endWith操作,经过测试,在上面最坏的情况下,效率要高出5-8倍,最初的写法在最好的情况下,和现在速度基本保持一致,但是我们上面说了,很多请求都不是静态资源,所以有很多请求都是走的最坏情况。


好了,如果你的代码不需要极高级别的优化,走到这一步已经够用了,或者说这个已经非常非常够用了,虽然优化的思路非常简单,再写就写成C++了;呵呵,不过我喜欢钻牛角尖,我也有点点洁癖,就是要玩什么,就喜欢玩到我认为的至高境界,所以我又再进行分析,画个图看看:



想办法是否可以去掉subtring的操作,也就是不做字符串截取的操作,我看到就那么几个字母,那么就匹配几个字母而已嘛!

于是乎代码有点像C写的了,做了进一步的改动


if(servletPath == null) return false;
int length = servletPath.length();
if(length < 3) return false;
char last = servletPath.charAt(length - 1);
char second = servletPath.charAt(length - 2);
char third = servletPath.charAt(length - 3);
char forth;
if(length > 3 && third != '.')
    forth = servletPath.charAt(length - 4);
else
    forth = 0;
        if((last == 'f' && second == 'i' && third == 'g' && forth == '.')
                || (last == 'g' && ((second == 'n' && third == 'p' && forth == '.') || (second == 'p' && third == 'j' && forth == '.')))
                || (last == 'p' && (second == 'm' && third == 'b' && forth == '.'))
                || (last == 's' && ((second == 'j' && third == '.')|| (second == 's' && third == 'c' && forth == '.')))
                || (last == 'o' && (second == 'c' && third == 'i'))) return true;


改动后的代码,更加像低级语言在写代码,呵呵,不过效率的确提高了一些,这个时候提高就不是太明显了,只是减少substring创创建中间对象生成时间,能到这一步,我想很少有人再去想了,但是但是代码洁癖超级高的话,还会去想,五个字符,最坏情况要匹配5次才能找到自己想要的入口,有没有办法一次性找到,还有,找到后通过路径直接匹配到内容,回到那个图上,看到很像图形结构


首先考虑入口应该如何处理?看到入口全是字母,这些字母的ascii都是数据0-127的,所以我们想直接用字母的ascii作为下标,我们姑且浪费点空间,创建一个长度为128的数组,使用结束字母ascii作为入口位置;


再考虑,进入后该如何算?我们就想通过图上的路由,最终可以找到最后一个字符的就是成立的,此时设计到子节点的查找,理论上这样是最快的,但是,实现起来每一层需要循环到自己要的那个路由,循环体本身对CPU的开销也是有的,而且要构造对象和指针来实现,访问数据需要通过间接访问,理论上的最优化,并不是我们想要的最优,但是我们的优化到此结束了吗?不是,算法行不通,我更喜欢钻牛角尖了,呵呵,


怎么解决第二步无法完成的情况呢?我考虑到虽然不能按照图的结构完成,那么我发现开始字符都是 "."将它抛开算,如果入参不是这样直接错误,另外,剩余的字符,就只有1-2个字符,而且这个字符的ascii都是数据0-127的,我惊喜了,0-127就是在byte的范围内,我可以组织成任何我想要的内容,我此时将两个字符,按照byte拼接,就可以拼接为2个byte位的数字,也就是short int 类型,因为java在JVM内核实现中其实在局部变量中都是一样大的,所以我们就直接用int了,如果int和int匹配就是一个compare,而不是按照每个字符去compare了;

再发现,上面的图结构中,按照路由向下走,每个入口下面最多2个可匹配的内容,所以我们就定死一个结构就是一个入口下两个int,默认为-1,如下(我这里定义的是内部类,写成static是为了静态方法调用):

static class TempNode {
    int c1 = -1;
    int c2 = -1;
}


OK,开始尝试,首先我们初始化我们需要进行对比的信息,就像编译时完成一些东西一样:

我们首先初始化一个128长度的数组:

private static TempNode[] tempNode1 = new TempNode[128];

      private static void addNode(char []chars) {
          int b = (int)chars[0];
          TempNode node = tempNode1[b];
          if(node == null) {
             node = new TempNode();
             tempNode1[b] = node;
          }
          int size = chars.length;
          int tmp = 0;
          if(size == 2) {
             tmp = (int)(chars[1]);
          }else if(size == 3) {
             tmp = (int)(chars[1] << 8 | chars[2]);
          }
          if(node.c1 == -1) node.c1 = tmp;
          else node.c2 = tmp;
      }

      static {
          addNode(new char[] {'f' , 'i' , 'g'});
          addNode(new char[] {'g' , 'n' , 'p'});
          addNode(new char[] {'g' , 'p' , 'j'});
          addNode(new char[] {'p' , 'm' , 'b'});
          addNode(new char[] {'s' , 'j'});
          addNode(new char[] {'s' , 's' , 'c'});
          addNode(new char[] {'o' , 'c' , 'i'});
      }

这部分访问这个类的时候,就会被初始化掉,初始化的目的就是为了可以被反复使用,而没有将这部分运算时间抛开掉了;此时怎么运算呢?代码描述如下:

1、取出访问字符最后一个字母,根据ascii到tempNode1取出对象,如果对象为空,则就没有任何匹配的,直接返回错误。

2、倒转访问字符,如果长度超过3个,且第三个字符不是“.”,则看第四个字符是不是“.”。

3、上述成立后,根据字符长度拼接处对应的int数据,与取出的TempNode对象中的两个int值进行匹配,得到boolean类型的值返回。


实际代码如下:

       

private static boolean testServletPath(String servletPath) {
     int length = servletPath.length();
     char last = servletPath.charAt(length - 1);
     TempNode node = tempNode1[(int)last];
     if(node == null) return false;
     char second = servletPath.charAt(length - 2);
     char third = servletPath.charAt(length - 3);
     char forth;
     int tmp = -1;
     if(length > 3 && third != '.') {
         forth = servletPath.charAt(length - 4);
         if(forth != '.') return false;
         tmp = (int)(second << 8 | third);
     }else if(third != '.') {
         return false;
     }else {
         forth = 0;
         tmp = second;
     }
     return tmp == node.c1 || tmp == node.c2;
}


我想这已经快到极点了,可以看到上面有进行 ‘.’ 的compare,也就是多进行操作了,那么是否可以直接将这个字符也放入到int中呢,int本身还有2个byte的空位,是的,可以这样做,但是会增加一次位偏移操作,所以和多一次判定,所以总体的效率会看不出多大的区别,但是也是可以那样做的,因为你会发现compare操作会做2次。


也就是只要能挖,在这种代码中能挖出很多宝贝,在高并发的应用系统中,如果在极高并发的代码段,尤其是无论任何程序都会经过的代码段,而且经过多次的那种,那么,这种优化就会产生总体上的提升。


最后,我们看看代码片段2,其实和片段1类似,只不过第一步是考虑endWith,第二个是contains,contarins其实有可能会更加慢,因为会在内部在到相应的字符做一次匹配;就像对比MSIE这个字符串,如果文本中出现多次M开头就又要开始匹配,这里将同一个文本和8种情况对比是很痛苦的事情,其实我们完全可以将上面8个字符串的第一个字母取出来,后面字符串作为byte位,最后组成一个long数据,代表一个数字,放在一个小数组中(此时就是用起始字符作为数组下标了);然后,在使用传入字符串时,每位进行位偏移,将数字和对应下标下的数组看看是否一致,一致就直接返回那个下标记录下的常量,若不是就继续向后了,也就是一遍扫描下来8个字符串可以对比到,而且每次内部对比的时候,就是一个简单的数字对比而已,这就是一种优化。


不论不论怎么说,优化还是归结到能不做的就不做,能少做的就少做,能只做一次就只做一次!

 

目录
相关文章
|
4天前
|
存储 算法 Java
解锁“分享文件”高效密码:探秘 Java 二叉搜索树算法
在信息爆炸的时代,文件分享至关重要。二叉搜索树(BST)以其高效的查找性能,为文件分享优化提供了新路径。本文聚焦Java环境下BST的应用,介绍其基础结构、实现示例及进阶优化。BST通过有序节点快速定位文件,结合自平衡树、多线程和权限管理,大幅提升文件分享效率与安全性。代码示例展示了文件插入与查找的基本操作,适用于大规模并发场景,确保分享过程流畅高效。掌握BST算法,助力文件分享创新发展。
|
17天前
|
存储 人工智能 算法
解锁分布式文件分享的 Java 一致性哈希算法密码
在数字化时代,文件分享成为信息传播与协同办公的关键环节。本文深入探讨基于Java的一致性哈希算法,该算法通过引入虚拟节点和环形哈希空间,解决了传统哈希算法在分布式存储中的“哈希雪崩”问题,确保文件分配稳定高效。文章还展示了Java实现代码,并展望了其在未来文件分享技术中的应用前景,如结合AI优化节点布局和区块链增强数据安全。
|
18天前
|
算法 安全 Java
Java线程调度揭秘:从算法到策略,让你面试稳赢!
在社招面试中,关于线程调度和同步的相关问题常常让人感到棘手。今天,我们将深入解析Java中的线程调度算法、调度策略,探讨线程调度器、时间分片的工作原理,并带你了解常见的线程同步方法。让我们一起破解这些面试难题,提升你的Java并发编程技能!
56 16
|
24天前
|
存储 监控 算法
剖析基于Java算法驱动的智能局域网管控之道
本文探讨了基于Java语言的局域网控制方案,结合链表数据结构与令牌桶算法,解决设备管理和流量调度难题。通过链表灵活存储网络设备信息,实现高效设备管理;令牌桶算法则精准控制流量,确保网络平稳运行。二者相辅相成,为校园、企业等局域网提供稳固高效的控制体系,保障业务连续性和数据安全。
|
21天前
|
算法 搜索推荐 Java
【潜意识Java】深度解析黑马项目《苍穹外卖》与蓝桥杯算法的结合问题
本文探讨了如何将算法学习与实际项目相结合,以提升编程竞赛中的解题能力。通过《苍穹外卖》项目,介绍了订单配送路径规划(基于动态规划解决旅行商问题)和商品推荐系统(基于贪心算法)。这些实例不仅展示了算法在实际业务中的应用,还帮助读者更好地准备蓝桥杯等编程竞赛。结合具体代码实现和解析,文章详细说明了如何运用算法优化项目功能,提高解决问题的能力。
54 6
|
21天前
|
算法 Java C++
【潜意识Java】蓝桥杯算法有关的动态规划求解背包问题
本文介绍了经典的0/1背包问题及其动态规划解法。
43 5
|
27天前
|
SQL Java 数据库连接
如何在 Java 代码中使用 JSqlParser 解析复杂的 SQL 语句?
大家好,我是 V 哥。JSqlParser 是一个用于解析 SQL 语句的 Java 库,可将 SQL 解析为 Java 对象树,支持多种 SQL 类型(如 `SELECT`、`INSERT` 等)。它适用于 SQL 分析、修改、生成和验证等场景。通过 Maven 或 Gradle 安装后,可以方便地在 Java 代码中使用。
173 11
|
1月前
|
存储 监控 算法
探秘局域网桌面监控:深入剖析 Java 语言核心算法
在数字化办公时代,局域网桌面监控如同企业的“智慧鹰眼”,确保工作效率与数据安全。本文以Java为载体,揭示哈希表在监控中的关键应用。通过高效的数据结构和算法,哈希表能快速索引设备连接信息,大幅提升监控的时效性和响应速度。代码示例展示了如何用Java实现设备网络连接监控,结合未来技术如AI、大数据,展望更智能的监控体系,助力企业在数字化浪潮中稳健前行。
|
1月前
|
JSON Java 数据挖掘
利用 Java 代码获取淘宝关键字 API 接口
在数字化商业时代,精准把握市场动态与消费者需求是企业成功的关键。淘宝作为中国最大的电商平台之一,其海量数据中蕴含丰富的商业洞察。本文介绍如何通过Java代码高效、合规地获取淘宝关键字API接口数据,帮助商家优化产品布局、制定营销策略。主要内容包括: 1. **淘宝关键字API的价值**:洞察用户需求、优化产品标题与详情、制定营销策略。 2. **获取API接口的步骤**:注册账号、申请权限、搭建Java开发环境、编写调用代码、解析响应数据。 3. **注意事项**:遵守法律法规与平台规则,处理API调用限制。 通过这些步骤,商家可以在激烈的市场竞争中脱颖而出。
|
9月前
|
存储 算法 Java
【数据结构与算法】1、学习动态数组数据结构(基本模拟实现 Java 的 ArrayList 实现增删改查)
【数据结构与算法】1、学习动态数组数据结构(基本模拟实现 Java 的 ArrayList 实现增删改查)
188 0

热门文章

最新文章