现在有一批叶子节点,它们通过id,parentId来表示父子级关系。
但是我们希望它们的关系是成树状图的形式展示的。
例如将下列数据:
[{id:1,parentId:null,childrens:null}{id:2,parentId:null,childrens:null},{id:3,parentId:1,childrens:null}
,{id:4,parentId:1,childrens:null},{id:5,parentId:2,childrens:null}]
转换成如下所示的树状结构:
[{id:1,parentId:null,childrens:[{id:3,parentId:1,childrens:null},{id:4,parentId:1,childrens:null}]}
,{id:2,parentId:null,childrens:[{id:5,parentId:1,childrens:null}]}]
首先想到的是通过迭代去实现,很快的写了这么几行代码并验证了正确性。
public static List<Node> getChildrensByNodes(List<Node> nodes,Object parentId){
List<Node> childrens = new ArrayList<Node>();
for(Node node : nodes){
if(node.getParentId() == parentId || parentId.equals(node.getParentId())){
node.setChildrens(getChildrensByNodes(nodes, node.getId()));
childrens.add(node);
}
}
return childrens;
}
但是在实现的过程中就想到了这么一个问题:因为上面的代码是通过不断的迭代遍历所有的叶子节点来构建树状结构的,那么当叶子节点增多,树的广度和深度变大时,这样粗暴的遍历必然会导致效率无法满足需求的问题。因此需要换个方式去迭代处理。
首先想到的是通过将已经加入树的叶子节点从节点集合中取出。但是很快就发现,尽管如此,也只是在遍历的时候去除了无需遍历的部分,但是整个节点集合的遍历还是无法避免。而这才是在数据量变大时引起效率问题的元凶。而且删除节点也是一项新的开销。
那么如何尽可能的避免对所有叶子节点的遍历又准确的拿到所需要的节点呢?
HashMap能够很好的满足这一需求:
因为HashMap对Value的存储是通过对key进行Hash计算后根据一定的逻辑后得出要存储的位置。因此我们从一个HashMap中拿一个指定key的数据是不需要遍历所有的数据的。
具体关于HashMap实现的逻辑在下面的网址中有很好的分析。
http://yikun.github.io/2015/04/01/Java-HashMap%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86%E5%8F%8A%E5%AE%9E%E7%8E%B0/
试着写了个用HashMap来构建树的方法,代码如下
public static List<Node> getTreeByNodes(List<Node> nodes){
Map<Object,List<Node>> nodeMap = new HashMap<Object, List<Node>>();
for(Node node : nodes){
Object parentId = node.getParentId();
List<Node> childrenNodes ;
if(nodeMap.containsKey(parentId)){
childrenNodes = nodeMap.get(parentId);
}else{
childrenNodes = new ArrayList<Node>();
nodeMap.put(parentId, childrenNodes);
}
childrenNodes.add(node);
}
return getTreeNodeByNodeMap(nodeMap,null);
}
private static List<Node> getTreeNodeByNodeMap(Map<Object,List<Node>> nodeMap,Object parentId){
List<Node> childrens = nodeMap.get(parentId);
if(childrens == null){
return null;
}
for(Node node : childrens){
node.setChildrens(getTreeNodeByNodeMap(nodeMap, node.getId()));
}
return childrens;
}
最后写了个测试方法,随机的生成指定数量的叶子节点,代码如下:
public static List<Node> getRamdomNodes(int size){
Integer id;
List<Node> nodes = new ArrayList<Node>();
for(id=1 ; id <= size ; id++){
Integer parentId;
if(id == 1){
parentId = null;
}else{
//得到[1,id-1]之间的随机数
parentId = (int) MathUtils.getRandomByArea(1, id-1);
}
Node node = new Node();
node.setId(id);
node.setParentId(parentId);
node.setName("节点"+id);
nodes.add(node);
}
return nodes;
}
对size分别为100,1000,10000,100000的叶子节点进行处理,
这是我的一组对比数据
List用时(毫秒):[3,12,783,213505]
HashMap用时(毫秒):[1,2,22,76]
最后对HashMap继续增加测试的数据量,当数据量达到一百万时,任然能够在一秒之内处理完毕。
千万级的数据需耗时在10秒左右。到了这个级别,需要想一些新的办法了。
最后附上代码地址:
https://github.com/DeveloperHuang/HJQUtils/tree/master/src/com/huang/tree