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