`

Java程序优化

    博客分类:
  • java
阅读更多

 

背景:

 

本人最近在做一个信令相关(电信行业的手机以及固话的通话记录)的项目,数据量非常大,之前也没碰到过这么大数据量,所以写程序的时候,没有意识到程序优化相关的知识,于是就出现了下面的程序。

 

程序一:

 

 

    /**
     * @description: 将字符串解析为List<SignalData>对象
     */
    public static List<SignalData> parseData(String data) {
        String[] listStr = data.split("\r\n");  //data中有多条记录,每条以\r\n换行,每条记录中存在着固定的五个字段的信息,每个字段以,隔开。要求去除数据,装入对象
        List<SignalData> result = new LinkedList<SignalData>();
        for (String dataStr : listStr) {
            String[] fieldArray = dataStr.split(",");
            if (fieldArray.length < 5) {
                continue;
            }
            SignalData item = new SignalData();    
            for (int i = 0; i < fieldArray.length; i++) {  //待优化
                if (i == 0) {
                    item.setAreaCode(fieldArray[i]);
                    continue;
                }
                if (i == 1) {
                    item.setCallingNum(fieldArray[i]);
                    continue;
                }
                if (i == 2) {
                    item.setCalledNum(fieldArray[i]);
                    continue;
                }
                if (i == 3) {
                    item.setCallType(fieldArray[i]);
                    continue;
                }
                if (i == 4) {
                    item.setSignallingTime(fieldArray[i]);
                    continue;
                }
            }
            result.add(item);
        }
        return result;
    }

 

 

上面的代码,猛一看,for循环体中还有continue,感觉像是优化的程序。 于是就放上去运行了,之后又看了看,突然灵光一闪,感觉不对劲,既然每个字段都有数据,我为什么不直接从数组中取呢,还要循环,真是自己作死啊,好,发现一处问题,这样的程序我都看不下去了,于是赶紧优化了下,于是出现下面的代码;

 

优化后:

 

 

    /**
     * @description: 将字符串解析为List<SignalData>对象
     */
    public static List<SignalData> parseData(String data) {
        String[] listStr = data.split("\r\n");
        List<SignalData> result = new LinkedList<SignalData>();
        
        for (String dataStr : listStr) {
            if(null==dataStr||dataStr.trim().equals("")) continue;
            String[] fieldArray = dataStr.split(",");
            if (fieldArray.length < 5) {
                continue;
            }
            SignalData item = new SignalData();
            item.setAreaCode(fieldArray[0]);  //优化之后
            item.setCallingNum(fieldArray[1]);
            item.setCalledNum(fieldArray[2]);
            item.setCallType(fieldArray[3]);
            item.setSignallingTime(fieldArray[4]);
            result.add(item);
        }
        return result;
    }

 

 

 

通过这个程序我大概算了一下,平均每天8G数据,每条数据50byte,  总共是 20 * 1024 *  1024  *  8 = 167772160 条记录, 而上面的每个循环体在取数据的时候,优化之前和优化之后,在判断数组下标相等的时候每条数据总共要花掉15次, 于是  20 * 1024 *  1024  *  8  *   15 = 2516582400 ,看着数字,我的内心是崩溃的,自己的失误,每天造成多了这么多次的运算,不仅程序变慢,还要要多浪费多少电,啊啊啊啊!

 

 

 

程序二:

 

 

优化前:

 

 

    @Override
    public void consume(ConsumerRecord<String, String> record) {
        try {
            //这里面是从kafka中pull数据,
            // pushPool 是一个静态的线程池对象,ParseAndPushTask是一个线程,record.value()就是上面的程序中的一批数据记录,
            pushPool.execute(new ParseAndPushTask(record.value()));  // 问题部分
        } catch (Exception e) {
            logger.error("error in PushBusinessTreate:", e);
        }
    }

 

 

 

这段程序,是从kafka中拉的数据,数据量就是上面的量,当写出这个程序,发布之后,直接OOM了。出现OOM赶紧停掉服务,想了一下,数据量这么大,每pull一条数据,都会生成一个线程,线程中的成员变量,也就是数据,至少在2kb的数据,也就是说,每条记录都要在堆内存中开辟2kb的数据,加上数据量大,不OOM都不正常了。。。。。

 

 

优化后:

 

 

    @Override
    public void consume(ConsumerRecord<String, String> record) {
        try {
		PushService.receiveDataFromProducer(record.value());   //这里使用了一个静态方法
		} catch (Exception e) {
			logger.error("error in PushBusinessTreate:", e);
		}
    }

 

 

修改之后,运行正常,这样,又节省了无数次的GC 啊 

 

 

程序三:

 

 

    public static List<AccountAndSubInfoVo> queryPushSubscriberData(Set<String> subscriptionIds) throws Exception {
        List<AccountAndSubInfoVo> result = new LinkedList<AccountAndSubInfoVo>();
        for (String subscriptionId : subscriptionIds) {
            String subscriberId = jedisCluster.get(Constants.SUBSCRIPTION + subscriptionId); // 获取订阅者ID
            if (subscriberId == null || "".equals(subscriberId)) {
                continue;
            }
            Map<String, String> map = jedisCluster.hgetAll(Constants.SUBSCRIBER + subscriberId);
            if (map != null) {
                AccountAndSubInfoVo vo = mapToBean(map, AccountAndSubInfoVo.class);  //待优化地方**********
                vo.setSubscriptionId(subscriptionId);
                vo.setSubscriberId(subscriberId);
                result.add(vo);
            }

        }

        return result;
    }
    
    public static <T> T mapToBean(Map<String, String> map, Class<T> obj) throws Exception {
        if (map == null) {
            return null;
        }
        Set<Entry<String, String>> sets = map.entrySet();
        T t = obj.newInstance();
        Method[] methods = obj.getDeclaredMethods();
        for (Entry<String, String> entry : sets) {
            String str = entry.getKey();
            String setMethod = "set" + str.substring(0, 1).toUpperCase() + str.substring(1);
            for (Method method : methods) {
                if (method.getName().equals(setMethod)) {
                    method.invoke(t, entry.getValue());
                }
            }
        }
        return t;
    }

 

 

 

 上面的情况大概是这样的,从redis中取出相关的数据(redis中的数据类型名称已经确定),取出形式为Map类型,然后呢,我需要把Map类型转换为对象类型,之前一般情况下都是写个反射,或者是调用一些API搞定,这次也是同样,但是看了看代码,觉得需要优化,因为反射技术本身就损耗性能,况且我是知道Map中的Key的,而且字段也不多,更有效的方法就是,去掉反射,用常规的setter方法,于是,出现下面的代码

 

优化后:

 

 

    public static List<AccountAndSubInfoVo> queryPushSubscriberData(Set<String> subscriptionIds) throws Exception {
        List<AccountAndSubInfoVo> result = new LinkedList<AccountAndSubInfoVo>();
        
        for (String subscriptionId : subscriptionIds) {
            String subscriberId = jedisCluster.get(Constants.SUBSCRIPTION + subscriptionId); // 获取订阅者ID
            if (subscriberId == null || "".equals(subscriberId)) {
                continue;
            }
            Map<String, String> map = jedisCluster.hgetAll(Constants.SUBSCRIBER + subscriberId);
            if (map != null) {
//                AccountAndSubInfoVo vo = mapToBean(map, AccountAndSubInfoVo.class); 优化程序,反射影响性能 ,
                AccountAndSubInfoVo vo = new AccountAndSubInfoVo();
                vo.setAppId(map.get(Constants.APP_ID));  //定义的常量字符串,下同
                vo.setAppSecret(map.get(Constants.APP_SECRET));
                vo.setPushUrl(map.get(Constants.PUSH_URL));
                vo.setSubscriptionId(subscriptionId);
                vo.setSubscriberId(subscriberId);
                result.add(vo);
            }

        }

        return result;
    }

 

 

 

 我想,这样也肯定会省掉很多时间。

 

 

总结:

 

 

通过上面的程序,让我深深的意识到了,平时在写完代码,review一把是多么的靠谱,不但会让程序更流畅一些,最重要的的是会省很多电,节省能源!以前写Web程序,数据量不大,这些问题也没有注意到,当量发生了变化之后,许多问题都会暴漏出来,大家在看到我的程序之后,都看看自己写的程序有没有类似的问题,有则改之无则加勉,这次特意写出来,分享给大家!

 

 

 

 

 下面分享一个很好的关于java程序优化的链接:

 

http://www.cnblogs.com/chinafine/articles/1787118.html

 

 

 

 

 

 

 

6
4
分享到:
评论
8 楼 henu_zhangyang 2016-03-11  
ruyi574812039 写道
for循环记录数过大,考虑多线程也好吧。。

7 楼 henu_zhangyang 2016-03-11  
java_workblog 写道
看完这个博客,里面回去查查自己的程序

O(∩_∩)O哈哈~
6 楼 henu_zhangyang 2016-03-11  
hq2999 写道
如果是用在移动设备上会更有价值

哪里都一样
5 楼 henu_zhangyang 2016-03-11  
bewithme 写道
太逗了,省电强迫症

要有环保意识
4 楼 ruyi574812039 2016-03-11  
for循环记录数过大,考虑多线程也好吧。。
3 楼 java_workblog 2016-03-10  
看完这个博客,里面回去查查自己的程序
2 楼 hq2999 2016-03-10  
如果是用在移动设备上会更有价值
1 楼 bewithme 2016-03-09  
太逗了,省电强迫症

相关推荐

Global site tag (gtag.js) - Google Analytics