자바의 hashMap은 내부 배열의 길이가 64, 한 버킷의 요소 수가 8 이상이 되면 해당 배열의 요소들은 연결리스트가 아닌 레드-블랙 트리 기반의 트리노드로 구현된다.
putVal의 로직 중 다음 로직을 보면 binCount를 반복하면서 TREEIFY_THRESHOLD-1 값에 다다를 때 treeifyBin을 호출한다.
...
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
...
1. treeifyBin
첫번째 조건으로 배열의 길이를 측정하고 64 미만이라면 treeify하지 않고 resize()를 호출한다.
그 외 조건에서는 replacementTreeNode를 이용해 TreeNode를 생성하고 해당 버킷에 대한 treeify를 수행한다.
2. treeify
treeify는 해시맵의 내부배열 요소를 Node(연결리스트) 에서 TreeNode(레드-블랙 트리, 연결리스트 통합) 으로 변경하는 작업을 수행한다.
3.balanceInsertion
레드-블랙 트리의 속성에 맞게 노드 구조를 재조정한다.
4.moveRootToFront
메서드 이름만 보면 단순히 root를 해당 버킷의 요소로 할당만 하는것 같지만
추가적으로 root.next에 기존의 연결리스트를 참조한다.
여기서 중요한 점은 hashMap의 TreeNode는 레드-블랙 트리의 특성과 LinkedList의 특성을 동시에 가지며,
이 메서드에서는 treeify를 호출한 버킷의 요소를 기존의 Node(LinkedList) 형식에서 TreeNode(red-black tree)로 변환하고
root(TreeNode).next에 기존의 Node를 연결하여 순서를 계속 기억하고 있다는 점이다.
이 연결 순서를 기억하기 위해 putTreeVal 같은 연산을 시행할 때, 해당 메서드 내부에서는
TreeNode의 값만을 연산하는것이 아니라, Node의 순서또한 업데이트한다.
이는 효율성의 문제로 LinkedList형식으로 돌아갈 때 사용하는 untreeify를 위해서이다.
5. checkInvariants
Node -> TreeNode 형식으로 변환되면서 참조, 속성에 대해서 위반사항이 없는지 확인한다.
treeify를 정리하면서 해시맵이 단순히 연결리스트 -> 레드-블랙 트리 구조로 변경되는것이
아니라 레드-블랙 트리 + 연결리스트 구조를 합친 TreeNode로 변경되는것이 놀라웠다.
그래서 putTreeVal 같은 메서드 호출시 내부 로직을 보면 보면 값을
TreeNode에 추가함과 동시에 연결리스트에도 추가한 값의 순서를 저장한다.
'Java > 자료구조' 카테고리의 다른 글
HashMap - 자바코드 뜯어보기(putTreeVal, removeTreeNode, untreeify) (1) | 2024.02.11 |
---|---|
레드-블랙 트리 (1) | 2024.01.26 |
HashMap - 자바코드 뜯어보기(연결리스트 중심으로) (0) | 2024.01.19 |
Queue(ArrayDeque) - 자바 코드 뜯어보기 (0) | 2024.01.10 |
Stack - 자바 코드 뜯어보기 (1) | 2023.12.30 |