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

原创 2012年03月23日 17:54:02

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


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

 

Python技术巧妙破解Google计算题。

开头先讲一下自己的亲身经历,05年的时候,也就是12年前,我去T公司面试,当时T公司在这个城市非常有名,有很多高手(号称小微软).我当时也是抱着初生牛犊不怕虎,想去会一会.在通过第一轮的笔试(当时考算...
  • cH3RUF0tErmB3yH
  • cH3RUF0tErmB3yH
  • 2017年12月16日 00:00
  • 142

蓝背抠像 绿背抠像 算法,实时视频抠像算法 视频直播抠像

影视制作中经常会见到蓝色或绿色的幕布,以蓝色或绿色为背景,极大方便了后期处理人员。 本人提供抠像算法可以实现50fps 1080P的实时视频抠像,可以对婚纱这些半透明的区域进行有效抠像,几乎达到专...
  • dengtaocs
  • dengtaocs
  • 2016年11月03日 17:09
  • 1388

连连看游戏核心代码(C++实现)

这两天研究了一下连连看游戏的源代码,感觉它挺简单的,主要就是判断选中的两张图片能否消去。我参考了网上的源代码(抱歉的是,不记得当时下载的网址了,在此对原作者表示深深的歉意!),然后自己把核心代码整理如...
  • u013149325
  • u013149325
  • 2013年12月16日 20:04
  • 2861

Java enum枚举的用法

一. 出现背景: 在JDK1.5之前,我们定义常量是这样的:public static final String RED = “RED”; 在JDK1.5中加入了枚举类型,我们可以把相关的常量分组到一...
  • zdp072
  • zdp072
  • 2014年10月23日 18:20
  • 2237

java相对于c和c++在内存管理上怎样提高了开发效率

java语言相对于c和c++而言,提高了开发者的开发效率,主要得益于java对于内存的管理: 1、java中没有通过使用强制转换指针类型或者通过进行指针运算访问内存的方法 2、java使用...
  • tomcat6666712
  • tomcat6666712
  • 2016年02月22日 10:50
  • 348

Java并发的核心控制机制

转载自:http://frodoking.github.io/2015/07/19/java-concurrent-mechanism/ 在一般性开发中,笔者经常看到很多同学在对待java并发开...
  • he90227
  • he90227
  • 2016年09月27日 14:13
  • 3018

Java与C++实现相同的MD5加密算法

1、Java版 package com.lyz.utils.common; import java.io.UnsupportedEncodingException; import java.secu...
  • l1028386804
  • l1028386804
  • 2015年07月23日 17:38
  • 2105

c++ 程序中实现抛出异常

抛出异常(也称为抛弃异常)即检测是否产生异常,在C++中,其采用throw语句来实现,如果检测到产生异常,则抛出异常。该语句的格式为: throw 表达式;     如果在try语句块的程序段中(包括...
  • WBENTELY
  • WBENTELY
  • 2017年04月19日 14:37
  • 329

视频稳像操作

#include #include #include #include using namespace std; using namespace cv; using namespace cv:...
  • Gone_HuiLin
  • Gone_HuiLin
  • 2016年11月18日 22:08
  • 1428

webstorm显示后缀为php文件时,html代码不高亮解决办法

Ctrl + Alt + S  (设置webstorm打开设置快捷键) 接下来,按照步骤操作即可: 1:找到Editor 下边的file Types ,点击; 2:滚动右...
  • lijie627239856
  • lijie627239856
  • 2017年12月11日 16:52
  • 57
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:java也能写出点点算法-像C++一样去优化核心并发的代码例子1
举报原因:
原因补充:

(最多只允许输入30个字)