java組裝樹形結構List問題
java組裝樹形結構List
實現(xiàn)方式千千萬,下面是本人實現(xiàn)的一種方式。
業(yè)務需求
將一個內關聯(lián)表中的數(shù)據(jù),組裝成樹形結構。
業(yè)務表:使用parent_id關聯(lián)父節(jié)點id,父級根節(jié)點的parent_id為0。

樹形結構
[
{
"id": 1,
"parentId": 0,
"userName": "AA",
"children": [
{
"id": 11,
"parentId": 1,
"userName": "AA-AA",
"children": [
{
"id": 111,
"parentId": 11,
"userName": "AA-AA-AA",
"children": []
}
]
}
]
},
{
"id": 2,
"parentId": 0,
"userName": "BB",
"children": [
{
"id": 22,
"parentId": 2,
"userName": "BB-BB",
"children": []
}
]
}
]java代碼
UserTree實體類:
import java.util.List;
public class UserTree {
private Integer id;
private Integer parentId;
private String userName;
private List<UserTree> children;
public UserTree(Integer id, Integer parentId, String userName) {
this.id = id;
this.parentId = parentId;
this.userName = userName;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getParentId() {
return parentId;
}
public void setParentId(Integer parentId) {
this.parentId = parentId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public List<UserTree> getChildren() {
return children;
}
public void setChildren(List<UserTree> children) {
this.children = children;
}
@Override
public String toString() {
return "UserTree{" +
"id=" + id +
", parentId=" + parentId +
", userName='" + userName + '\'' +
", children=" + children.toString() +
'}';
}
}核心邏輯代碼
import java.util.ArrayList;
import java.util.List;
/**
* 測試用戶樹形結構
* @author 測試
*/
public class TestUserTree {
public static void main(String[] args) {
// 模擬從數(shù)據(jù)庫表中查詢出的 user數(shù)據(jù)list
List<UserTree> userDataList = new ArrayList<>();
userDataList.add(new UserTree(1, 0, "AA"));
userDataList.add(new UserTree(11, 1, "AA-AA"));
userDataList.add(new UserTree(111, 11, "AA-AA-AA"));
userDataList.add(new UserTree(2, 0, "BB"));
userDataList.add(new UserTree(22, 2, "BB-BB"));
// 父級根節(jié)點 list
List<UserTree> rootUserList = new ArrayList<>();
// 從查詢出的數(shù)據(jù)中 獲取 所有父級根節(jié)點
for (UserTree user : userDataList) {
// parentId為0的是父級
if (user.getParentId().equals(0)) {
rootUserList.add(user);
}
}
// 最終的樹形結構list
List<UserTree> userTreeList = new ArrayList<>();
// 構建樹形結構
for (UserTree rootUser : rootUserList) {
UserTree user = buildUserTree(userDataList, rootUser);
userTreeList.add(user);
}
System.out.println(userTreeList);
}
/**
* 遞歸構建樹形結構
* @param userDataList 所有的用戶數(shù)據(jù)list
* @param userTree 用戶對象
* @return
*/
public static UserTree buildUserTree(List<UserTree> userDataList, UserTree userTree) {
List<UserTree> childrenUserList = new ArrayList<>();
for (UserTree user : userDataList) {
// 當前數(shù)據(jù)的 parentId 等于 父節(jié)點的 id,則該數(shù)據(jù)是當前父級節(jié)點的子級。
if (user.getParentId().equals(userTree.getId())) {
// 遞歸調用
childrenUserList.add(buildUserTree(userDataList, user));
}
}
userTree.setChildren(childrenUserList);
return userTree;
}
}最終打印出的就是上方提到的樹形結構。
核心的思路就是,使用遞歸的方式逐級遍歷所有的用戶數(shù)據(jù),找出每一層父級節(jié)點的子級,將子級節(jié)點保存為list賦值給父級的children字段。
java組裝樹形結構優(yōu)化思路
項目中經(jīng)常會遇到前端需要展現(xiàn)樹形結構數(shù)據(jù),比如菜單樹、省市區(qū)聯(lián)動,在小數(shù)據(jù)量的時候,不管用什么算法都可以,但一旦數(shù)據(jù)大,不同算法的差距就會非常的大。
公司的項目中老代碼用的是遞歸方法構建樹結構,2萬多個數(shù)據(jù)就需要跑20s,把生產(chǎn)服務器CPU都跑滿了,于是對該方法進行重構。
思路
- 為了不影響傳入的數(shù)據(jù),需要拷貝原數(shù)據(jù)集合。(增加和刪除頻繁,使用LinkedList結構)
- 拷貝的過程中,一并收集每個節(jié)點的id,后面用來判斷每個節(jié)點的parentId是否存在,不存在則認為是根節(jié)點。(為了在比較的時候更快速,使用Set結構)
- 將集合中的數(shù)據(jù)用parentId作為key,放入map中,得到Map<String, List<TreeNode>>結構數(shù)據(jù)。(利用Java8的Collectors.groupingBy獲?。?/li>
- 遍歷集合,將節(jié)點的id作為key,從第3步組裝的map中獲取子節(jié)點,利用Java的地址引用構建樹,并且將不是根節(jié)點的數(shù)據(jù)從集合移出。
實現(xiàn)代碼 (可以直接運行)
TreeNode
@Data
public class TreeNode {
private String id;
private String code;
private String name;
private String parentId;
private List<TreeNode> children;
}實現(xiàn)代碼
public static List<TreeNode> buildTree(List<Map<String, Object>> oriDataMapList) {
// 判空
if (oriDataMapList == null || oriDataMapList.isEmpty()) {
return new ArrayList<>();
}
// 最終要返回的只保留根節(jié)點的集合,增刪比較多,使用LinkedList
List<TreeNode> respTreeNodeList = new LinkedList<>();
// id集合,后面用來判斷parentId是否在oriTreeNodeList存在,如果存在則說明是子節(jié)點,不存在則認為是根節(jié)點
Set<String> idSet = new HashSet<>(oriDataMapList.size());
// 拷貝數(shù)據(jù)
TreeNode treeNode;
for (Map map : oriDataMapList) {
treeNode = new TreeNode();
treeNode.setId((String) map.get("id"));
treeNode.setCode((String) map.get("code"));
treeNode.setName((String) map.get("name"));
treeNode.setParentId((String) map.get("parentId"));
// 遍歷的時候順便保存id集合
idSet.add(treeNode.getId());
respTreeNodeList.add(treeNode);
}
// 獲取由parentId作為key的map,同一個父節(jié)點的數(shù)據(jù)已經(jīng)被匯總成一個list,通過key就能獲取
Map<String, List<TreeNode>> parentIdMap = respTreeNodeList.stream().collect(Collectors.groupingBy(item -> item.getParentId() != null ? item.getParentId() : "nonParent"));
// 設置children,并從根節(jié)點移出不是父節(jié)點的數(shù)據(jù)
Iterator<TreeNode> iterator = respTreeNodeList.iterator();
TreeNode node;
while (iterator.hasNext()) {
node = iterator.next();
// 獲取當前結點的子節(jié)點集合
node.setChildren(parentIdMap.get(node.getId()));
// 如果父id在id集合中存在,則說明他不是根節(jié)點,移除
if (idSet.contains(node.getParentId())) {
iterator.remove();
}
}
return respTreeNodeList;
}測試代碼
public static void main(String[] args) {
// 模擬測試數(shù)據(jù)
List<Map<String, Object>> mapList = new ArrayList<>();
Map<String, Object> map;
for (int i = 0; i < 1000; i++) {
String parentId = String.valueOf(UUID.randomUUID());
map = new HashMap<>();
map.put("id", parentId);
// %03d 長度為3,不夠的補0
map.put("code", String.format("%03d", i));
mapList.add(map);
for (int j = 0; j < 1000; j++) {
map = new HashMap<>();
String id = String.valueOf(UUID.randomUUID());
map.put("id", id);
map.put("code", String.format("%03d", i) + String.format("%03d", j));
map.put("parentId", parentId);
mapList.add(map);
}
}
long start = System.currentTimeMillis();
List<TreeNode> treeNodeList = buildTree(mapList);
String msg = String.format("組裝【%s】條數(shù)據(jù),耗時【%s】ms", mapList.size(), (System.currentTimeMillis() - start));
System.out.println(msg);
}運行結果
組裝【1001000】條數(shù)據(jù),耗時【417】ms
結論:可以看到,跑一百萬的數(shù)據(jù)耗時只有不到0.5s,速度大大優(yōu)化
總結
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
劍指Offer之Java算法習題精講字符串操作與數(shù)組及二叉搜索樹
跟著思路走,之后從簡單題入手,反復去看,做過之后可能會忘記,之后再做一次,記不住就反復做,反復尋求思路和規(guī)律,慢慢積累就會發(fā)現(xiàn)質的變化2022-03-03
java實現(xiàn)ThreadLocal線程局部變量的實現(xiàn)
本文主要介紹了java實現(xiàn)ThreadLocal線程局部變量的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-07-07
Spring Boot實戰(zhàn)之靜態(tài)資源處理
這篇文章主要介紹了Spring Boot實戰(zhàn)之靜態(tài)資源處理,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-01-01
Spring RedirectAttributes參數(shù)跳轉代碼實例
這篇文章主要介紹了Spring RedirectAttributes參數(shù)跳轉代碼實例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-04-04

