Sử dụng java Map.containsKey () thừa khi sử dụng map.get ()


90

Tôi đã tự hỏi một thời gian liệu có được phép trong phương pháp hay nhất để từ chối sử dụng containsKey()phương pháp trên java.util.Mapvà thay vào đó thực hiện kiểm tra vô hiệu đối với kết quả từ get().

Cơ sở lý luận của tôi là có vẻ thừa nếu thực hiện tra cứu giá trị hai lần - đầu tiên cho giá trị containsKey()và sau đó một lần nữa cho get().

Mặt khác, có thể là hầu hết các triển khai tiêu chuẩn của Mapbộ đệm trong lần tra cứu cuối cùng hoặc trình biên dịch có thể loại bỏ phần dư thừa và để có thể đọc được mã, tốt hơn là duy trì containsKey()phần đó.

Tôi sẽ đánh giá cao ý kiến ​​của bạn.

Câu trả lời:


110

Một số triển khai Bản đồ được phép có giá trị rỗng, ví dụ như HashMap, trong trường hợp này nếu get(key)trả về nullnó không đảm bảo rằng không có mục nhập nào trong bản đồ được liên kết với khóa này.

Vì vậy, nếu bạn muốn biết liệu một bản đồ có chứa công dụng chính hay không Map.containsKey. Nếu bạn chỉ cần một giá trị được ánh xạ tới một công dụng chính Map.get(key). Nếu bản đồ này cho phép các giá trị null, thì giá trị trả về là null không nhất thiết chỉ ra rằng bản đồ không chứa ánh xạ cho khóa; Trong trường hợp như vậy Map.containsKeylà vô ích và sẽ ảnh hưởng đến hiệu suất. Hơn nữa, trong trường hợp truy cập đồng thời vào một bản đồ (ví dụ ConcurrentHashMap), sau khi bạn kiểm tra, Map.containsKey(key)có khả năng mục nhập sẽ bị xóa bởi một luồng khác trước khi bạn gọi Map.get(key).


8
Ngay cả khi giá trị được đặt thành null, bạn có muốn xử lý điều đó khác với khóa / giá trị chưa được đặt không? Nếu bạn không cần phải đặc biệt để đối xử với nó một cách khác, bạn chỉ có thể sử dụngget()
Peter Lawrey

1
Nếu Maplà của bạn private, lớp của bạn có thể đảm bảo rằng a nullkhông bao giờ được chèn vào bản đồ. Trong trường hợp đó, bạn có thể sử dụng get()theo sau là kiểm tra null thay vì containsKey(). Làm như vậy có thể rõ ràng hơn và có lẽ hiệu quả hơn một chút trong một số trường hợp.
Raedwald

43

Tôi nghĩ rằng nó là khá chuẩn để viết:

Object value = map.get(key);
if (value != null) {
    //do something with value
}

thay vì

if (map.containsKey(key)) {
    Object value = map.get(key);
    //do something with value
}

Nó không kém phần dễ đọc và hiệu quả hơn một chút nên tôi không thấy có lý do gì để không làm điều đó. Rõ ràng là nếu bản đồ của bạn có thể chứa null, thì hai tùy chọn không có cùng ngữ nghĩa .


8

Như assylias đã chỉ ra, đây là một câu hỏi về ngữ nghĩa. Nói chung, Map.get (x) == null là những gì bạn muốn, nhưng có những trường hợp điều quan trọng là phải sử dụng hàm containsKey.

Một trong những trường hợp như vậy là một bộ nhớ cache. Tôi đã từng giải quyết vấn đề hiệu suất trong một ứng dụng web đang truy vấn cơ sở dữ liệu của nó thường xuyên để tìm kiếm các thực thể không tồn tại. Khi tôi nghiên cứu mã bộ nhớ đệm cho thành phần đó, tôi nhận ra rằng nó đang truy vấn cơ sở dữ liệu nếu cache.get (key) == null. Nếu cơ sở dữ liệu trả về null (không tìm thấy thực thể), chúng tôi sẽ lưu khóa đó vào bộ nhớ cache -> ánh xạ null.

Chuyển sang containsKey đã giải quyết được vấn đề vì ánh xạ tới giá trị null thực sự có ý nghĩa gì đó. Việc ánh xạ khóa thành null có ý nghĩa ngữ nghĩa khác với khóa không tồn tại.


Hấp dẫn. Tại sao bạn không chỉ cần thêm dấu kiểm trước khi các giá trị vào bộ nhớ đệm?
Saket,

Điều đó sẽ không thay đổi bất cứ điều gì. Vấn đề là ánh xạ khóa thành null có nghĩa là "chúng tôi đã làm việc này rồi. Nó được lưu vào bộ nhớ đệm. Giá trị null". Thay vì hoàn toàn không chứa một khóa nhất định, có nghĩa là "Không biết, không có trong bộ nhớ cache, chúng tôi có thể cần kiểm tra DB."
Brandon

4
  • containsKeytheo sau là a getchỉ thừa nếu chúng ta biết apriori rằng giá trị null sẽ không bao giờ được phép. Nếu giá trị null không hợp lệ, lời gọi containsKeycó một hình phạt hiệu suất không nhỏ và chỉ là chi phí như được hiển thị trong điểm chuẩn bên dưới.

  • Các Optionalthành ngữ Java 8 - Optional.ofNullable(map.get(key)).ifPresenthoặc Optional.ofNullable(map.get(key)).ifPresent- chịu một chi phí không nhỏ so với chỉ kiểm tra rỗng vani.

  • A HashMapsử dụng O(1)tra cứu bảng không đổi trong khi a TreeMapsử dụng O(log(n))tra cứu. Các containsKeytheo sau là một getthành ngữ là chậm hơn nhiều khi gọi trên TreeMap.

Điểm chuẩn

Xem https://github.com/vkarun/enum-reverse-lookup-table-jmh

// t1
static Type lookupTreeMapNotContainsKeyThrowGet(int t) {
  if (!lookupT.containsKey(t))
    throw new IllegalStateException("Unknown Multihash type: " + t);
  return lookupT.get(t);
}
// t2
static Type lookupTreeMapGetThrowIfNull(int t) {
  Type type = lookupT.get(t);
  if (type == null)
    throw new IllegalStateException("Unknown Multihash type: " + t);
  return type;
}
// t3
static Type lookupTreeMapGetOptionalOrElseThrow(int t) {
  return Optional.ofNullable(lookupT.get(t)).orElseThrow(() -> new 
      IllegalStateException("Unknown Multihash type: " + t));
}
// h1
static Type lookupHashMapNotContainsKeyThrowGet(int t) {
  if (!lookupH.containsKey(t))
    throw new IllegalStateException("Unknown Multihash type: " + t);
  return lookupH.get(t);
}
// h2
static Type lookupHashMapGetThrowIfNull(int t) {
  Type type = lookupH.get(t);
  if (type == null)
    throw new IllegalStateException("Unknown Multihash type: " + t);
  return type;
}
// h3
static Type lookupHashMapGetOptionalOrElseThrow(int t) {
  return Optional.ofNullable(lookupH.get(t)).orElseThrow(() -> new 
    IllegalStateException("Unknown Multihash type: " + t));
}
Điểm chuẩn (lặp lại) (tra cứu Ứng dụng) Chế độ Cnt Đơn vị lỗi Điểm

MultihashTypeLookupBenchmark.testLookup 1000 t1 trung bình 9 33,438 ± 4,514 us / op
MultihashTypeLookupBenchmark.testLookup 1000 t2 trung bình 9 26,986 ± 0,405 us / op
MultihashTypeLookupBenchmark.testLookup 1000 t3 trung bình 9 39.259 ± 1.306 us / op
MultihashTypeLookupBenchmark.testLookup 1000 h1 trung bình 9 18,954 ± 0,414 us / op
MultihashTypeLookupBenchmark.testLookup 1000 h2 trung bình 9 15.486 ± 0.395 us / op
MultihashTypeLookupBenchmark.testLookup 1000 h3 trung bình 9 16,780 ± 0,719 us / op

Tham khảo nguồn TreeMap

https://github.com/openjdk-mirror/jdk7u-jdk/blob/master/src/share/classes/java/util/TreeMap.java

Tham khảo nguồn HashMap

https://github.com/openjdk-mirror/jdk7u-jdk/blob/master/src/share/classes/java/util/HashMap.java


3

Chúng tôi có thể làm cho câu trả lời @assylias dễ đọc hơn với Java8 Tùy chọn,

Optional.ofNullable(map.get(key)).ifPresent(value -> {
     //do something with value
};)

2

Trong Java nếu bạn kiểm tra việc triển khai

public boolean containsKey(Object key) {
    return getNode(hash(key), key) != null;
}

public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

cả hai đều sử dụng getNode để lấy kết quả phù hợp, nơi công việc chính được thực hiện.

dự phòng là theo ngữ cảnh, chẳng hạn như nếu bạn có một từ điển được lưu trữ trong một bản đồ băm. Khi bạn muốn truy xuất nghĩa của một từ

đang làm...

if(dictionary.containsKey(word)) {
   return dictionary.get(word);
}

là thừa.

nhưng nếu bạn muốn kiểm tra một từ có hợp lệ hay không dựa vào từ điển. đang làm...

 return dictionary.get(word) != null;

kết thúc...

 return dictionary.containsKey(word);

là thừa.

Nếu bạn kiểm tra việc triển khai HashSet , sử dụng HashMap nội bộ, hãy sử dụng phương thức 'chứaKey' trong 'chứa'.

    public boolean contains(Object o) {
        return map.containsKey(o);
    }
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.