답변:
문서는 꽤 잘 설명 :
HashMap의 인스턴스에는 성능에 영향을주는 두 가지 매개 변수, 즉 초기 용량과로드 팩터가 있습니다. 용량은 해시 테이블의 버킷 수이며 초기 용량은 단순히 해시 테이블이 생성 될 때의 용량입니다. 로드 팩터는 용량이 자동으로 증가하기 전에 해시 테이블이 얼마나 가득 찼는 지 측정합니다. 해시 테이블의 항목 수가로드 팩터 및 현재 용량의 곱을 초과하면 해시 테이블이 버킷 수의 약 두 배를 갖도록 해시 테이블이 다시 해시됩니다 (즉, 내부 데이터 구조가 재 빌드 됨).
일반적으로 기본로드 팩터 (.75)는 시간과 공간 비용간에 좋은 균형을 제공합니다. 값이 클수록 공간 오버 헤드는 줄어들지 만 조회 비용은 증가합니다 (get 및 put을 포함한 대부분의 HashMap 클래스 작업에 반영됨). 초기 용량을 설정할 때 재해시 작업의 수를 최소화하기 위해 맵의 예상 항목 수와로드 계수를 고려해야합니다. 초기 용량이 최대 항목 수를로드 계수로 나눈 값보다 큰 경우 재해시 작업이 발생하지 않습니다.
모든 성능 최적화와 마찬가지로 사물을 조기에 최적화하지 않는 것이 좋습니다 (예 : 병목 현상이 발생한 위치에 대한 하드 데이터가없는 경우).
HashMap
테이크 의 기본 초기 용량 은 16이고로드 팩터는 0.75f (즉, 현재 맵 크기의 75 %)입니다. 부하 계수는 HashMap
용량을 두 배로 늘려야 하는 수준을 나타냅니다 .
예를 들어 같은 용량과 부하 계수의 제품 16 * 0.75 = 12
. 이는 12 번째 키-값 쌍을에 저장 한 후 HashMap
용량이 32가 됨을 나타냅니다 .
실제로, 내 계산에서 "완벽한"부하 계수는 log 2 (~ 0.7)에 더 가깝습니다. 이보다 적은 부하 계수는 더 나은 성능을 제공합니다. 아마도 .75가 모자에서 빠져 나온 것 같습니다.
증명:
버킷이 비어 있는지 여부를 예측하여 체인을 피하고 분기 예측을 활용할 수 있습니다. 비어있을 확률이 0.5를 초과하면 버킷이 비어있을 수 있습니다.
s는 크기를 나타내고 n은 추가 된 키 수를 나타냅니다. 이항 정리를 사용하면 버킷이 비어있을 확률은 다음과 같습니다.
P(0) = C(n, 0) * (1/s)^0 * (1 - 1/s)^(n - 0)
따라서 버킷이 비어 있으면 아마도 비어있을 것입니다
log(2)/log(s/(s - 1)) keys
s가 무한대에 도달하고 추가 된 키 수가 P (0) = .5와 같으면 n / s가 log (2)에 빠르게 접근합니다.
lim (log(2)/log(s/(s - 1)))/s as s -> infinity = log(2) ~ 0.693...
.75
가) 가장 이해하기 쉬운 분수로 반올림되어 log(2)
마술 숫자가 아닌 것 같습니다. JDK 기본값에 대한 업데이트를보고 싶습니다. 구현 위에 언급 한 의견은 다음과 같습니다 .D
HashMap이 용량을 늘리기 위해 소진 할 용량의 양은?
기본적으로로드 팩터는 초기 용량의 0.75 (16)이므로 용량이 증가하기 전에 버킷의 25 %가 해제되어 새 해시 코드가 포함 된 많은 새 버킷이 버킷이 증가한 직후에 존재하게됩니다. 버킷 수
로딩 팩터를 1.0으로 설정하면 매우 흥미로운 일이 발생할 수 있습니다.
hashCode가 888 인 해시 맵에 객체 x를 추가하고 해시 맵에서 해시 코드를 나타내는 버킷이 free이므로 객체 x 가 버킷에 추가되지만 해시 코드가 다른 객체 y를 추가하는 경우 다시 말하십시오. 또한 888 그러면 버킷의 끝에 확실히 객체 y가 추가됩니다 ( 버킷은 키, 값 및 다음을 저장하는 linkedList 구현이기 때문에 ). 이것은 성능에 영향을 미칩니다! 조회를 수행하는 경우 객체 y 가 더 이상 버킷 헤드에 없기 때문에 걸리는 시간이 O (1) 가되지 않습니다.이번에는 동일한 버킷에 몇 개의 항목이 있는지에 따라 다릅니다. 이를 해시 충돌이라고하며로드 팩터가 1보다 작은 경우에도 발생합니다.
더 낮은로드 팩터 = 더 많은 여유 버킷 = 더 적은 충돌 가능성 = 고성능 = 높은 공간 요구 사항.
내가 어딘가에 틀렸다면 나를 바로 잡으십시오.
LinkedList
이라고 Amortized Constant Execution Time
로모그래퍼 및 표시 +
등O(1)+
로부터 문서 :
로드 팩터는 용량이 자동으로 증가하기 전에 해시 테이블이 얼마나 가득 찼는 지 측정합니다.
실제로는 특정 요구 사항에 따라 다르며 초기로드 팩터를 지정하기위한 "거의 규칙"이 없습니다.
HashMap DEFAULT_INITIAL_CAPACITY = 16 및 DEFAULT_LOAD_FACTOR = 0.75f의 경우 HashMap의 최대 모든 항목 수는 16 * 0.75 = 12 입니다. 열세 번째 요소가 추가되면 HashMap의 용량 (배열 크기)이 두 배가됩니다! Perfect illustration이 질문에 답변했습니다 : image from here :
https://javabypatel.blogspot.com/2015/10/what-is-load-factor-and-rehashing-in-hashmap.html
버킷이 가득 차면 살펴 봐야합니다
매우 긴 연결리스트.
그리고 그것은 요점을 물리 치는 것입니다.
여기 네 개의 버킷이있는 예가 있습니다.
지금까지 HashSet에 코끼리와 오소리가 있습니다.
이것은 꽤 좋은 상황입니까?
각 요소에는 0 개 또는 하나의 요소가 있습니다.
이제 HashSet에 두 개의 요소를 더 추가했습니다.
buckets elements
------- -------
0 elephant
1 otter
2 badger
3 cat
이것도 나쁘지 않습니다.
모든 버킷에는 하나의 요소 만 있습니다. 내가 알고 싶다면 팬더가 포함되어 있습니까?
버킷 번호 1을 매우 빠르게 볼 수 있지만 그렇지 않습니다.
거기와
나는 그것이 우리의 컬렉션에 없다는 것을 알았습니다.
고양이가 들어 있는지 알고 싶다면 양동이를 봐
3 번
나는 고양이를 발견한다.
수집.
코알라를 추가하면 그렇게 나쁘지 않습니다.
buckets elements
------- -------
0 elephant
1 otter -> koala
2 badger
3 cat
아마 지금 버킷 번호 1 대신에
하나의 요소
나는 두 가지를 볼 필요가있다.
하지만 적어도 코끼리와 오소리를 볼 필요는 없습니다
고양이.
팬더를 다시 찾고 있다면 양동이에만있을 수 있습니다.
1 번과
수달 이외의 다른 것을 볼 필요가 없습니다.
코알라.
하지만 이제 버킷 번호 1에 악어를 넣고
어디로 가는지 보자.
버킷 번호 1이 계속 커지고
더 커지면 기본적으로 모든 것을 살펴 봐야합니다
찾을 요소
버킷 번호 1에 있어야합니다.
buckets elements
------- -------
0 elephant
1 otter -> koala ->alligator
2 badger
3 cat
다른 버킷에 문자열을 추가하기 시작하면
맞습니다. 문제는 매번 점점 커지고 있습니다
단일 버킷.
버킷이 가득 차지 않도록 어떻게 막을 수 있습니까?
여기서 해결책은
"the HashSet can automatically
resize the number of buckets."
버킷이 가져오고 있음을 HashSet이 인식합니다.
너무 꽉 찼습니다.
이 모든 조회의 이점을 잃어 가고 있습니다.
집단.
그리고 더 많은 버킷을 생성합니다 (일반적으로 이전보다 두 배).
그런 다음 요소를 올바른 버킷에 넣으십시오.
여기에 별도의 기본 HashSet 구현이 있습니다.
연결. 이제 "자체 크기 조정 HashSet"을 만들겠습니다.
이 HashSet은 버킷이
너무 가득 차고
더 많은 버킷이 필요합니다.
loadFactor는 HashSet 클래스의 또 다른 필드입니다.
loadFactor는 당 평균 요소 수를 나타냅니다.
버킷,
우리는 크기를 조정하고 싶습니다.
loadFactor는 시간과 공간의 균형입니다.
버킷이 가득 차면 크기가 조정됩니다.
물론 시간이 걸리지 만
양동이가 길면 시간을 절약 할 수 있습니다.
조금 더 비어 있습니다.
예를 보자.
여기 HashSet이 있습니다. 지금까지 4 개의 요소를 추가했습니다.
코끼리, 개, 고양이 및 물고기.
buckets elements
------- -------
0
1 elephant
2 cat ->dog
3 fish
4
5
이 시점에서 나는 loadFactor,
문지방,
버킷 당 평균 요소 수는 괜찮습니다.
로 0.75입니다.
버킷 수는 buckets.length이며 6이며
이 시점에서 HashSet에는 네 가지 요소가 있으므로
현재 크기는 4입니다.
HashSet의 크기를 조정하겠습니다. 즉, 버킷을 더 추가하고
버킷 당 평균 요소 수가 초과하는 경우
loadFactor.
현재 크기를 버킷으로 나눈 길이입니다.
loadFactor보다 큽니다.
이 시점에서 버킷 당 평균 요소 수
4를 6으로 나눈 값입니다.
4 개의 요소, 6 개의 버킷, 0.67입니다.
0.75로 설정 한 임계 값보다 작으므로
괜찮아.
크기를 조정할 필요가 없습니다.
그러나 이제 woodchuck을 추가한다고 가정 해 봅시다.
buckets elements
------- -------
0
1 elephant
2 woodchuck-> cat ->dog
3 fish
4
5
Woodchuck은 버킷 번호 3으로 끝납니다.
이 시점에서 currentSize는 5입니다.
이제 버킷 당 평균 요소 수
현재 크기를 buckets.length로 나눈 값입니다.
6 개의 버킷으로 나눈 5 개의 요소는 0.83입니다.
그리고 이것은 0.75 인 loadFactor를 초과합니다.
이 문제를 해결하기 위해
아마 양동이
더 비어있어서
버킷 포함
요소는 조금 덜 복잡합니다. 크기를 조정하고 싶습니다.
내 HashSet.
HashSet의 크기를 조정하려면 두 단계가 필요합니다.
먼저 양동이 수를 두 배로 늘리고 양동이는 6 개로
이제 12 개의 버킷이 있습니다.
여기서 0.75로 설정 한 loadFactor는 동일하게 유지됩니다.
그러나 변경된 버킷 수는 12입니다.
동일하게 유지 된 요소의 수는 5입니다.
5를 12로 나눈 값은 약 0.42입니다.
loadFactor,
이제 괜찮습니다.
그러나 우리는 이러한 요소 중 일부가
양동이가 잘못되었습니다.
예를 들어 코끼리.
코끼리는 양동이 번호 2에 있었는데
코끼리의 캐릭터
8이었다.
버킷은 6 개, 마이너스 6은 2입니다.
이것이 2 위로 끝나는 이유입니다.
하지만 이제 12 개의 버킷이 있고 8 mod 12는 8이므로
코끼리는 더 이상 버킷 번호 2에 속하지 않습니다.
코끼리는 버킷 번호 8에 속합니다.
우드 척은 어떻습니까?
Woodchuck은이 모든 문제를 시작한 것입니다.
우드 척은 버킷 번호 3으로 끝났습니다.
9 mod 6이 3이기 때문에
그러나 이제 우리는 9 mod 12를 수행합니다.
9 mod 12는 9, woodchuck은 버킷 번호 9로 이동합니다.
그리고 당신은이 모든 것의 장점을 봅니다.
이제 버킷 번호 3에는 요소가 3 개가 아닌 2 개의 요소 만 있습니다.
여기 코드가 있습니다.
여기서 우리는 HashSet에 별도의 체인을 연결했습니다.
크기 조정을 수행하지 않았습니다.
이제 크기 조정을 사용하는 새로운 구현이 있습니다.
이 코드의 대부분은 동일합니다.
우리는 여전히 그것이 포함되어 있는지 결정합니다
이미 가치.
그렇지 않은 경우 어떤 버킷인지 파악합니다.
에 들어가고
그런 다음 해당 버킷에 추가하고 해당 LinkedList에 추가하십시오.
그러나 이제 currentSize 필드를 증가시킵니다.
currentSize는 숫자를 추적 한 필드입니다.
HashSet의 요소들
우리는 그것을 증가시키고 우리는 볼 것입니다
평균 하중에서
버킷 당 평균 요소 수
우리는 그 분할을 여기에서 할 것입니다.
여기에 약간의 캐스팅을해야합니다.
우리는 두 배를 얻는다.
그런 다음 평균 하중을 현장과 비교합니다
내가 설정 한
예를 들어이 HashSet을 만들 때 0.75는
loadFactor.
평균 하중이 loadFactor보다 큰 경우
즉, 버킷 당 너무 많은 요소가 있음을 의미합니다
평균, 나는 다시 삽입해야합니다.
다시 삽입하는 메소드 구현은 다음과 같습니다.
모든 요소.
먼저 oldBuckets라는 로컬 변수를 만듭니다.
버킷이 현재 서있는 버킷을 말합니다.
모든 것을 크기 조정하기 전에.
참고 아직 새로운 연결 목록 배열을 만들지 않았습니다.
버킷의 이름을 oldBuckets로 바꿉니다.
이제 버킷은 우리 수업의 한 분야 였다는 것을 기억하십시오.
이제 새 배열을 만들려면
링크 된 목록으로 구성되지만 요소의 두 배가됩니다.
처음처럼.
이제 실제로 다시 삽입해야합니다.
나는 모든 오래된 버킷을 반복 할 것입니다.
oldBuckets의 각 요소는 문자열의 LinkedList입니다.
그것은 양동이입니다.
나는 그 양동이를 통해 그 안에 각 요소를 얻을 것이다
버킷.
이제 newBuckets에 다시 삽입하겠습니다.
해시 코드를 얻습니다.
나는 그것이 어떤 인덱스인지 알아낼 것입니다.
이제 새로운 버킷, 새로운 LinkedList를 얻습니다.
문자열과
새 버킷에 추가하겠습니다.
우리가 본 HashSets는 Linked의 배열입니다.
목록 또는 버킷.
자체 크기 조정 HashSet은 일부 비율 또는
capacity = N/0.75
rehash를 피하기 위해 지정 하도록 제안 하지만 내 초기 생각은 방금 설정되었습니다load factor = 1
. 그 접근법에 단점이 있습니까? 왜 요인이 영향을로드합니다get()
및put()
운영 비용?