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个字符串可以对比到,而且每次内部对比的时候,就是一个简单的数字对比而已,这就是一种优化。


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

 

目录
相关文章
|
3天前
|
存储 算法 数据处理
公司局域网管理中的哈希表查找优化 C++ 算法探究
在数字化办公环境中,公司局域网管理至关重要。哈希表作为一种高效的数据结构,通过哈希函数将关键值(如IP地址、账号)映射到数组索引,实现快速的插入、删除与查找操作。例如,在员工登录验证和设备信息管理中,哈希表能显著提升效率,避免传统线性查找的低效问题。本文以C++为例,展示了哈希表在局域网管理中的具体应用,包括设备MAC地址与IP分配的存储与查询,并探讨了优化哈希函数和扩容策略,确保网络管理高效准确。
|
3天前
|
存储 负载均衡 算法
基于 C++ 语言的迪杰斯特拉算法在局域网计算机管理中的应用剖析
在局域网计算机管理中,迪杰斯特拉算法用于优化网络路径、分配资源和定位故障节点,确保高效稳定的网络环境。该算法通过计算最短路径,提升数据传输速率与稳定性,实现负载均衡并快速排除故障。C++代码示例展示了其在网络模拟中的应用,为企业信息化建设提供有力支持。
35 15
|
6天前
|
传感器 监控 Java
Java代码结构解析:类、方法、主函数(1分钟解剖室)
### Java代码结构简介 掌握Java代码结构如同拥有程序世界的建筑蓝图,类、方法和主函数构成“黄金三角”。类是独立的容器,承载成员变量和方法;方法实现特定功能,参数控制输入环境;主函数是程序入口。常见错误包括类名与文件名不匹配、忘记static修饰符和花括号未闭合。通过实战案例学习电商系统、游戏角色控制和物联网设备监控,理解类的作用、方法类型和主函数任务,避免典型错误,逐步提升编程能力。 **脑图速记法**:类如太空站,方法即舱段;main是发射台,static不能换;文件名对仗,括号要成双;参数是坐标,void不返航。
26 5
|
12天前
|
存储 监控 算法
公司监控上网软件架构:基于 C++ 链表算法的数据关联机制探讨
在数字化办公时代,公司监控上网软件成为企业管理网络资源和保障信息安全的关键工具。本文深入剖析C++中的链表数据结构及其在该软件中的应用。链表通过节点存储网络访问记录,具备高效插入、删除操作及节省内存的优势,助力企业实时追踪员工上网行为,提升运营效率并降低安全风险。示例代码展示了如何用C++实现链表记录上网行为,并模拟发送至服务器。链表为公司监控上网软件提供了灵活高效的数据管理方式,但实际开发还需考虑安全性、隐私保护等多方面因素。
19 0
公司监控上网软件架构:基于 C++ 链表算法的数据关联机制探讨
|
21天前
|
存储 安全 C语言
C++ String揭秘:写高效代码的关键
在C++编程中,字符串操作是不可避免的一部分。从简单的字符串拼接到复杂的文本处理,C++的string类为开发者提供了一种更高效、灵活且安全的方式来管理和操作字符串。本文将从基础操作入手,逐步揭开C++ string类的奥秘,帮助你深入理解其内部机制,并学会如何在实际开发中充分发挥其性能和优势。
|
1月前
|
JavaScript NoSQL Java
接替此文【下篇-服务端+后台管理】优雅草蜻蜓z系统JAVA版暗影版为例-【蜻蜓z系列通用】-2025年全新项目整合搭建方式-这是独立吃透代码以后首次改变-独立PC版本vue版搭建教程-优雅草卓伊凡
接替此文【下篇-服务端+后台管理】优雅草蜻蜓z系统JAVA版暗影版为例-【蜻蜓z系列通用】-2025年全新项目整合搭建方式-这是独立吃透代码以后首次改变-独立PC版本vue版搭建教程-优雅草卓伊凡
179 96
接替此文【下篇-服务端+后台管理】优雅草蜻蜓z系统JAVA版暗影版为例-【蜻蜓z系列通用】-2025年全新项目整合搭建方式-这是独立吃透代码以后首次改变-独立PC版本vue版搭建教程-优雅草卓伊凡
|
1月前
|
存储 算法 Java
解锁“分享文件”高效密码:探秘 Java 二叉搜索树算法
在信息爆炸的时代,文件分享至关重要。二叉搜索树(BST)以其高效的查找性能,为文件分享优化提供了新路径。本文聚焦Java环境下BST的应用,介绍其基础结构、实现示例及进阶优化。BST通过有序节点快速定位文件,结合自平衡树、多线程和权限管理,大幅提升文件分享效率与安全性。代码示例展示了文件插入与查找的基本操作,适用于大规模并发场景,确保分享过程流畅高效。掌握BST算法,助力文件分享创新发展。
|
2月前
|
存储 人工智能 算法
解锁分布式文件分享的 Java 一致性哈希算法密码
在数字化时代,文件分享成为信息传播与协同办公的关键环节。本文深入探讨基于Java的一致性哈希算法,该算法通过引入虚拟节点和环形哈希空间,解决了传统哈希算法在分布式存储中的“哈希雪崩”问题,确保文件分配稳定高效。文章还展示了Java实现代码,并展望了其在未来文件分享技术中的应用前景,如结合AI优化节点布局和区块链增强数据安全。
|
2月前
|
算法 安全 Java
Java线程调度揭秘:从算法到策略,让你面试稳赢!
在社招面试中,关于线程调度和同步的相关问题常常让人感到棘手。今天,我们将深入解析Java中的线程调度算法、调度策略,探讨线程调度器、时间分片的工作原理,并带你了解常见的线程同步方法。让我们一起破解这些面试难题,提升你的Java并发编程技能!
98 16
|
2月前
|
算法 Java C++
【潜意识Java】蓝桥杯算法有关的动态规划求解背包问题
本文介绍了经典的0/1背包问题及其动态规划解法。
60 5

热门文章

最新文章