Java列表复制性能测试报告
作者: 杜伟 QQ:184167125
- 1综述
本文对Java的列表(List、Map)的复制性能进行测试,并给出测试结果。在系统开发时,后台业务逻辑中的数据模型操作,需要对大量的列表进行操作,其中包含了列表的复制,这些操作要求必须满足高性能要求。本文对有100万个元素的列表进行了浅拷贝复制测试,希望通过这些测试数据,可以为系统架构的高性能设计做参考依据。作者:杜伟(微信:easydw),转发请注明。
- 2测试结果及意义
.测试结果(List)
- 经过本文的测试,发现Java对列表的浅拷贝性能非常高,100万条的列表(List)拷贝只需1、2毫秒;
- Map的拷贝耗时稍长,约60毫秒;
- 内存占用上,100万条的List占用内存约71M,List复制占用内存约7.6M。
2.测试结果(Map)
- Map的浅拷贝性能一般,100万条的Map拷贝需要60毫秒左右;
private Map<String, Object> _dataMap;
- Map带List拷贝性能更慢,100万条的拷贝耗时160毫秒左右;本项测试包含了创建100万个List<Object>的时间,详情见代码。这也说明了List的拷贝耗时非常少。
private Map<String, List<Object>> _dataListMap;
- 内存占用:Map的内存占用比较多,下面是Map<String, Object>的内存占用,100万个对象的Map竟然占用600M的内存,有点夸张,复制后的内存占用0.8M又有点偏少,几次测试均是如此。感兴趣的朋友可验证下吧。
3.测试意义
在高并发、高性能的后台系统中,最难处理的就是列表的操作同步问题,即:对列表的进行元素的增加和删除操作时,如此时还有对该列表的for循环,就会出现系统错误。通常做法是在增删和for循环的代码段增加同步锁,但是这样往往会导致系统性能瓶颈。
有了以上这些测试数据,在进行系统建模设计及模型高效访问时,就可以进行合理的设计,例如:列表的访问基本不再需要加同步锁,只需要进行拷贝列表即可;Map的操作可以进行封装,避免在业务模块中的循环操作,同样可取消同步锁,这样做可以让系统性能进一步的提高。
测试过程
- 数据准备
1.数据类采用非常简单的模拟类,如下:
public class UserInfo {
public String id = "";
public int num = 0;
}
2.测试类采用单例模式,简化系统测试
public class CopyTest {
private static CopyTest _ct = new CopyTest();
public static CopyTest Instance(){
return _ct;
}
……
}
3.在测试类中创建2个列表来保存数据
_lstdata是原始保存数据列表, _lstdata2是复制后的列表保存对象.
private int count = 1000000;
private int index = 0;
private List<UserInfo> _lstdata2;
private List<UserInfo> _lstdata = new ArrayList<UserInfo>();
private Map<String, Object> _dataMap; // 唯一索引列表
private Map<String, List<Object>> _dataListMap; // 非唯一索引列表
private Map<String, Object> _dataMap2; // 唯一索引列表
private Map<String, List<Object>> _dataListMap2; // 非唯一索引列表
4.数据生成
程序初始化时,创建100个对象并保存到_lstdata列表中。
public void InitData(){
if (_lstdata2 != null)
_lstdata2.clear();
_lstdata.clear();
List<Object> lstdata;
_dataMap = new HashMap<>();
_dataListMap = new HashMap<>();
for (int i=0; i<count; i++){
index = i;
UserInfo ui = new UserInfo();
ui.num = i;
ui.id = String.valueOf(i);
_lstdata.add(ui);
}
if (_dataMap.containsKey(ui.id) == false)
_dataMap.put(ui.id, ui);
if (_dataListMap.containsKey(ui.id) == false) {
lstdata = new ArrayList<Object>();
_dataListMap.put(ui.id, lstdata);
}
else {
lstdata = _dataListMap.get(ui.id);
}
if (lstdata != null)
lstdata.add(ui);
else
LogT.AddErr(String.format("AddItem-非唯一列表添加批量失败:未找到指定key=[%s]对应的列表,继续执行"));
}
}
-
- 浅拷贝说明
本程序采用如下语句实现List列表的复制,这种复制方式是“浅拷贝”,即:两个列表可以自行进行for循环,其中的对象都指向相同的内存区域。
_lstdata2 = new ArrayList<>(_lstdata);
-
- 性能测试
public String CopyData(){
String ret = "";
TimeScan timeScan = new TimeScan();
timeScan.Start(0 ,"00");
_lstdata2 = new ArrayList<>(_lstdata); // 这句话耗时非常少,只是消耗部分内存
timeScan.stopAll();
ret = "列表元素数量:"+_lstdata.size()+", "+_lstdata2.size();
ret += timeScan.getAllTxt();
return ret;
}
public String CopyDic(){
String ret = "";
TimeScan timeScan = new TimeScan();
timeScan.Start(0 ,"00");
_dataMap2 = new HashMap<>(_dataMap);
_dataMap.remove("11");
timeScan.stopAll();
ret = "Map元素数量:"+_dataMap.size()+", "+_dataMap2.size();
ret += "--"+timeScan.getAllTxt();
return ret;
}
带List的Map列表浅拷贝,包括了其中的List的拷贝,这样确保新的Map列表与原列表是独立两个对象。
public String CopyDic2(){
String ret = "";
TimeScan timeScan = new TimeScan();
timeScan.Start(0 ,"00");
_dataListMap2 = new HashMap<>(_dataListMap);
List<Object> lst ;
for (String key:_dataListMap2.keySet()) {
lst = new ArrayList<>(_dataListMap2.get(key));
_dataListMap2.put(key, lst);
}
List<Object> lsttmp = _dataListMap.get("1");
if (lsttmp != null)
lsttmp.add(NewItem());
timeScan.stopAll();
ret += "<p>MapEx元素数量:"+_dataListMap.size()+", "+_dataListMap2.size();
ret += "<p>MapEx中1的元素数:"+_dataListMap.get("1").size() +","+_dataListMap2.get("1").size();
ret += "--"+timeScan.getAllTxt();
return ret;
}