Tại sao ConcảnHashMap ngăn chặn các khóa và giá trị null?


141

JavaDoc ConcurrentHashMapnói điều này:

Thích Hashtablenhưng không giống HashMap, lớp này không cho phép nullđược sử dụng làm khóa hoặc giá trị.

Câu hỏi của tôi: Tại sao?

Câu hỏi thứ 2: Tại sao không Hashtablecho phép null?

Tôi đã sử dụng rất nhiều HashMaps để lưu trữ dữ liệu. Nhưng khi thay đổi thành ConcurrentHashMaptôi đã gặp nhiều rắc rối vì NullPulumExceptions.


1
Tôi nghĩ đó là một sự mâu thuẫn cực kỳ khó chịu. EnumMap cũng không cho phép null. Rõ ràng không có giới hạn kỹ thuật nào không cho phép các phím null. đối với Bản đồ <K, V>, chỉ cần trường gõ chữ V sẽ cung cấp hỗ trợ cho các khóa null (có thể là trường boolean khác nếu bạn muốn phân biệt giữa giá trị null và không có giá trị).
RAY

6
Một câu hỏi hay hơn là "tại sao HashMap cho phép khóa null và giá trị null?". Hoặc có thể, "tại sao Java cho phép null cư trú ở tất cả các loại?" Hoặc thậm chí "tại sao Java lại có null?".
Jed Wesley-Smith

Câu trả lời:


220

Từ tác giả của ConcurrentHashMapchính mình (Doug Lea) :

Lý do chính mà null không được phép trong Đồng thời (Đồng thờiHashMaps, Đồng thờiSkipListMaps) là sự mơ hồ có thể chỉ chấp nhận được trong các bản đồ không đồng thời không thể chấp nhận được. Cái chính là nếu map.get(key)trả về null, bạn không thể phát hiện xem nullkhóa có ánh xạ rõ ràng với khóa không được ánh xạ hay không. Trong bản đồ không đồng thời, bạn có thể kiểm tra điều này thông qua map.contains(key), nhưng trong bản đồ đồng thời, bản đồ có thể đã thay đổi giữa các cuộc gọi.


7
Cảm ơn bạn, nhưng những gì về việc có null là chìa khóa?
AmitW

2
tại sao không sử dụng Optionals làm giá trị nội bộ
benez

2
@benez Optionallà một tính năng Java 8, trước đó không có sẵn (Java 5). Bạn có thể sử dụng Optionals bây giờ, thực sự.
Bruno

@AmitW, tôi nghĩ rằng ans là như nhau, tức là mơ hồ. Ví dụ, giả sử một luồng làm cho khóa là null và lưu trữ giá trị đối với nó. Sau đó, một chủ đề khác đã thay đổi một khóa khác thành null. Khi luồng thứ hai cố gắng thêm giá trị mới, nó sẽ được thay thế. Nếu luồng thứ hai cố gắng nhận giá trị, nó sẽ nhận giá trị cho một khóa khác, khóa được sửa đổi bởi khóa đầu tiên. Tình huống như vậy nên tránh.
Dexter

44

Tôi tin rằng, ít nhất là một phần, cho phép bạn kết hợp containsKeyvà thực hiện getmột cuộc gọi. Nếu bản đồ có thể giữ null, không có cách nào để biết liệu getcó trả về null hay không vì không có khóa cho giá trị đó hoặc chỉ vì giá trị là null.

Tại sao đó là một vấn đề? Bởi vì không có cách nào an toàn để làm điều đó cho mình. Lấy mã sau:

if (m.containsKey(k)) {
   return m.get(k);
} else {
   throw new KeyNotPresentException();
}

mlà bản đồ đồng thời, khóa k có thể bị xóa giữa các cuộc gọi containsKeygetcuộc gọi, khiến đoạn mã này trả về một giá trị không bao giờ có trong bảng, thay vì mong muốn KeyNotPresentException.

Thông thường bạn sẽ giải quyết điều đó bằng cách đồng bộ hóa, nhưng với một bản đồ đồng thời tất nhiên sẽ không hoạt động. Do đó, chữ ký getphải thay đổi và cách duy nhất để làm điều đó theo cách tương thích ngược là ngăn người dùng chèn giá trị null vào vị trí đầu tiên và tiếp tục sử dụng nó như một trình giữ chỗ cho "không tìm thấy khóa".


Bạn có thể làm map.getOrDefault(key, NULL_MARKER). Nếu đó là null, giá trị là null. Nếu nó trả về NULL_MARKER, giá trị không có mặt.
Oliv

@Oliv Chỉ kể từ Java 8. Ngoài ra, có thể không có điểm đánh dấu null hợp lý cho loại đó.
Alice Purcell

@AlicePurcell, "nhưng với một bản đồ đồng thời tất nhiên sẽ không hoạt động" - tại sao, tôi có thể đồng bộ hóa trên phiên bản đồng thời tương tự - vì vậy tự hỏi tại sao nó không hoạt động. bạn có thể giải thích điều này
samshers

@samshers Không có hoạt động nào trên đồng bộ hóa bản đồ đồng thời, vì vậy bạn cần đồng bộ hóa bên ngoài tất cả các cuộc gọi, tại thời điểm đó, bạn không chỉ mất tất cả lợi ích về hiệu suất khi có bản đồ đồng thời, bạn đã để lại bẫy cho những người bảo trì trong tương lai đương nhiên sẽ mong đợi có thể truy cập một cách an toàn bản đồ đồng thời mà không cần đồng bộ hóa.
Alice Purcell

@AlicePurcell, tuyệt vời. Mặc dù về mặt kỹ thuật có thể, đó chắc chắn sẽ là một cơn ác mộng bảo trì và sẽ không có bất kỳ người dùng nào sau này mong đợi rằng họ phải đồng bộ hóa trên phiên bản đồng thời.
samshers

4

Josh Bloch thiết kế HashMap; Doug Lea thiết kế ConcurrentHashMap. Tôi hy vọng đó không phải là bôi nhọ. Trên thực tế tôi nghĩ rằng vấn đề là null thường yêu cầu gói để null thực sự có thể thay thế cho chưa được khởi tạo. Nếu mã máy khách yêu cầu null thì nó có thể trả chi phí (nhỏ được thừa nhận) để tự đóng gói null.


2

Bạn không thể đồng bộ hóa trên null.

Chỉnh sửa: Đây không phải là chính xác tại sao trong trường hợp này. Ban đầu tôi nghĩ có điều gì đó thú vị đang xảy ra với việc khóa mọi thứ chống lại các bản cập nhật đồng thời hoặc sử dụng trình giám sát Object để phát hiện nếu có gì đó bị sửa đổi, nhưng khi kiểm tra mã nguồn, có vẻ như tôi đã nhầm - họ khóa bằng cách sử dụng "phân đoạn" dựa trên bitmask của hàm băm.

Trong trường hợp đó, tôi nghi ngờ họ đã làm điều đó để sao chép Hashtable và tôi nghi ngờ Hashtable đã làm điều đó bởi vì trong thế giới cơ sở dữ liệu quan hệ, null! = Null, vì vậy sử dụng null làm khóa không có ý nghĩa gì.


Huh? Không có đồng bộ hóa được thực hiện trên các khóa và giá trị của Bản đồ. Điều đó sẽ vô nghĩa.
Tobias Müller

Có các loại khóa khác được thực hiện. Đó là những gì làm cho nó "Đồng thời". Để làm điều đó, nó cần một Object để treo.
Paul Tomblin

2
Tại sao không có Đối tượng cụ thể trong nội bộ có thể được sử dụng để đồng bộ hóa các giá trị null? ví dụ: "Đối tượng riêng NULL = new Object ();". Tôi nghĩ rằng tôi đã thấy điều này trước đây ...
Marcel

Những loại khóa khác có nghĩa là gì?
Tobias Müller

Trên thực tế, bây giờ tôi nhìn vào mã nguồn gee.cs.oswego.edu/dl/groupes/EDU/oswego/cs/dl/util/concản/iêu tôi đang nghi ngờ nghiêm trọng về điều đó. Có vẻ như nó sử dụng khóa phân khúc, không khóa trên các mục riêng lẻ.
Paul Tomblin

0

Đồng thờiHashMap là an toàn chủ đề. Tôi tin rằng việc không cho phép các khóa và giá trị null là một phần của việc đảm bảo rằng nó an toàn cho chuỗi.


0

Tôi đoán rằng đoạn mã sau của tài liệu API đưa ra một gợi ý hay: "Lớp này hoàn toàn tương thích với Hashtable trong các chương trình dựa trên sự an toàn của luồng nhưng không dựa trên chi tiết đồng bộ hóa của nó."

Có lẽ họ chỉ muốn làm ConcurrentHashMap hoàn toàn tương thích / hoán đổi cho nhau Hashtable. Và như Hashtablekhông cho phép các khóa và giá trị null ..


2
Và tại sao Hashtable không hỗ trợ null?
Marcel

Từ việc xem mã của nó, tôi không thấy một lý do rõ ràng tại sao Hashtable không cho phép các giá trị null. Có lẽ đó chỉ là một quyết định API từ khi lớp được tạo?! HashMap có một số xử lý đặc biệt cho trường hợp null trong nội bộ mà Hashtable không có. (Nó luôn ném NullPulumException.)
Tobias Müller

-2

Tôi không nghĩ rằng không cho phép giá trị null là một lựa chọn chính xác. Trong nhiều trường hợp, chúng tôi muốn đặt một khóa có giá trị null vào bản đồ hiện tại. Tuy nhiên, bằng cách sử dụng ConcảnHashMap, chúng tôi không thể làm điều đó. Tôi đề nghị rằng phiên bản sắp tới của JDK có thể hỗ trợ điều đó.


1
Bạn đã nghĩ về việc cạnh tranh cho Javachampion?
BlackBishop

Sử dụng Tùy chọn nếu bạn muốn hành vi giống như null trong các khóa của mình.
Alice Purcell
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.