HashMap'te yük faktörünün önemi nedir?


232

HashMapiki önemli özelliğe sahiptir: sizeve load factor. Java belgelerini inceledim ve 0.75filk yük faktörü olduğunu söylüyor . Ama asıl kullanımını bulamıyorum.

Birisi yük faktörü belirlememiz gereken farklı senaryoların neler olduğunu ve farklı durumlar için örnek ideal değerlerin neler olduğunu açıklayabilir mi?

Yanıtlar:


266

Dokümantasyon oldukça iyi açıklıyor:

HashMap örneğinin performansını etkileyen iki parametresi vardır: başlangıç ​​kapasitesi ve yük faktörü. Kapasite, karma tablosundaki kova sayısıdır ve ilk kapasite, karma tablonun oluşturulduğu andaki kapasitedir. Yük faktörü, hash tablosunun kapasitesi otomatik olarak artırılmadan önce ne kadar dolu olmasına izin verildiğinin bir ölçüsüdür. Karma tablosundaki giriş sayısı yük faktörünün ürününü ve mevcut kapasiteyi aştığında, karma tablosu yeniden şekillendirilir (yani dahili veri yapıları yeniden oluşturulur), böylece karma tablosu kova sayısının yaklaşık iki katına sahiptir.

Genel bir kural olarak, varsayılan yük faktörü (.75), zaman ve alan maliyetleri arasında iyi bir denge sağlar. Yüksek değerler, alan ek yükünü azaltır, ancak arama maliyetini artırır (get ve put dahil HashMap sınıfının işlemlerinin çoğuna yansır). Yeniden doldurma işlemlerinin sayısını en aza indirgemek için başlangıç ​​kapasitesini ayarlarken haritadaki beklenen giriş sayısı ve yük faktörü dikkate alınmalıdır. Başlangıç ​​kapasitesi, maksimum yük sayısının yük faktörüne bölünmesinden büyükse, hiçbir yeniden yenileme işlemi gerçekleşmez.

Tüm performans optimizasyonlarında olduğu gibi, işleri zamanından önce optimize etmekten kaçınmak iyi bir fikirdir (örneğin, darboğazların nerede olduğuna dair sert veriler olmadan).


14
Diğer cevaplar, capacity = N/0.75yeniden şekillenmeyi önlemek için belirtmeyi önermektedir, ancak ilk düşüncem yeni ayarlanmıştı load factor = 1. Bu yaklaşımın dezavantajları olur mu? Yük faktörü neden etki get()ve put()işletme maliyetlerini etkiler ?
supermitch

19
Bir yük faktörü = giriş sayısı ile 1 hashmap = kapasite istatistiksel olarak önemli miktarda çarpışmaya sahip olacaktır (= birden fazla anahtar aynı hash üretirken). Çarpışma meydana geldiğinde, arama süresi artar, çünkü bir kovada> 1 eşleşen giriş olacaktır, bunun için anahtarın eşitlik için ayrı ayrı kontrol edilmesi gerekir. Bazı ayrıntılı matematik: preshing.com/20110504/hash-collision- olasılıklar
Mart'ta

8
Seni takip etmiyorum @atimb; Loadset özelliği yalnızca depolama boyutunu ne zaman artıracağınızı belirlemek için kullanılır. - Bir yük kümesine sahip olmak karma çarpışma olasılığını nasıl artırabilir? - Karma algoritması, haritada kaç öğe bulunduğunu veya ne sıklıkta yeni depolama alanı "kovaları" vb. Aldığını bilmiyor. Aynı boyuttaki herhangi bir nesne kümesi için, ne kadar depolandıklarına bakılmaksızın, tekrarlanan hash değerlerinin aynı olasılığı ...
BrainSlugs83

19
Haritanın boyutu daha büyükse, karma çarpışma olasılığı daha azdır. Örneğin, haritanın boyutu 4 ise, 4, 8, 16 ve 32 hash kodlarına sahip öğeler aynı kovaya yerleştirilir, ancak haritanın boyutu 32'den büyükse, her öğe kendi bir kova alır. Başlangıç ​​boyutu 4 ve yük faktörü 1.0 olan harita (4 kova, ancak tek bir kovadaki 4 elemanın tümü), yük faktörü 0.75 (8 kova, iki kova dolu - "4" elemanı ve "8", "16", "32" elemanları ile).
30

1
@Adelin Arama maliyeti daha yüksek yük faktörleri için artar çünkü daha yüksek değerler için daha fazla çarpışma olacaktır ve Java'nın çarpışmaları işleme biçimi, aynı karma kodlu öğeleri bir veri yapısı kullanarak aynı kovaya koymaktır. Java 8'den başlayarak, bu veri yapısı bir ikili arama ağacıdır. Bu, eklenen tüm öğelerin aynı karma koduna sahip olması durumunda aramayı en kötü durum karmaşıklığı O (lg (n)) yapar.
Gigi Bayte 2

141

HashMapÇekimlerin varsayılan başlangıç ​​kapasitesi 16 ve yük faktörü 0,75f'dir (yani mevcut harita boyutunun% 75'i). Yük faktörü, HashMapkapasitenin hangi seviyede ikiye katlanması gerektiğini temsil eder .

Örneğin kapasite ve yük faktörü olarak ürün 16 * 0.75 = 12. Bu, 12. anahtar / değer çiftini içine kaydettikten sonra HashMapkapasitesinin 32 olduğunu gösterir.


3
Cevabınız açık olsa da, 12 anahtar / değer çiftini sakladıktan hemen sonra kapasitenin 32 olup olmadığını veya 13. giriş eklendiğinde, o sırada kapasite değişip değişmediğini söyleyebilir misiniz?
userab

Bu, kova sayısının 2 arttığı anlamına mı geliyor?
LoveMeow

39

Aslında, benim hesaplamalardan, "mükemmel" yük faktörü log 2 (~ 0.7) daha yakın. Her ne kadar bundan daha düşük herhangi bir yük faktörü daha iyi performans verecektir. Sanırım .75 muhtemelen bir şapkadan çıkarıldı.

Kanıt:

Zincirin önlenmesi ve bir kova boş olup olmadığını tahmin ederek dal tahmininden faydalanılabilir. Bir kova, boş olma olasılığı .5'i aşarsa muhtemelen boştur.

S, eklenen anahtar sayısını ve n sayısını temsil edelim. Binom teoremini kullanarak, bir kepçenin boş olma olasılığı:

P(0) = C(n, 0) * (1/s)^0 * (1 - 1/s)^(n - 0)

Bu nedenle, bir kova daha az varsa muhtemelen boştur.

log(2)/log(s/(s - 1)) keys

S sonsuza ulaştığında ve eklenen anahtar sayısı P (0) = .5 olacaksa, n / s hızla log (2) 'ye yaklaşır:

lim (log(2)/log(s/(s - 1)))/s as s -> infinity = log(2) ~ 0.693...

4
Matematik nerds FTW! Muhtemelen .75anlaşılması en kolay kesire yuvarlanmıştı log(2)ve daha az sihirli bir sayı gibi görünüyor. Ben bunun uygulanması yukarıda bahsedilen yorum ile, JDK varsayılan değerine bir güncelleme görmek isteriz: D
Decoded

2
Bu yanıtı gerçekten sevmek istiyorum, ama bir JavaEE geliştiricisiyim, yani matematik asla benim güçlü takımım değildi, bu yüzden lol ne yazdığınızı çok az anlıyorum
searchengine27

28

Yük faktörü nedir?

HashMap'in kapasitesini arttırması için tüketilecek kapasite miktarı?

Neden yük faktörü?

Yük faktörü varsayılan olarak başlangıç ​​kapasitesinin (16) 0,75'idir, bu nedenle kapasitede bir artış olmadan kovaların% 25'i serbest olacaktır ve bu, yeni hashcode'larla birlikte yeni hashcode'ların artmasından hemen sonra var olduğunu gösteren birçok yeni kova yapar. kova sayısı.

Şimdi neden birçok ücretsiz kova tutmalısınız & ücretsiz kova tutmanın performans üzerindeki etkisi nedir?

Yükleme faktörünü 1.0 olarak ayarlarsanız, çok ilginç bir şey olabilir.

HashCode'unuz 888 olan hashmap'inize x nesnesini eklediğinizi varsayalım. Ayrıca 888 sonra y nesneniz emin KUTU sonunda KUTU eklenecektir ( çünkü kovalar, anahtar, değer ve sonraki depolayan LinkedList uygulama başka bir şey değildir ) şimdi bu bir performans etkisi var! Senin bu yana nesne y Eğer bir arama geçen zaman olacak değil gerçekleştirirseniz kepçenin kafasında artık mevcut O (1)bu sefer aynı kovada kaç tane öğe olduğuna bağlı. Bu arada karma çarpışma denir ve bu yükleme faktörünüz 1'den küçük olduğunda bile gerçekleşir.

Performans, karma çarpışma ve yükleme faktörü arasındaki ilişki?

Düşük yük faktörü = daha fazla boş kova = daha az çarpışma şansı = yüksek performans = yüksek alan gereksinimi.

Bir yerde yanılıyorsam beni düzeltin.


2
HashCode öğesinin 1- {count bucket} aralığındaki bir sayıya nasıl soyulduğu hakkında biraz bilgi ekleyebilirsiniz ve bu nedenle kovaların başına gelmez, ancak hash algoritmasının son sonucu bir daha geniş menzil. HashCode tam karma algoritma değildir, kolayca yeniden işlenebilecek kadar küçüktür. Yani "serbest kovalar" kavramı değil, "minimum boş kova" sayısı vardır, çünkü tüm öğelerinizi aynı kovada saklıyor olabilirsiniz. Aksine, karma kodunuzun kapasite * (1 / load_factor) değerine eşit olan anahtar alanıdır. 40 element, 0.25 yük faktörü = 160 kova.
user1122069

İ bir nesne için arama süresi düşünmek LinkedListolarak ifade edilir Amortized Constant Execution Time, bir ile gösterilen +şekildeO(1)+
Raf

19

Gönderen belgeler :

Yük faktörü, hash tablosunun kapasitesi otomatik olarak artırılmadan önce ne kadar dolu olmasına izin verildiğinin bir ölçüsüdür.

Bu gerçekten sizin özel gereksinimlerinize bağlıdır, bir başlangıç ​​yük faktörü belirlemek için "temel kural" yoktur.


Belgeler ayrıca; "Genel bir kural olarak, varsayılan yük faktörü (.75), zaman ve alan maliyetleri arasında iyi bir denge sağlar." Bu yüzden emin olmayan herkes için varsayılan, iyi bir kural.
ferekdoley


2

Kovalar çok fazla dolarsa, o zaman

çok uzun bağlantılı bir liste.

Ve bu bir nevi noktayı yenmek.

İşte dört kovamın olduğu bir örnek.

Şu ana kadar HashSet'imde fil ve porsuk var.

Bu oldukça iyi bir durum, değil mi?

Her öğenin sıfır veya bir öğesi vardır.

Şimdi HashSet'imize iki öğe daha ekledik.

     buckets      elements
      -------      -------
        0          elephant
        1          otter
         2          badger
         3           cat

Bu da fena değil.

Her kova sadece bir elemana sahiptir. Eğer bilmek istersem, bu panda içerir mi?

Çok hızlı bir şekilde kova 1'e bakabilirim ve değil

orada ve

Koleksiyonumuzda olmadığını biliyorum.

Eğer kedi içerip içermediğini bilmek istersem, kovaya bakarım

3 numara,

Kedi buluyorum, çok çabuk biliyorum ki bizim

Toplamak.

Ya koala eklersem, o kadar da kötü değil.

             buckets      elements
      -------      -------
        0          elephant
        1          otter -> koala 
         2          badger
         3           cat

Belki şimdi 1 numaralı kova yerine sadece

bir eleman,

Twokisine bakmam gerek.

Ama en azından fil, porsuk ve

kedi.

Yine panda arıyorum, sadece kovada olabilir

1 numara ve

Su samuru dışında başka bir şeye bakmak zorunda değilim ve

koala.

Ama şimdi timsahı 1 numaralı kovaya koydum ve sen

bakın belki nereye gidiyor.

1 numaralı kova gittikçe büyüyor ve

daha büyük, o zaman temelde tüm

bulmak için bu unsurlar

1 numaralı kovada olması gereken bir şey.

            buckets      elements
      -------      -------
        0          elephant
        1          otter -> koala ->alligator
         2          badger
         3           cat

Diğer kovalara dize eklemeye başlarsam,

doğru, sorun her geçen gün daha da büyüyor

tek kova.

Kovalarımızın çok fazla dolmasını nasıl önleyebiliriz?

Buradaki çözüm

          "the HashSet can automatically

        resize the number of buckets."

HashSet, kovaların

çok dolu.

Tüm bu aramaların bu avantajını kaybediyor

elementler.

Ve sadece daha fazla kova oluşturacak (genellikle eskisinden iki kez) ve

ardından elemanları doğru kovaya yerleştirin.

İşte temel HashSet uygulamamız ayrı

zincirleme. Şimdi bir "kendini yeniden boyutlandıran HashSet" oluşturacağım.

Bu HashSet, kovaların

çok dolmak ve

daha fazla kovaya ihtiyacı var.

loadFactor, HashSet sınıfımızdaki başka bir alandır.

loadFactor başına ortalama öğe sayısını temsil eder

Kova,

üzerinde yeniden boyutlandırmak istiyoruz.

loadFactor, mekan ve zaman arasındaki bir dengedir.

Kovalar çok dolarsa yeniden boyutlandırırız.

Bu elbette zaman alıyor, ama

kovalar bir

biraz daha boş.

Bir örnek görelim.

İşte bir HashSet, şu ana kadar dört öğe ekledik.

Fil, köpek, kedi ve balık.

          buckets      elements
      -------      -------
        0          
        1          elephant
         2          cat ->dog
         3           fish
          4         
           5

Bu noktada, loadFactor,

eşik,

her bir kova için iyi olduğum ortalama öğe sayısı

ile, 0.75.

Kova sayısı kovadır. 6 olan uzunluk ve

bu noktada HashSet'imizin dört öğesi vardır, yani

geçerli boyut 4'tür.

HashSet'imizi yeniden boyutlandıracağız, yani daha fazla kova ekleyeceğiz,

grup başına ortalama öğe sayısı aşıldığında

loadFactor.

Geçerli boyutun kovalara bölünmesi budur. Uzunluk

loadFactor'dan daha büyük.

Bu noktada, kova başına ortalama öğe sayısı

4 bölü 6'dır.

4 element, 6 kova, bu 0.67.

Bu, 0,75 olarak belirlediğim eşikten daha az.

Tamam.

Yeniden boyutlandırmaya ihtiyacımız yok.

Ama şimdi diyelim ki dağ sıçanı ekliyoruz.

                  buckets      elements
      -------      -------
        0          
        1          elephant
         2        woodchuck-> cat ->dog
         3           fish
          4         
           5

Woodchuck, kova numarası 3 ile sonuçlanacaktı.

Bu noktada currentSize 5'tir.

Ve şimdi kova başına ortalama öğe sayısı

currentSize öğesinin buckets.length değerine bölünmesiyle elde edilen değerdir.

Bu 5 elementin 6 kovaya bölünmesi 0.83'tür.

Bu da 0.75 olan loadFactor'u aşıyor.

Bu sorunu çözmek için,

kovalar belki biraz

işlemlerin daha

kova içerir

bir element biraz daha az karmaşık olacak, yeniden boyutlandırmak istiyorum

HashSet'im.

HashSet'i yeniden boyutlandırmak iki adım alır.

İlk önce kova sayısını ikiye katlayacağım, 6 kova vardı,

Şimdi 12 kepçem olacak.

Burada 0.75 olarak ayarladığım loadFactor'un aynı kaldığını unutmayın.

Ancak değiştirilen kova sayısı 12'dir,

aynı kalan eleman sayısı 5'tir.

5 bölü 12 yaklaşık 0.42, bu bizim

Yük faktörü,

şimdi iyiyiz.

Ama işimiz bitmedi çünkü bu öğelerin bazıları

yanlış kova şimdi.

Örneğin fil.

Fil 2 numaralı kovadaydı çünkü

fil karakterleri

8 idi.

6 kepçemiz var, 8 eksi 6 2.

Bu yüzden 2 numaraya çıktı.

Ama şimdi 12 kova var, 8 mod 12 8, yani

fil artık kova numarası 2'ye ait değil.

Fil, kova numarası 8'e aittir.

Dağ sıçanı ne olacak?

Tüm bu soruna başlayan Woodchuck'tı.

Woodchuck 3 numaralı kepçeyle sonuçlandı.

Çünkü 9 mod 6 3'tür.

Ama şimdi 9 mod 12 yapıyoruz.

9 mod 12 9'dur, dağ sıçanı 9 numaralı kovaya gider.

Ve tüm bunların avantajını görüyorsunuz.

Şimdi 3 numaralı kova sadece iki öğeye sahipken, daha önce 3'e sahipti.

İşte kodumuz,

HashSet'imizi ayrı zincirleme ile

yeniden boyutlandırma yapmadı.

Şimdi, yeniden boyutlandırma kullandığımız yeni bir uygulama.

Bu kodun çoğu aynı,

hala içerdiği

değer zaten.

Değilse, hangi kovayı koyacağımızı anlayacağız

girmeli ve

daha sonra bu bölüme ekleyin, bu LinkedList'e ekleyin.

Ama şimdi currentSize alanını artırıyoruz.

currentSize, sayıyı takip eden alandı

HashSet'imizdeki öğelerin

Onu artıracağız ve sonra bakacağız

ortalama yükte,

grup başına ortalama öğe sayısı.

Bu bölümü burada yapacağız.

Emin olmak için burada biraz döküm yapmalıyız

bir çift olsun.

Ve sonra, bu ortalama yükü alanla karşılaştıracağız

olarak belirlediğim

Örneğin, bu HashSet'i oluşturduğumda 0.75

loadFactor.

Ortalama yük loadFactor'dan büyükse,

bu, kova başına çok fazla öğe olduğu anlamına gelir

ve yeniden yerleştirmem gerekiyor.

İşte yeniden yerleştirme yöntemini uygulama

tüm öğeleri.

İlk olarak, oldBuckets adlı yerel bir değişken oluşturacağım.

Şu anda durdukları kovalara atıfta bulunuyor

her şeyi yeniden boyutlandırmaya başlamadan önce.

Not Henüz yeni bir bağlantılı listeler dizisi oluşturmuyorum.

Sadece kovaları eski kovalar olarak yeniden adlandırıyorum.

Şimdi kovaların sınıfımızdaki bir alan olduğunu hatırlıyorum

şimdi yeni bir dizi oluşturmak için

Bağlantılı listelerin% 100'üne eşittir, ancak bunun iki katı öğe

ilk kez yaptığı gibi.

Şimdi yeniden yerleştirmeyi yapmalıyım,

Tüm eski kovaları tekrarlayacağım.

OldBuckets içindeki her öğe bir LinkedList dizesidir

bu bir kova.

O kovadan geçeceğim ve içindeki her elemanı alacağım

Kova.

Ve şimdi yeni kovalara tekrar yerleştireceğim.

Onun hashCode'unu alacağım.

Hangi indeks olduğunu anlayacağım.

Ve şimdi yeni bir kova alıyorum, yeni LinkedList

teller ve

Onu yeni kovaya ekleyeceğim.

Özetlemek gerekirse, gördüğümüz gibi HashSets, Linked dizileri

Listeler veya bölümler.

Kendini yeniden boyutlandıran HashSet, bir oran veya


1

Bir tablo boyutu n * 1.5 veya n + (n >> 1) seçerdim, bu bölme olmadan 0,66666 ~ yük faktörü verir, bu da çoğu sistemde, özellikle de bölme olmayan taşınabilir sistemlerde yavaştır. donanım.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.