HashMap에서로드 팩터의 중요성은 무엇입니까?


232

HashMap두 가지 중요한 특성을 가지고 : sizeload factor. Java 설명서를 살펴본 0.75f결과 초기로드 요소라고합니다. 그러나 실제 사용을 찾을 수 없습니다.

어떤 사람이 하중 계수를 설정해야하는 다른 시나리오와 다른 경우에 이상적인 샘플 값을 설명 할 수 있습니까?

답변:


266

문서는 꽤 잘 설명 :

HashMap의 인스턴스에는 성능에 영향을주는 두 가지 매개 변수, 즉 초기 용량과로드 팩터가 있습니다. 용량은 해시 테이블의 버킷 수이며 초기 용량은 단순히 해시 테이블이 생성 될 때의 용량입니다. 로드 팩터는 용량이 자동으로 증가하기 전에 해시 테이블이 얼마나 가득 찼는 지 측정합니다. 해시 테이블의 항목 수가로드 팩터 및 현재 용량의 곱을 초과하면 해시 테이블이 버킷 수의 약 두 배를 갖도록 해시 테이블이 다시 해시됩니다 (즉, 내부 데이터 구조가 재 빌드 됨).

일반적으로 기본로드 팩터 (.75)는 시간과 공간 비용간에 좋은 균형을 제공합니다. 값이 클수록 공간 오버 헤드는 줄어들지 만 조회 비용은 증가합니다 (get 및 put을 포함한 대부분의 HashMap 클래스 작업에 반영됨). 초기 용량을 설정할 때 재해시 작업의 수를 최소화하기 위해 맵의 예상 항목 수와로드 계수를 고려해야합니다. 초기 용량이 최대 항목 수를로드 계수로 나눈 값보다 큰 경우 재해시 작업이 발생하지 않습니다.

모든 성능 최적화와 마찬가지로 사물을 조기에 최적화하지 않는 것이 좋습니다 (예 : 병목 현상이 발생한 위치에 대한 하드 데이터가없는 경우).


14
다른 답변은 capacity = N/0.75rehash를 피하기 위해 지정 하도록 제안 하지만 내 초기 생각은 방금 설정되었습니다 load factor = 1. 그 접근법에 단점이 있습니까? 왜 요인이 영향을로드합니다 get()put()운영 비용?
supermitch

19
항목 수가 = 용량 인로드 팩터 = 1 해시 맵은 통계적으로 상당한 양의 충돌을가집니다 (= 여러 키가 동일한 해시를 생성하는 경우). 충돌이 발생하면 하나의 버킷에서> 1 개의 일치하는 항목이 있으므로 조회 시간이 증가하므로 키를 개별적으로 검사해야합니다. 일부 자세한 수학 : preshing.com/20110504/hash-collision-probabilities
atimb

8
@atimb 님을 팔로우하지 않습니다. loadset 속성은 스토리지 크기를 올바르게 늘릴시기를 결정하는 데만 사용됩니까? -하나의로드 집합이 해시 충돌 가능성을 어떻게 증가시킬 수 있습니까? -해싱 알고리즘은 맵에 몇 개의 항목이 있는지 또는 얼마나 자주 새로운 스토리지 "버킷"을 획득하는지 등을 알지 못합니다. 동일한 크기의 오브젝트 세트의 경우 저장 방법에 관계없이 반복 된 해시 값이 동일한 확률 ...
BrainSlugs83

19
지도의 크기가 클 경우 해시 충돌의 가능성이 줄어 듭니다. 예를 들어 해시 코드가 4, 8, 16 및 32 인 요소는 맵의 크기가 4 인 경우 동일한 버킷에 배치되지만 맵의 크기가 32보다 크면 모든 항목에 자체 버킷이 생깁니다. 초기 크기가 4이고로드 팩터 1.0 (4 버킷, 단일 버킷의 4 개 요소)이 모두있는 맵은이 예제에서로드 팩터 0.75 (8 버킷, 2 개의 버킷이 채워진- "4"요소와 "8", "16", "32"요소).
30

1
@Adelin 조회 비용은 더 높은 값에 대한 충돌이 더 많기 때문에 더 높은로드 팩터에 대해 증가하며 Java가 충돌을 처리하는 방법은 동일한 해시 코드가있는 항목을 데이터 구조를 사용하여 동일한 버킷에 배치하는 것입니다. Java 8부터이 데이터 구조는 이진 검색 트리입니다. 이렇게하면 추가 된 모든 요소가 동일한 해시 코드를 갖는 경우 최악의 경우가 발생하여 조회 최악의 시간 복잡도 O (lg (n))가됩니다.
Gigi Bayte 2

141

HashMap테이크 의 기본 초기 용량 은 16이고로드 팩터는 0.75f ​​(즉, 현재 맵 크기의 75 %)입니다. 부하 계수는 HashMap용량을 두 배로 늘려야 하는 수준을 나타냅니다 .

예를 들어 같은 용량과 부하 계수의 제품 16 * 0.75 = 12. 이는 12 번째 키-값 쌍을에 저장 한 후 HashMap용량이 32가 됨을 나타냅니다 .


3
답은 분명하지만 12 개의 키-값 쌍을 저장 한 직후 용량이 32가되는지 또는 13 번째 항목이 추가 될 때 용량이 변경되어 항목이 삽입되는지 여부를 알려주십시오.
userab

버킷 수가 2 개 증가 했습니까?
LoveMeow

39

실제로, 내 계산에서 "완벽한"부하 계수는 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...

4
수학 대단하다 FTW! 아마도가 ( .75가) 가장 이해하기 쉬운 분수로 반올림되어 log(2)마술 숫자가 아닌 것 같습니다. JDK 기본값에 대한 업데이트를보고 싶습니다. 구현 위에 언급 한 의견은 다음과 같습니다 .D
Decoded

2
난 정말이 답변 등을 원하지만, 나는 거의 당신이 롤 쓴의 이해, 그래서 나는 수학이 정말 내 강점 결코 의미 JavaEE 어플 개발자, 해요
searchengine27을

28

하중 계수는 무엇입니까?

HashMap이 용량을 늘리기 위해 소진 할 용량의 양은?

왜 부하율?

기본적으로로드 팩터는 초기 용량의 0.75 (16)이므로 용량이 증가하기 전에 버킷의 25 %가 해제되어 새 해시 코드가 포함 된 많은 새 버킷이 버킷이 증가한 직후에 존재하게됩니다. 버킷 수

이제 많은 무료 버킷을 유지해야하는 이유와 무료 버킷을 유지하는 것이 성능에 미치는 영향은 무엇입니까?

로딩 팩터를 1.0으로 설정하면 매우 흥미로운 일이 발생할 수 있습니다.

hashCode가 888 인 해시 맵에 객체 x를 추가하고 해시 맵에서 해시 코드를 나타내는 버킷이 free이므로 객체 x 가 버킷에 추가되지만 해시 코드가 다른 객체 y를 추가하는 경우 다시 말하십시오. 또한 888 그러면 버킷의 끝에 확실히 객체 y가 추가됩니다 ( 버킷은 키, 값 및 다음을 저장하는 linkedList 구현이기 때문에 ). 이것은 성능에 영향을 미칩니다! 조회를 수행하는 경우 객체 y 가 더 이상 버킷 헤드에 없기 때문에 걸리는 시간이 O (1) 가되지 않습니다.이번에는 동일한 버킷에 몇 개의 항목이 있는지에 따라 다릅니다. 이를 해시 충돌이라고하며로드 팩터가 1보다 작은 경우에도 발생합니다.

성능, 해시 충돌 및 로딩 팩터 간의 상관 관계

더 낮은로드 팩터 = 더 많은 여유 버킷 = 더 적은 충돌 가능성 = 고성능 = 높은 공간 요구 사항.

내가 어딘가에 틀렸다면 나를 바로 잡으십시오.


2
hashCode가 1- {count bucket} 범위의 숫자로 제거되는 방식에 대해 조금 추가 할 수 있으므로 버킷 수는 아니지만 해시 알고리즘의 최종 결과는 더 큰 범위. HashCode는 전체 해시 알고리즘이 아니며 쉽게 재 처리 할 수있을 정도로 작습니다. 따라서 모든 요소를 ​​동일한 버킷에 저장할 수 있으므로 "자유 버킷"이라는 개념은 없지만 "최소 개수의 무료 버킷"이 있습니다. 오히려 해시 코드의 키 공간이며 capacity * (1 / load_factor)와 같습니다. 40 개 요소, 0.25 하중 계수 = 160 버킷.
user1122069

나는에서 개체에 대한 조회 시간 생각 LinkedList이라고 Amortized Constant Execution Time로모그래퍼 및 표시 +O(1)+
라프

19

로부터 문서 :

로드 팩터는 용량이 자동으로 증가하기 전에 해시 테이블이 얼마나 가득 찼는 지 측정합니다.

실제로는 특정 요구 사항에 따라 다르며 초기로드 팩터를 지정하기위한 "거의 규칙"이 없습니다.


문서는 또한 말한다; "일반적으로 기본로드 팩터 (.75)는 시간과 공간 비용간에 좋은 균형을 제공합니다." 따라서 확실하지 않은 사람은 기본값을 사용하는 것이 좋습니다.
ferekdoley


2

버킷이 가득 차면 살펴 봐야합니다

매우 긴 연결리스트.

그리고 그것은 요점을 물리 치는 것입니다.

여기 네 개의 버킷이있는 예가 있습니다.

지금까지 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은 일부 비율 또는


1

n * 1.5 또는 n + (n >> 1)의 테이블 크기를 선택하면 분할없이 .66666 ~의 부하 계수를 얻을 수 있습니다. 이는 분할이없는 대부분의 시스템, 특히 분할이없는 휴대용 시스템에서 느립니다. 하드웨어.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.