Kiểm tra sự tồn tại của khóa trong HashMap


309

Việc kiểm tra sự tồn tại của khóa trong HashMap luôn cần thiết phải không?

Tôi có một HashMap với 1000 mục và tôi đang xem xét cải thiện hiệu quả. Nếu HashMap đang được truy cập rất thường xuyên, thì việc kiểm tra sự tồn tại của khóa tại mỗi lần truy cập sẽ dẫn đến một chi phí lớn. Thay vào đó nếu khóa không có và do đó xảy ra ngoại lệ, tôi có thể bắt ngoại lệ. (khi tôi biết rằng điều này sẽ hiếm khi xảy ra). Điều này sẽ giảm một nửa số lượt truy cập vào HashMap.

Đây có thể không phải là một thực hành lập trình tốt, nhưng nó sẽ giúp tôi giảm số lượng truy cập. Hay tôi đang thiếu một cái gì đó ở đây?

[ Cập nhật ] Tôi không có giá trị null trong HashMap.


8
"Do đó và ngoại lệ xảy ra" - ngoại lệ nào? Đây không phải là từ java.util.HashMap ...
serg10

Câu trả lời:


513

Bạn có bao giờ lưu trữ một giá trị null? Nếu không, bạn chỉ có thể làm:

Foo value = map.get(key);
if (value != null) {
    ...
} else {
    // No such key
}

Mặt khác, bạn chỉ có thể kiểm tra sự tồn tại nếu bạn nhận được giá trị null:

Foo value = map.get(key);
if (value != null) {
    ...
} else {
    // Key might be present...
    if (map.containsKey(key)) {
       // Okay, there's a key but the value is null
    } else {
       // Definitely no such key
    }
}

1
@Samuel: Chỉ khi null là giá trị có thể. Nếu bạn chắc chắn không có giá trị null trong bản đồ, getthì tốt thôi, và tránh thực hiện hai lần tra cứu khi bạn cũng cần giá trị đó.
Jon Skeet

Mặc dù điều này có thể rõ ràng hơn là một ví dụ, bạn cũng có thể viết if(value!=null || map.containsKey(key))cho phần thứ hai. Ít nhất nếu bạn muốn làm điều tương tự theo một trong hai cách - không có mã lặp lại. Nó sẽ hoạt động do ngắn mạch .
Cullub

66

Bạn sẽ không đạt được bất cứ điều gì bằng cách kiểm tra rằng khóa tồn tại. Đây là mã của HashMap:

@Override
public boolean containsKey(Object key) {
    Entry<K, V> m = getEntry(key);
    return m != null;
}

@Override
public V get(Object key) {
    Entry<K, V> m = getEntry(key);
    if (m != null) {
        return m.value;
    }
    return null;
}

Chỉ cần kiểm tra nếu giá trị trả về cho get()khác với null.

Đây là mã nguồn HashMap.


Tài nguyên :


2
Điểm quan trọng trong việc chỉ ra một triển khai cụ thể của các phương pháp này là gì?
jarnbjo

2
Để giải thích rằng trong hầu hết các trường hợp, việc kiểm tra khóa tồn tại sẽ mất khoảng thời gian tương tự như nhận giá trị. Vì vậy, nó sẽ không tối ưu hóa bất cứ điều gì để kiểm tra khóa thực sự tồn tại trước khi nhận được giá trị. Tôi biết đó là một khái quát nhưng nó có thể giúp hiểu.
Colin Hebert

Một liên kết tốt là grepcode.com/file/reposective.grepcode.com/java/root/jdk/openjdk/ ((OpenJDK có nguồn gốc rất mạnh từ mã Mặt trời) và có vẻ như tôi đã sai. Tôi đã so sánh phiên bản cho Java5 với Java6; chúng hoạt động khác nhau trong lĩnh vực này (nhưng cả hai đều chính xác, như các đoạn bạn đã đăng).
Donal Fellows

2
Như đã chỉ ra trong câu trả lời được chấp nhận, snawer này hoàn toàn sai. Tất nhiên, bạn có thể đạt được điều gì đó bằng cách kiểm tra sự tồn tại của khóa so sánh các giá trị - bạn có thể phân biệt các khóa không tồn tại với các khóa tồn tại nhưng được ánh xạ thành null dưới dạng giá trị.
Julian H.

43

Cách tốt hơn là sử dụng containsKeyphương pháp HashMap. Ngày mai sẽ có người thêm null vào Bản đồ. Bạn nên phân biệt giữa sự hiện diện của khóa và khóa có giá trị null.


Vâng. Hoặc phân lớp HashMap để ngăn chặn lưu trữ hoàn toàn null.
RobAu

1
1+ cho các kiểu nguyên thủy là giá trị không cần thiết phải sử dụng câu trả lời này
Prashant Bhanarkar

Nó cũng trôi chảy hơn khi viết .containsKey () hơn là kiểm tra null. Chúng ta nên lo lắng nhiều hơn về việc dễ đọc, giúp tiết kiệm thời gian của nhà phát triển, hơn là về một số tối ưu hóa nhỏ, trong hầu hết các trường hợp. Ít nhất là không tối ưu hóa trước khi nó trở nên cần thiết.
Tối đa

23

Bạn có nghĩa là bạn đã có mã như

if(map.containsKey(key)) doSomethingWith(map.get(key))

khắp nơi ? Sau đó, bạn chỉ cần kiểm tra xem map.get(key)trả về null và đó là nó. Nhân tiện, HashMap không đưa ra ngoại lệ cho các khóa bị thiếu, thay vào đó, nó trả về null. Trường hợp duy nhất containsKeycần thiết là khi bạn lưu trữ giá trị null, để phân biệt giữa giá trị null và giá trị bị thiếu, nhưng điều này thường được coi là thực tiễn xấu.


8

Chỉ cần sử dụng containsKey()cho rõ ràng. Nó nhanh và giữ cho mã sạch và dễ đọc. Điểm chung của HashMaps là việc tra cứu khóa nhanh, chỉ cần đảm bảo hashCode()equals()được thực hiện đúng.


4
if(map.get(key) != null || (map.get(key) == null && map.containsKey(key)))

3

Bạn cũng có thể sử dụng computeIfAbsent()phương thức trongHashMap lớp.

Trong ví dụ sau, maplưu trữ danh sách các giao dịch (số nguyên) được áp dụng cho khóa (tên của tài khoản ngân hàng). Để thêm 2 giao dịch 100200để checking_accountbạn có thể viết:

HashMap<String, ArrayList<Integer>> map = new HashMap<>();
map.computeIfAbsent("checking_account", key -> new ArrayList<>())
   .add(100)
   .add(200);

Bằng cách này, bạn không phải kiểm tra xem khóa checking_accountcó tồn tại hay không.

  • Nếu nó không tồn tại, một cái sẽ được tạo và trả về bởi biểu thức lambda.
  • Nếu nó tồn tại, thì giá trị cho khóa sẽ được trả về computeIfAbsent().

Thật thanh lịch! 👍


0

Tôi thường sử dụng thành ngữ

Object value = map.get(key);
if (value == null) {
    value = createValue(key);
    map.put(key, value);
}

Điều này có nghĩa là bạn chỉ nhấn bản đồ hai lần nếu thiếu khóa


0
  1. Nếu lớp khóa là của bạn, hãy đảm bảo các phương thức hashCode () và equals () được triển khai.
  2. Về cơ bản, quyền truy cập vào HashMap phải là O (1) nhưng với việc thực hiện phương thức hashCode sai, nó trở thành O (n), vì giá trị có cùng khóa băm sẽ được lưu dưới dạng danh sách Liên kết.

0

Câu trả lời Jon Skeet giải quyết tốt hai tình huống (bản đồ có nullgiá trị và không có nullgiá trị) một cách hiệu quả.

Về các mục số và mối quan tâm hiệu quả, tôi muốn thêm một cái gì đó.

Tôi có một HashMap với 1.000 mục nhập và tôi đang xem xét cải thiện hiệu quả. Nếu HashMap đang được truy cập rất thường xuyên, thì việc kiểm tra sự tồn tại của khóa tại mỗi lần truy cập sẽ dẫn đến một chi phí lớn.

Một bản đồ với 1.000 mục không phải là một bản đồ lớn.
Cũng như một bản đồ với 5.000 hoặc 10.000 mục.
Mapđược thiết kế để thực hiện truy xuất nhanh với kích thước như vậy.

Bây giờ, nó giả định rằng hashCode()các phím bản đồ cung cấp phân phối tốt.

Nếu bạn có thể sử dụng một Integerloại khóa, hãy làm điều đó. Phương pháp
của nó hashCode()rất hiệu quả do các va chạm là không thể đối với intcác giá trị duy nhất :

public final class Integer extends Number implements Comparable<Integer> {
    ...
    @Override
    public int hashCode() {
        return Integer.hashCode(value);
    }

    public static int hashCode(int value) {
        return value;
    }
    ...
}

Nếu đối với khóa, bạn phải sử dụng một loại tích hợp khác như Stringví dụ thường được sử dụng Map, bạn có thể có một số va chạm nhưng từ 1 nghìn đến vài nghìn đối tượng trong đó Map, bạn nên có rất ít trong số đó làString.hashCode() phương thức cung cấp một phân phối tốt.

Nếu bạn sử dụng một loại tùy chỉnh, ghi đè hashCode()equals()chính xác và đảm bảo tổng thể hashCode()cung cấp phân phối công bằng.
Bạn có thể tham khảo mục 9 Java Effectiveđề cập đến nó.
Đây là một bài viết chi tiết cách.

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.