Tầm quan trọng của yếu tố tải trong HashMap là gì?


232

HashMapcó hai tính chất quan trọng: sizeload factor. Tôi đã xem qua tài liệu Java và nó nói 0.75flà hệ số tải ban đầu. Nhưng tôi không thể tìm thấy việc sử dụng thực tế của nó.

Ai đó có thể mô tả các kịch bản khác nhau mà chúng ta cần đặt hệ số tải là gì và một số giá trị lý tưởng mẫu cho các trường hợp khác nhau là gì không?

Câu trả lời:


266

Các tài liệu giải thích nó khá tốt:

Một phiên bản của HashMap có hai tham số ảnh hưởng đến hiệu suất của nó: dung lượng ban đầu và hệ số tải. Dung lượng là số lượng xô trong bảng băm và công suất ban đầu chỉ đơn giản là công suất tại thời điểm bảng băm được tạo. Hệ số tải là thước đo mức độ đầy đủ của bảng băm được phép nhận trước khi công suất của nó được tự động tăng lên. Khi số lượng mục trong bảng băm vượt quá sản phẩm của hệ số tải và công suất hiện tại, bảng băm được thử lại (nghĩa là cấu trúc dữ liệu nội bộ được xây dựng lại) để bảng băm có số lượng gấp đôi số lượng.

Theo nguyên tắc chung, hệ số tải mặc định (.75) mang lại sự đánh đổi tốt giữa thời gian và chi phí không gian. Các giá trị cao hơn làm giảm chi phí không gian nhưng tăng chi phí tra cứu (được phản ánh trong hầu hết các hoạt động của lớp HashMap, bao gồm cả lấy và đặt). Số lượng mục dự kiến ​​trong bản đồ và hệ số tải của nó phải được tính đến khi thiết lập công suất ban đầu, để giảm thiểu số lượng các hoạt động làm lại. Nếu công suất ban đầu lớn hơn số lượng mục nhập tối đa chia cho hệ số tải, sẽ không có hoạt động làm lại nào xảy ra.

Như với tất cả các tối ưu hóa hiệu suất, một ý tưởng tốt là tránh tối ưu hóa mọi thứ sớm (nghĩa là không có dữ liệu cứng về nơi tắc nghẽn).


14
Các câu trả lời khác đang đề xuất chỉ định capacity = N/0.75để tránh luyện tập lại, nhưng suy nghĩ ban đầu của tôi mới được đặt ra load factor = 1. Sẽ có nhược điểm cho cách tiếp cận đó? Tại sao yếu tố tải ảnh hưởng get()put()chi phí vận hành?
siêu thị

19
Hệ số tải = 1 hashmap với số lượng mục = dung lượng sẽ thống kê có số lượng va chạm đáng kể (= khi nhiều khóa tạo ra cùng một hàm băm). Khi xảy ra xung đột, thời gian tra cứu tăng lên, vì trong một nhóm sẽ có> 1 mục khớp, trong đó khóa phải được kiểm tra riêng cho sự bằng nhau. Một số phép toán chi tiết: preshing.com/20110504/hash-collision-probabilities
atimb

8
Tôi không theo dõi bạn @atimb; Thuộc tính loadset chỉ được sử dụng để xác định khi nào cần tăng kích thước lưu trữ phải không? - Làm thế nào để có một bộ tải của một tăng khả năng va chạm băm? - Thuật toán băm không có kiến ​​thức về số lượng vật phẩm trong bản đồ hoặc tần suất sử dụng "xô" lưu trữ mới, v.v ... Đối với bất kỳ nhóm đối tượng nào có cùng kích thước, bất kể chúng được lưu trữ như thế nào, bạn nên có cùng xác suất của các giá trị băm lặp đi lặp lại ...
BrainSlugs83

19
Xác suất va chạm băm là ít hơn, nếu kích thước của bản đồ lớn hơn. Ví dụ: các phần tử có mã băm 4, 8, 16 và 32 sẽ được đặt trong cùng một nhóm, nếu kích thước của bản đồ là 4, nhưng mọi mục sẽ có một nhóm riêng, nếu kích thước của bản đồ lớn hơn 32. Bản đồ với kích thước ban đầu 4 và hệ số tải 1.0 (4 xô, nhưng tất cả 4 yếu tố trong một nhóm) sẽ trong ví dụ này trung bình chậm hơn hai lần so với một hệ số khác với hệ số tải 0,75 (8 xô, hai xô đầy - với phần tử "4" và với các phần tử "8", "16", "32").
30h

1
Chi phí tra cứu @Adelin được tăng cho các yếu tố tải cao hơn vì sẽ có nhiều xung đột hơn cho các giá trị cao hơn và cách Java xử lý các xung đột là bằng cách đặt các mục có cùng mã băm vào cùng một nhóm bằng cấu trúc dữ liệu. Bắt đầu trong Java 8, cấu trúc dữ liệu này là một cây tìm kiếm nhị phân. Điều này làm cho việc tìm kiếm phức tạp trong trường hợp xấu nhất O (lg (n)) với trường hợp xấu nhất xảy ra nếu tất cả các yếu tố được thêm vào có cùng mã băm.
Gigi Bayte ngày 2

141

Công suất ban đầu mặc định của HashMapmất là 16 và hệ số tải là 0,75f (tức là 75% kích thước bản đồ hiện tại). Hệ số tải thể hiện ở mức độ nào HashMapnên tăng gấp đôi công suất.

Ví dụ sản phẩm của công suất và hệ số tải như 16 * 0.75 = 12. Điều này thể hiện rằng sau khi lưu trữ cặp khóa - giá trị thứ 12 vào HashMap, dung lượng của nó trở thành 32.


3
Mặc dù câu trả lời của bạn rất rõ ràng, bạn có thể vui lòng cho biết ngay sau khi lưu trữ 12 cặp khóa-giá trị, dung lượng trở thành 32 hay là khi mục thứ 13 được thêm vào, tại thời điểm đó, dung lượng thay đổi và sau đó mục nhập được chèn.
userab

điều đó có nghĩa là số lượng thùng được tăng thêm 2?
LoveMeow

39

Trên thực tế, từ tính toán của tôi, hệ số tải "hoàn hảo" gần với log 2 (~ 0,7). Mặc dù bất kỳ hệ số tải nhỏ hơn này sẽ mang lại hiệu suất tốt hơn. Tôi nghĩ rằng .75 có lẽ đã được rút ra khỏi một chiếc mũ.

Bằng chứng:

Xâu chuỗi có thể tránh được và dự đoán nhánh được khai thác bằng cách dự đoán nếu một thùng rỗng hay không. Một thùng có thể trống nếu xác suất của nó trống vượt quá 0,5.

Đặt s đại diện cho kích thước và n số lượng phím được thêm vào. Sử dụng định lý nhị thức, xác suất của một thùng rỗng là:

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

Vì vậy, một cái xô có thể trống nếu có ít hơn

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

Khi s đạt đến vô cùng và nếu số lượng khóa được thêm vào sao cho P (0) = .5, thì n / s sẽ tiếp cận log (2) nhanh chóng:

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

4
Toán học FTW! Có khả năng .75được làm tròn đến phân số dễ hiểu gần nhất log(2)và trông giống như một con số ma thuật. Tôi rất muốn thấy một bản cập nhật cho giá trị mặc định của JDK, với nhận xét đã nói ở trên triển khai: D
Giải mã

2
Tôi thực sự muốn câu trả lời này, nhưng tôi là một nhà phát triển JavaEE, có nghĩa là toán học chưa bao giờ thực sự là bộ đồ mạnh mẽ của tôi, vì vậy tôi hiểu rất ít về những gì bạn đã viết lol
searchengine27

28

Hệ số tải là gì?

Lượng dung lượng sẽ cạn kiệt để HashMap tăng dung lượng?

Tại sao hệ số tải?

Hệ số tải theo mặc định là 0,75 công suất ban đầu (16) do đó 25% số xô sẽ được miễn phí trước khi có sự gia tăng công suất & điều này làm cho nhiều nhóm mới có mã băm mới chỉ ra chúng tồn tại ngay sau khi tăng số thùng.

Bây giờ tại sao bạn nên giữ nhiều thùng miễn phí & tác động của việc giữ các thùng miễn phí đối với hiệu suất là gì?

Nếu bạn đặt hệ số tải thành 1.0 thì điều gì đó rất thú vị có thể xảy ra.

Giả sử bạn đang thêm một đối tượng x vào hashmap của mình có hashCode là 888 và trong hashmap của bạn, nhóm đại diện cho mã băm là miễn phí, vì vậy đối tượng x được thêm vào nhóm, nhưng bây giờ lại nói nếu bạn đang thêm một đối tượng y có hashCode còn 888 thì đối tượng y của bạn sẽ được thêm vào cho chắc chắn NHƯNG ở cuối thùng ( vì các thùng không có gì ngoài khóa lưu trữ thực hiện được liệt kê, giá trị & tiếp theo ) bây giờ điều này có tác động hiệu suất! Vì đối tượng y của bạn không còn hiện diện trong đầu xô nếu bạn thực hiện tra cứu, thời gian thực hiện sẽ không phải là O (1)lần này tùy thuộc vào số lượng vật phẩm trong cùng một thùng. Đây được gọi là xung đột băm bằng cách này & điều này thậm chí xảy ra khi hệ số tải của bạn nhỏ hơn 1.

Tương quan giữa hiệu suất, va chạm băm & hệ số tải?

Hệ số tải thấp hơn = nhiều thùng miễn phí = ít cơ hội va chạm = hiệu suất cao = yêu cầu không gian cao.

Sửa tôi nếu tôi sai ở đâu đó.


2
Bạn có thể thêm một chút về cách mã băm được rút xuống thành một số có phạm vi 1- {số đếm}, và do đó, nó không phải là số lượng các nhóm, nhưng kết quả cuối cùng của thuật toán băm bao gồm một phạm vi lớn hơn. HashCode không phải là thuật toán băm đầy đủ, nó chỉ đủ nhỏ để dễ dàng xử lý lại. Vì vậy, không có khái niệm về "xô miễn phí", mà là "số lượng thùng miễn phí tối thiểu", vì bạn có thể lưu trữ tất cả các yếu tố của mình trong cùng một nhóm. Thay vào đó, đó là không gian khóa của mã băm của bạn, bằng với dung lượng * (1 / load_factor). 40 phần tử, hệ số tải 0,25 = 160 thùng.
dùng1122069

Tôi nghĩ rằng thời gian tra cứu cho một đối tượng từ LinkedListđược gọi là Amortized Constant Execution Timevà thể hiện bằng một +nhưO(1)+
Raf

19

Từ tài liệu :

Hệ số tải là thước đo mức độ đầy đủ của bảng băm được phép nhận trước khi công suất của nó tự động tăng

Nó thực sự phụ thuộc vào các yêu cầu cụ thể của bạn, không có "quy tắc chung" để chỉ định hệ số tải ban đầu.


Các tài liệu cũng nói; "Theo nguyên tắc chung, hệ số tải mặc định (.75) mang lại sự đánh đổi tốt giữa thời gian và chi phí không gian.". Vì vậy, đối với bất cứ ai không chắc chắn, mặc định là một quy tắc tốt.
ferekdoley

4

Đối với HashMap DEFAULT_INITIAL_CAPACITY = 16DEFAULT_LOAD_FACTOR = 0.75f, điều đó có nghĩa là số MAX của TẤT CẢ các mục trong HashMap = 16 * 0.75 = 12 . Khi phần tử thứ mười ba sẽ được thêm dung lượng (kích thước mảng) của HashMap sẽ được nhân đôi! Minh họa hoàn hảo đã trả lời câu hỏi này: nhập mô tả hình ảnh ở đây hình ảnh được lấy từ đây:

https://javabypatel.blogspot.com/2015/10/what-is-load-factor-and-rehashing-in-hashmap.html


2

Nếu xô quá đầy, thì chúng ta phải xem qua

một danh sách liên kết rất dài.

Và đó là loại đánh bại điểm.

Vì vậy, đây là một ví dụ nơi tôi có bốn thùng.

Tôi có con voi và con lửng trong Hashset của tôi cho đến nay.

Đây là một tình huống khá tốt, phải không?

Mỗi phần tử có 0 hoặc một phần tử.

Bây giờ chúng tôi đặt thêm hai yếu tố vào Hashset của chúng tôi.

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

Điều này cũng không tệ lắm.

Mỗi thùng chỉ có một yếu tố. Vì vậy, nếu tôi muốn biết, điều này có chứa gấu trúc?

Tôi có thể nhanh chóng nhìn vào cái xô số 1 và nó không

ở đó và

Tôi biết nó không có trong bộ sưu tập của chúng tôi.

Nếu tôi muốn biết nó có chứa mèo không, tôi nhìn vào cái xô

số 3,

Tôi tìm thấy con mèo, tôi rất nhanh biết nếu nó ở trong chúng ta

bộ sưu tập.

Điều gì sẽ xảy ra nếu tôi thêm koala, điều đó không tệ lắm.

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

Có lẽ bây giờ thay vì trong xô số 1 chỉ nhìn vào

một yếu tố

Tôi cần nhìn vào hai.

Nhưng ít nhất tôi không phải nhìn vào con voi, con lửng và

con mèo.

Nếu tôi lại tìm gấu trúc, nó chỉ có thể ở trong thùng

số 1 ​​và

Tôi không phải nhìn bất cứ thứ gì khác sau đó rái cá và

koala.

Nhưng bây giờ tôi đặt cá sấu vào thùng số 1 và bạn có thể

xem có thể nơi này sẽ đi.

Rằng nếu xô số 1 cứ ngày càng lớn hơn và

lớn hơn, về cơ bản tôi phải xem qua tất cả

những yếu tố cần tìm

một cái gì đó nên có trong xô số 1.

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

Nếu tôi bắt đầu thêm chuỗi vào các nhóm khác,

đúng, vấn đề ngày càng lớn hơn

xô đơn.

Làm thế nào để chúng ta ngăn chặn xô của chúng tôi quá đầy?

Giải pháp ở đây là

          "the HashSet can automatically

        resize the number of buckets."

Có Hashset nhận ra rằng các thùng đang nhận được

quá đầy đủ

Đó là mất lợi thế của tất cả một trong những tìm kiếm này

các yếu tố.

Và nó sẽ chỉ tạo ra nhiều thùng hơn (thường gấp đôi so với trước đây) và

Sau đó đặt các yếu tố vào thùng chính xác.

Vì vậy, đây là triển khai Hashset cơ bản của chúng tôi với riêng biệt

xích Bây giờ tôi sẽ tạo ra một "Hashset tự thay đổi kích thước".

Hashset này sẽ nhận ra rằng các thùng là

quá đầy đủ và

nó cần nhiều xô hơn

loadFactor là một lĩnh vực khác trong lớp Hashset của chúng tôi.

loadFactor đại diện cho số phần tử trung bình trên mỗi

Gầu múc,

ở trên mà chúng tôi muốn thay đổi kích thước.

loadFactor là sự cân bằng giữa không gian và thời gian.

Nếu xô quá đầy thì chúng tôi sẽ thay đổi kích thước.

Điều đó cần có thời gian, tất nhiên, nhưng

nó có thể giúp chúng ta tiết kiệm thời gian xuống đường nếu xô là

thêm một chút trống rỗng.

Hãy xem một ví dụ.

Đây là Hashset, chúng tôi đã thêm bốn yếu tố cho đến nay.

Voi, chó, mèo và cá.

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

Tại thời điểm này, tôi đã quyết định rằng loadFactor,

ngưỡng,

số lượng phần tử trung bình trên mỗi thùng mà tôi ổn

với, là 0,75.

Số lượng xô là buckets.length, là 6 và

tại thời điểm này, Hashset của chúng tôi có bốn yếu tố, vì vậy

kích thước hiện tại là 4.

Chúng tôi sẽ thay đổi kích thước Hashset của chúng tôi, đó là chúng tôi sẽ thêm nhiều nhóm hơn,

khi số lượng phần tử trung bình trên mỗi thùng vượt quá

loadFactor.

Đó là khi kích thước hiện tại chia cho buckets.length là

lớn hơn loadFactor.

Tại thời điểm này, số phần tử trung bình trên mỗi thùng

là 4 chia cho 6.

4 yếu tố, 6 thùng, đó là 0,67.

Đó là ít hơn ngưỡng tôi đặt là 0,75 vì vậy chúng tôi

Được chứ.

Chúng tôi không cần thay đổi kích thước.

Nhưng bây giờ hãy nói rằng chúng ta thêm woodchuck.

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

Woodchuck sẽ kết thúc trong xô số 3.

Tại thời điểm này, kích thước hiện tại là 5.

Và bây giờ số lượng phần tử trung bình trên mỗi thùng

là kích thước hiện tại chia cho buckets.length.

Đó là 5 yếu tố chia cho 6 thùng là 0,83.

Và điều này vượt quá loadFactor là 0,75.

Để giải quyết vấn đề này, để thực hiện

xô có lẽ một chút

trống hơn để các hoạt động như xác định xem một

xô chứa

một yếu tố sẽ ít phức tạp hơn một chút, tôi muốn thay đổi kích thước

Hashset của tôi.

Thay đổi kích thước Hashset mất hai bước.

Đầu tiên tôi sẽ nhân đôi số thùng, tôi có 6 thùng,

Bây giờ tôi sẽ có 12 thùng.

Lưu ý ở đây là loadFactor mà tôi đặt thành 0,75 giữ nguyên.

Nhưng số lượng thùng thay đổi là 12,

số phần tử giữ nguyên, là 5.

5 chia cho 12 là khoảng 0,42, đó là dưới của chúng tôi

hệ số tải,

Vì vậy, bây giờ chúng tôi ổn.

Nhưng chúng tôi không hoàn thành vì một số yếu tố này nằm trong

xô sai bây giờ.

Ví dụ, con voi.

Voi đã ở trong thùng số 2 vì số lượng

nhân vật trong voi

được 8.

Chúng ta có 6 thùng, 8 trừ 6 là 2.

Đó là lý do tại sao nó kết thúc ở số 2.

Nhưng bây giờ chúng ta có 12 thùng, 8 mod 12 là 8, vì vậy

voi không thuộc xô số 2 nữa.

Voi thuộc thùng số 8.

Còn gỗ thì sao?

Woodchuck là người bắt đầu toàn bộ vấn đề này.

Woodchuck đã kết thúc trong xô số 3.

Vì 9 mod 6 là 3.

Nhưng bây giờ chúng tôi làm 9 mod 12.

9 mod 12 là 9, woodchuck đi đến xô số 9.

Và bạn thấy lợi thế của tất cả điều này.

Bây giờ xô số 3 chỉ có hai yếu tố trong khi trước đó nó có 3.

Vì vậy, đây là mã của chúng tôi,

nơi chúng tôi đã có Hashset với chuỗi kết nối riêng biệt

đã không làm thay đổi kích thước.

Bây giờ, đây là một triển khai mới, nơi chúng tôi sử dụng thay đổi kích thước.

Hầu hết các mã này là như nhau,

chúng tôi vẫn sẽ xác định xem nó có chứa

giá trị rồi.

Nếu không, chúng ta sẽ tìm ra cái xô nào

nên đi vào và

sau đó thêm nó vào nhóm đó, thêm nó vào LinkedList đó.

Nhưng bây giờ chúng tôi tăng trường currentSize.

currentSize là trường theo dõi số

các yếu tố trong Hashset của chúng tôi.

Chúng tôi sẽ tăng nó và sau đó chúng tôi sẽ xem xét

ở mức tải trung bình,

số lượng phần tử trung bình trên mỗi thùng.

Chúng tôi sẽ làm điều đó xuống đây.

Chúng tôi phải thực hiện một chút đúc ở đây để đảm bảo

rằng chúng tôi nhận được gấp đôi.

Và sau đó, chúng tôi sẽ so sánh tải trung bình đó với trường

mà tôi đã đặt là

0,75 khi tôi tạo Hashset này, ví dụ, đó là

loadFactor.

Nếu tải trung bình lớn hơn loadFactor,

điều đó có nghĩa là có quá nhiều yếu tố trên mỗi thùng

trung bình, và tôi cần phải xác nhận lại.

Vì vậy, đây là việc chúng tôi thực hiện phương pháp để xác nhận lại

Tất cả các yếu tố.

Đầu tiên, tôi sẽ tạo một biến cục bộ có tên oldBuckets.

Đó là đề cập đến các thùng như họ hiện đang đứng

trước khi tôi bắt đầu thay đổi kích thước mọi thứ.

Lưu ý Tôi chưa tạo một mảng mới của danh sách được liên kết.

Tôi chỉ đổi tên xô thành oldBuckets.

Bây giờ hãy nhớ xô là một lĩnh vực trong lớp của chúng tôi, tôi sẽ

bây giờ tạo một mảng mới

danh sách được liên kết nhưng điều này sẽ có gấp đôi số yếu tố

như nó đã làm lần đầu tiên

Bây giờ tôi cần thực sự làm lại,

Tôi sẽ lặp đi lặp lại qua tất cả các thùng cũ.

Mỗi phần tử trong oldBuckets là một ListList của chuỗi

đó là một cái xô

Tôi sẽ đi qua cái xô đó và lấy từng phần tử trong đó

Gầu múc.

Và bây giờ tôi sẽ lắp lại nó vào newBuckets.

Tôi sẽ nhận được mã băm của nó.

Tôi sẽ tìm ra nó là chỉ số nào.

Và bây giờ tôi nhận được nhóm mới, LinkedList mới của

chuỗi và

Tôi sẽ thêm nó vào cái xô mới đó.

Vì vậy, để tóm tắt lại, HashSets như chúng ta đã thấy là các mảng của Liên kết

Danh sách, hoặc xô.

Một Hashset tự thay đổi kích thước có thể nhận ra bằng cách sử dụng một số tỷ lệ hoặc


1

Tôi sẽ chọn kích thước bảng là n * 1,5 hoặc n + (n >> 1), điều này sẽ cho hệ số tải là 0,66666 ~ không phân chia, chậm trên hầu hết các hệ thống, đặc biệt là trên các hệ thống di động không có phân chia phần cứng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.