กำลังใช้ java Map.containsKey () ซ้ำซ้อนเมื่อใช้ map.get ()


93

ฉันสงสัยมาระยะหนึ่งแล้วว่ามันเป็นไปได้หรือไม่ตามแนวทางปฏิบัติที่ดีที่สุดที่จะละเว้นจากการใช้containsKey()วิธีการjava.util.Mapและทำการตรวจสอบค่าว่างกับผลลัพธ์จากget().

เหตุผลของฉันที่ดูเหมือนว่าจะทำซ้ำซ้อนการค้นหาของมูลค่าสองครั้ง - ครั้งแรกสำหรับและจากนั้นอีกครั้งสำหรับcontainsKey()get()

ในทางกลับกันอาจเป็นไปได้ว่าการใช้Mapแคชมาตรฐานส่วนใหญ่ในการค้นหาครั้งสุดท้ายหรือคอมไพลเลอร์สามารถกำจัดความซ้ำซ้อนได้และเพื่อความสามารถในการอ่านโค้ดจึงควรรักษาcontainsKey()ส่วนนั้นไว้

ฉันจะขอบคุณมากสำหรับความคิดเห็นของคุณ

คำตอบ:


112

การใช้งานแผนที่บางอย่างได้รับอนุญาตให้มีค่า null เช่น HashMap ในกรณีนี้หากget(key)ส่งคืนnullไม่รับประกันว่าจะไม่มีรายการในแผนที่ที่เชื่อมโยงกับคีย์นี้

ดังนั้นหากคุณต้องการทราบว่าแผนที่มีการใช้งานหลักหรือMap.containsKeyไม่ Map.get(key)ถ้าคุณเพียงแค่ต้องมีค่าแมปกับการใช้งานที่สำคัญ หากแผนที่นี้อนุญาตค่า null ดังนั้นค่าที่ส่งคืนของ null ไม่จำเป็นต้องระบุว่าแผนที่นั้นไม่มีการแมปสำหรับคีย์ ในกรณีเช่นนี้Map.containsKeyไม่มีประโยชน์และจะส่งผลต่อประสิทธิภาพ นอกจากนี้ในกรณีของการเข้าถึงพร้อมกันกับแผนที่ (เช่นConcurrentHashMap) หลังจากที่คุณผ่านการทดสอบมีโอกาสที่รายการจะถูกลบออกจากกระทู้ก่อนที่จะสายอื่นMap.containsKey(key)Map.get(key)


8
แม้ว่าค่าจะถูกตั้งค่าเป็นnullคุณต้องการที่จะถือว่าแตกต่างจากคีย์ / ค่าที่ไม่ได้ตั้งค่าหรือไม่? หากคุณไม่จำเป็นต้องปฏิบัติแตกต่างกันโดยเฉพาะคุณสามารถใช้get()
Peter Lawrey

1
หากMapเป็นprivateเช่นนั้นชั้นเรียนของคุณอาจรับประกันได้ว่าnullจะไม่มีการแทรกในแผนที่ ในกรณีที่คุณสามารถใช้get()ตามด้วยการตรวจสอบ null containsKey()แทน การทำเช่นนี้จะชัดเจนกว่าและอาจมีประสิทธิภาพมากกว่าเล็กน้อยในบางกรณี
Raedwald

44

ฉันคิดว่ามันค่อนข้างเป็นมาตรฐานที่จะเขียน:

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

แทน

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

มันอ่านได้ไม่น้อยและมีประสิทธิภาพมากกว่าเล็กน้อยดังนั้นฉันจึงไม่เห็นเหตุผลใด ๆ ที่จะไม่ทำ เห็นได้ชัดถ้าแผนที่ของคุณสามารถมีโมฆะสองตัวเลือกที่ไม่ได้มีความหมายเดียวกัน


8

ตามที่ระบุไว้ assylias นี่เป็นคำถามเชิงความหมาย โดยทั่วไป Map.get (x) == null คือสิ่งที่คุณต้องการ แต่มีบางกรณีที่สำคัญที่จะต้องใช้ containKey

กรณีหนึ่งคือแคช ครั้งหนึ่งฉันเคยทำงานกับปัญหาประสิทธิภาพการทำงานในเว็บแอปที่กำลังค้นหาฐานข้อมูลบ่อยครั้งเพื่อค้นหาเอนทิตีที่ไม่มีอยู่จริง เมื่อฉันศึกษารหัสการแคชสำหรับส่วนประกอบนั้นฉันรู้ว่ามันกำลังค้นหาฐานข้อมูลถ้า cache.get (key) == null หากฐานข้อมูลคืนค่า null (ไม่พบเอนทิตี) เราจะแคชคีย์นั้น -> การแมป null

การเปลี่ยนไปใช้ containsKey ช่วยแก้ปัญหาได้เนื่องจากการแมปกับค่า null มีความหมายจริงๆ การแมปคีย์กับ null มีความหมายเชิงความหมายแตกต่างจากคีย์ที่ไม่มีอยู่


น่าสนใจ. ทำไมคุณไม่เพิ่มการตรวจสอบค่าว่างก่อนที่จะแคชค่า?
สะเก็ด

นั่นจะไม่เปลี่ยนแปลงอะไร ประเด็นคือการแมปคีย์กับ null หมายความว่า "เราได้ทำสิ่งนี้แล้วมันถูกแคชไว้ค่าเป็นโมฆะ" เมื่อเทียบกับที่ไม่มีคีย์ใด ๆ เลยซึ่งหมายความว่า "ไม่ทราบไม่ใช่ในแคชเราอาจต้องตรวจสอบ DB"
Brandon

5
  • containsKeyตามด้วย a getซ้ำซ้อนก็ต่อเมื่อเรารู้ apriori ว่าค่า null จะไม่ได้รับอนุญาต ถ้าค่าว่างไม่ถูกต้องการเรียกใช้containsKeyจะมีโทษประสิทธิภาพที่ไม่สำคัญและเป็นเพียงค่าใช้จ่ายดังที่แสดงในเกณฑ์มาตรฐานด้านล่าง

  • OptionalสำนวนJava 8 Optional.ofNullable(map.get(key)).ifPresentหรือOptional.ofNullable(map.get(key)).ifPresent- มีค่าใช้จ่ายที่ไม่สำคัญเมื่อเทียบกับการตรวจสอบค่าว่างวานิลลา

  • A HashMapใช้การO(1)ค้นหาตารางคงที่ในขณะที่TreeMapใช้การO(log(n))ค้นหา containsKeyตามด้วยสำนวนได้ช้าเมื่อเรียกบนgetTreeMap

เกณฑ์มาตรฐาน

ดู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));
}
เกณฑ์มาตรฐาน (การทำซ้ำ) (lookupApproach) โหมด Cnt หน่วยคะแนนข้อผิดพลาด

MultihashTypeLookupBenchmark.testLookup 1000 t1 เฉลี่ย 9 33.438 ± 4.514 us / op
MultihashTypeLookupBenchmark.testLookup 1,000 t2 เฉลี่ย 9 26.986 ± 0.405 us / op
MultihashTypeLookupBenchmark.testLookup 1,000 t3 เฉลี่ย 9 39.259 ± 1.306 us / op
MultihashTypeLookupBenchmark.testLookup 1000 h1 เฉลี่ย 9 18.954 ± 0.414 us / op
MultihashTypeLookupBenchmark.testLookup 1,000 h2 เฉลี่ย 9 15.486 ± 0.395 us / op
MultihashTypeLookupBenchmark.testLookup 1000 h3 เฉลี่ย 9 16.780 ± 0.719 us / op

แหล่งอ้างอิง TreeMap

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

แหล่งอ้างอิง HashMap

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


3

เราสามารถทำให้คำตอบ @assylias อ่านง่ายขึ้นด้วย Java8 Optional,

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

2

ใน Java หากคุณตรวจสอบการใช้งาน

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;
}

ทั้งคู่ใช้ getNode เพื่อดึงข้อมูลการจับคู่ซึ่งงานหลักจะเสร็จสิ้น

ความซ้ำซ้อนเป็นบริบทเช่นหากคุณมีพจนานุกรมที่เก็บไว้ในแผนที่แฮช เมื่อคุณต้องการดึงความหมายของคำศัพท์

กำลังทำ ...

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

ซ้ำซ้อน

แต่ถ้าคุณต้องการตรวจสอบคำว่าถูกต้องหรือไม่ขึ้นอยู่กับพจนานุกรม กำลังทำ ...

 return dictionary.get(word) != null;

เกิน...

 return dictionary.containsKey(word);

ซ้ำซ้อน

หากคุณตรวจสอบการใช้งานHashSetซึ่งใช้ HashMap ภายในให้ใช้เมธอด 'containKey' ใน 'มี'

    public boolean contains(Object o) {
        return map.containsKey(o);
    }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.