Java HashMap จัดการกับวัตถุต่าง ๆ ด้วยรหัสแฮชเดียวกันได้อย่างไร


223

ตามความเข้าใจของฉันฉันคิดว่า:

  1. มันถูกกฎหมายอย่างสมบูรณ์แบบสำหรับวัตถุสองชิ้นที่มีแฮชโค้ดเดียวกัน
  2. หากวัตถุสองชนิดเท่ากัน (ใช้เมธอด equals ()) วัตถุเหล่านั้นจะมีแฮชโค้ดเดียวกัน
  3. หากวัตถุสองชิ้นไม่เท่ากันวัตถุเหล่านั้นจะไม่มีแฮชโค้ดเดียวกัน

ฉันถูกไหม?

ตอนนี้ถ้าถูกต้องฉันมีคำถามต่อไปนี้: HashMapภายในใช้ hashcode ของวัตถุ ดังนั้นหากวัตถุสองชิ้นสามารถมีแฮชโค้ดเดียวกันได้ดังนั้นHashMapแทร็กที่ใช้คีย์นั้นจะทำอย่างไร

บางคนสามารถอธิบายวิธีการHashMapใช้แฮชโค้ดของวัตถุได้อย่างไร


29
สำหรับบันทึก: # 1 และ # 2 ถูกต้อง # 3 ผิด: วัตถุสองชิ้นที่ไม่เท่ากันอาจมีรหัสแฮชเดียวกัน
Joachim Sauer

6
# 1 และ # 3 ขัดแย้งแม้กระทั่ง
Delfic

แน่นอนถ้า # 2 ไม่ปฏิบัติตามดังนั้นการดำเนินการเท่ากับ () (หรือ arguably the hashCode ()) ไม่ถูกต้อง
Joachim Sauer

คำตอบ:


346

hashmap ทำงานเช่นนี้ (นี่ง่ายเล็กน้อย แต่มันแสดงให้เห็นถึงกลไกพื้นฐาน):

มันมีจำนวนของ "ถัง" ที่จะใช้ในการเก็บคู่ค่าคีย์ในแต่ละถังมีหมายเลขที่ไม่ซ้ำกัน - นั่นคือสิ่งที่ระบุถัง เมื่อคุณใส่คู่คีย์ - ค่าลงในแผนที่ hashmap จะดูที่รหัสแฮชของคีย์และเก็บคู่ไว้ในที่ฝากข้อมูลที่ตัวระบุเป็นรหัสแฮชของคีย์ ตัวอย่างเช่น: รหัสแฮชของคีย์คือ 235 -> ทั้งคู่ถูกเก็บไว้ในหมายเลขที่ฝากข้อมูล 235 (โปรดทราบว่าที่เก็บข้อมูลหนึ่งชุดสามารถเก็บคู่คีย์ - ค่าได้มากกว่าหนึ่งคู่)

เมื่อคุณค้นหาค่าใน hashmap โดยให้คีย์เป็นค่าแรกจะดูที่รหัสแฮชของคีย์ที่คุณให้ hashmap จะมองเข้าไปในที่ฝากข้อมูลที่เกี่ยวข้องจากนั้นจะเปรียบเทียบคีย์ที่คุณให้กับคีย์ของคู่ทั้งหมดในที่ฝากข้อมูลโดยเปรียบเทียบกับที่เก็บข้อมูลเหล่าequals()นั้น

ตอนนี้คุณสามารถดูได้ว่าวิธีนี้มีประสิทธิภาพมากในการค้นหาคู่คีย์ - ค่าในแผนที่อย่างไร: โดยรหัสแฮชของคีย์ hashmap จะรู้ทันทีว่าจะมองที่ฝากข้อมูลใด

เมื่อดูที่กลไกด้านบนคุณสามารถเห็นความต้องการที่จำเป็นเกี่ยวกับhashCode()และequals()วิธีการของคีย์:

  • หากสองปุ่มเหมือนกัน ( equals()ส่งคืนtrueเมื่อคุณเปรียบเทียบ) hashCode()วิธีการของพวกเขาจะต้องส่งคืนหมายเลขเดิม หากคีย์ละเมิดสิ่งนี้คีย์ที่มีค่าเท่ากันอาจถูกเก็บไว้ในที่เก็บข้อมูลที่แตกต่างกันและ hashmap จะไม่สามารถหาคู่คีย์ - ค่า (เพราะจะดูในที่เก็บข้อมูลเดียวกัน)

  • หากมีสองปุ่มที่แตกต่างกันก็ไม่สำคัญว่ารหัสแฮชจะเหมือนกันหรือไม่ พวกเขาจะถูกเก็บไว้ในที่ฝากข้อมูลเดียวกันหากรหัสแฮชของพวกเขาเหมือนกันและในกรณีนี้ hashmap จะใช้equals()เพื่อแยกพวกเขาออกจากกัน


4
คุณเขียน "และ hashmap จะไม่สามารถหาคู่คีย์ - ค่า (เพราะมันจะดูในที่ฝากข้อมูลเดียวกัน)" คุณช่วยอธิบายได้ไหมว่ามันจะดูในที่เก็บข้อมูลเดียวกันบอกว่าทั้งสองเท่ากับวัตถุคือ t1 และ t2 และพวกเขาเท่ากันและ t1 และ t2 มี hashcodes h1 และ h2 ตามลำดับดังนั้น t1.equals (t2) = จริงและ h1! = h2 ดังนั้นเมื่อ hashmap จะหา t1 มันจะดูใน bucket h1 และสำหรับ t2 ใน bucket t2?
Geek

19
ถ้าสองปุ่มมีค่าเท่ากัน แต่พวกเขาhashCode()รหัสกัญชาผลตอบแทนวิธีการที่แตกต่างกันแล้วequals()และวิธีการเรียนที่สำคัญละเมิดสัญญาและคุณจะได้ผลลัพธ์ที่แปลกเมื่อใช้คีย์เหล่านั้นในhashCode() HashMap
Jesper

ที่เก็บข้อมูลแต่ละชุดสามารถมีคู่ค่าคีย์ได้หลายคู่ซึ่งใช้รายการที่ลิงก์ภายใน แต่ความสับสนของฉันคือ - ถังที่นี่คืออะไร? โครงสร้างข้อมูลใดที่ใช้ภายใน มีการเชื่อมต่อระหว่างถังหรือไม่?
Ankit Sharma

1
@AnkitSharma หากคุณต้องการทราบรายละเอียดทั้งหมดจริง ๆ ให้ค้นหาซอร์สโค้ดของHashMapซึ่งคุณสามารถค้นหาในไฟล์src.zipในไดเรกทอรีการติดตั้ง JDK ของคุณ
Jesper

1
@ 1290 ความสัมพันธ์เดียวระหว่างคีย์ในที่ฝากข้อมูลเดียวกันคือมีรหัสแฮชเดียวกัน
Jesper

88

การยืนยันที่สามของคุณไม่ถูกต้อง

มันถูกกฎหมายอย่างสมบูรณ์แบบสำหรับวัตถุที่ไม่เท่ากันสองตัวที่มีรหัสแฮชเดียวกัน มันถูกใช้HashMapเป็น "ตัวกรองผ่านแรก" เพื่อให้แผนที่สามารถค้นหารายการที่เป็นไปได้อย่างรวดเร็วด้วยคีย์ที่ระบุ คีย์ที่มีรหัสแฮชเดียวกันจะถูกทดสอบเพื่อความเท่าเทียมกันกับคีย์ที่ระบุ

คุณไม่ต้องการข้อกำหนดที่วัตถุที่ไม่เท่ากันสองตัวไม่สามารถมีรหัสแฮชเดียวกันได้มิฉะนั้นจะทำให้คุณมีวัตถุที่เป็นไปได้2 32ตัว (หมายความว่าประเภทที่แตกต่างไม่สามารถใช้ฟิลด์ของวัตถุเพื่อสร้างรหัสแฮชได้เนื่องจากคลาสอื่น ๆ สามารถสร้างแฮชเดียวกันได้)


6
คุณมาถึงวัตถุที่เป็นไปได้ 2 ^ 32 อย่างไร
Geek

5
ฉันมาช้า แต่สำหรับคนที่ยังสงสัย: แฮชโค้ดใน Java นั้นเป็น int และ int มีค่าที่เป็นไปได้ 2 ^ 32
Xerus

69

แผนภาพโครงสร้าง HashMap

HashMapเป็นอาร์เรย์ของEntryวัตถุ

พิจารณาHashMapเป็นเพียงอาร์เรย์ของวัตถุ

ดูสิ่งนี้Objectคือ:

static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        final int hash;
 
}

แต่ละEntryวัตถุแสดงถึงคู่ของคีย์ - ค่า เขตข้อมูลnextอ้างถึงEntryวัตถุอื่นถ้าที่ฝากข้อมูลมีมากกว่าหนึ่งEntryรายการ

บางครั้งอาจเกิดขึ้นได้ว่ารหัสแฮชสำหรับวัตถุที่แตกต่างกัน 2 แบบนั้นเหมือนกัน ในกรณีนี้วัตถุสองรายการจะถูกบันทึกในที่เก็บข้อมูลเดียวและจะแสดงเป็นรายการที่เชื่อมโยง จุดเริ่มต้นเป็นวัตถุที่เพิ่มขึ้นเมื่อเร็ว ๆ นี้ วัตถุนี้หมายถึงวัตถุอื่นที่มีnextเขตข้อมูลและอื่น ๆ nullรายการสุดท้ายหมายถึง

เมื่อคุณสร้าง a HashMapด้วยคอนสตรัคเตอร์เริ่มต้น

HashMap hashMap = new HashMap();

อาร์เรย์ถูกสร้างขึ้นด้วยขนาด 16 และโหลดเริ่มต้น 0.75

การเพิ่มคู่คีย์ - ค่าใหม่

  1. คำนวณรหัสลับสำหรับกุญแจ
  2. คำนวณตำแหน่งhash % (arrayLength-1)ที่ควรวางองค์ประกอบ (หมายเลขถัง)
  3. หากคุณพยายามที่จะเพิ่มมูลค่าด้วยรหัสที่ได้รับการบันทึกไว้HashMapแล้วค่าจะถูกเขียนทับ
  4. องค์ประกอบอื่นจะถูกเพิ่มไปยังถัง

หากที่เก็บข้อมูลมีองค์ประกอบอย่างน้อยหนึ่งรายการอยู่ใหม่จะได้รับการเพิ่มและวางในตำแหน่งแรกของที่เก็บข้อมูล nextเขตข้อมูลของมันหมายถึงองค์ประกอบเก่า

การลบ

  1. คำนวณ hashcode สำหรับรหัสที่ให้
  2. คำนวณหมายเลขถัง hash % (arrayLength-1)
  3. รับการอ้างอิงไปยังออบเจครายการแรกในที่เก็บข้อมูลและโดยใช้วิธีเท่ากับทำซ้ำกับรายการทั้งหมดในที่เก็บข้อมูลที่กำหนด Entryในที่สุดเราจะได้พบกับที่ถูกต้อง หากไม่พบองค์ประกอบที่ต้องการให้ส่งคืนnull

3
นี้เป็นธรรมมันจะเป็นhash % (arrayLength-1) hash % arrayLengthแต่มันก็เป็นจริง hash & (arrayLength-1)นั่นคือเนื่องจากมันใช้กำลังสอง ( 2^n) สำหรับความยาวของอาเรnย์
weston

ฉันคิดว่ามันไม่ใช่อาร์เรย์ของวัตถุ Entity แต่เป็นอาร์เรย์ของ LinkedList / Tree และต้นไม้แต่ละต้นภายในมีวัตถุ Entity
Mudit bhaintwal

@shevchyk ทำไมเราถึงเก็บคีย์และแฮช พวกเขาใช้อะไร เราไม่ได้สูญเสียความทรงจำที่นี่หรือ
roottraveller

hashset ภายในใช้ hashmap กฎการเพิ่มและการลบของ hashmap มีผลดีต่อ hashset หรือไม่
แลกเปลี่ยน

2
@weston ไม่เพียง แต่ที่ hashCode เป็นintซึ่งแน่นอนสามารถลบการทำแบบโมดูโลกับจำนวนเชิงลบจะทำให้คุณเป็นจำนวนลบ
ยู

35

คุณสามารถค้นหาข้อมูลที่ยอดเยี่ยมได้ที่http://javarevisited.blogspot.com/2011/02/how-hashmap-works-in-java.html

เพื่อสรุป:

HashMap ทำงานบนหลักการของการแฮช

ใส่ (คีย์ค่า): HashMap เก็บทั้งวัตถุที่สำคัญและค่าเป็น Map.Entry Hashmap ใช้ hashcode (กุญแจ) เพื่อรับฝากข้อมูล หากมีการชนกัน HashMap ใช้ LinkedList เพื่อเก็บวัตถุ

get (key): HashMap ใช้ hashcode ของ Key Object เพื่อค้นหาตำแหน่งที่เก็บข้อมูลจากนั้นเรียกใช้วิธี keys.equals () เพื่อระบุโหนดที่ถูกต้องใน LinkedList และส่งคืนวัตถุค่าที่เกี่ยวข้องสำหรับคีย์นั้นใน Java HashMap


3
ผมพบคำตอบที่มีให้โดยแจสเปอร์ที่ดีกว่าผมก็รู้สึกว่าบล็อกเป็นมากขึ้นต่อการจัดการสัมภาษณ์กว่าการทำความเข้าใจแนวคิด
Narendra N

@ NarendraN ฉันเห็นด้วยกับคุณ
Abhijit Gaikwad

22

นี่คือคำอธิบายคร่าวๆของHashMapกลไก 's สำหรับJava 8รุ่น(มันอาจจะแตกต่างกันเล็กน้อยจาก Java 6)


โครงสร้างข้อมูล

  • ตาราง
    แฮชค่าแฮชคำนวณผ่านทางhash()คีย์และจะตัดสินใจว่าที่เก็บข้อมูลของ hashtable ใดที่จะใช้สำหรับคีย์ที่กำหนด
  • รายการที่เชื่อมโยง (เดียวดาย)
    เมื่อจำนวนองค์ประกอบในที่เก็บข้อมูลมีขนาดเล็กรายการที่เชื่อมโยงโดยลำพังจะใช้
  • ต้นไม้สีแดงดำ
    เมื่อนับองค์ประกอบในถังขนาดใหญ่ต้นไม้สีแดงดำใช้

คลาส(ภายใน)

  • Map.Entry
    แสดงเอนทิตีเดียวใน map, หน่วยคีย์ / ค่า
  • HashMap.Node
    รายการรุ่นที่เชื่อมโยงของโหนด

    มันสามารถเป็นตัวแทนของ:

    • ถังแฮช
      เพราะมันมีคุณสมบัติกัญชา
    • โหนดในรายการที่เชื่อมโยงโดยลำพัง(จึงยังหัวของ LinkedList)
  • HashMap.TreeNode
    แผนผังรุ่นของโหนด

ฟิลด์(ภายใน)

  • Node[] table
    ตารางฝากข้อมูล (ส่วนหัวของรายการที่เชื่อมโยง)
    หากที่ฝากข้อมูลไม่มีองค์ประกอบแสดงว่าเป็นค่าว่างดังนั้นจึงใช้พื้นที่ของการอ้างอิงเท่านั้น
  • Set<Map.Entry> entrySet ชุดเอนทิตี้
  • int size
    จำนวนกิจการ
  • float loadFactor
    ระบุว่าอนุญาตให้ใช้ตารางแฮชแบบเต็มได้อย่างไรก่อนปรับขนาด
  • int threshold
    ขนาดถัดไปที่จะปรับขนาด
    สูตร:threshold = capacity * loadFactor

วิธีการ(ภายใน)

  • int hash(key)
    คำนวณแฮชด้วยกุญแจ
  • วิธีแมปแฮชกับถังข้อมูลอย่างไร
    ใช้ตรรกะต่อไปนี้:

    static int hashToBucket(int tableSize, int hash) {
        return (tableSize - 1) & hash;
    }

เกี่ยวกับความจุ

table.lengthในตารางแฮชกำลังการผลิตหมายถึงการนับถังก็อาจจะได้รับจาก
สามารถคำนวณได้ด้วยthresholdและloadFactorไม่จำเป็นต้องกำหนดเป็นฟิลด์คลาส

สามารถรับความจุที่มีประสิทธิภาพผ่าน: capacity()


การดำเนินงาน

  • ค้นหาเอนทิตีตามคีย์
    ขั้นแรกให้ค้นหาที่ฝากข้อมูลตามค่าแฮชจากนั้นวนซ้ำรายการที่ลิงก์หรือค้นหาทรีเรียง
  • เพิ่มเอนทิตีด้วยคีย์
    ก่อนอื่นให้ค้นหา bucket ตามค่าแฮชของคีย์
    จากนั้นลองค้นหาค่า:
    • หากพบให้เปลี่ยนค่า
    • มิฉะนั้นเพิ่มโหนดใหม่ที่จุดเริ่มต้นของรายการที่เชื่อมโยงหรือแทรกลงในต้นไม้ที่เรียงลำดับ
  • ปรับขนาด
    เมื่อthresholdถึงจะเพิ่มความจุของ hashtable เป็นสองเท่า ( table.length) จากนั้นทำการแฮชอีกครั้งบนองค์ประกอบทั้งหมดเพื่อสร้างตารางใหม่
    นี่อาจเป็นการดำเนินการที่มีราคาแพง

ประสิทธิภาพ

  • รับและใส่
    ความซับซ้อนของเวลาเป็นO(1)เพราะ:
    • Bucket เข้าถึงได้จากดัชนีอาเรO(1)ย์
    • รายการที่เชื่อมโยงในที่เก็บข้อมูลแต่ละอันมีความยาวน้อยจึงสามารถดูO(1)ได้
    • ขนาดของต้นไม้ยังถูก จำกัด เพราะจะขยายกำลังการผลิตและอีกครั้งเมื่อกัญชาเพิ่มขึ้นนับองค์ประกอบนั้นอาจดูว่ามันเป็นไม่ได้O(1)O(log N)

คุณช่วยยกตัวอย่างความซับซ้อนของเวลาได้
Jitendra

@jsroyal นี้อาจอธิบายความซับซ้อนมากขึ้นอย่างชัดเจน: en.wikipedia.org/wiki/Hash_table แต่ในระยะสั้น: การค้นหากลุ่มเป้าหมายคือ O (1) เพราะคุณค้นหาโดยกลุ่มดัชนีในอาร์เรย์ จากนั้นภายในถังจำนวนคือองค์ประกอบมีขนาดเล็ก & โดยเฉลี่ยจำนวนคงที่แม้จะมีจำนวนองค์ประกอบทั้งหมดใน hashtable ทั้งหมดดังนั้นการค้นหาองค์ประกอบเป้าหมายภายในถังก็คือ O (1) ดังนั้น O (1) + O (1) = O (1)
Eric Wang

14

hashcode กำหนดที่ฝากข้อมูลสำหรับ hashmap ที่จะตรวจสอบ หากมีวัตถุมากกว่าหนึ่งรายการในที่ฝากข้อมูลการค้นหาเชิงเส้นจะถูกดำเนินการเพื่อค้นหาว่ารายการใดในที่เก็บข้อมูลเท่ากับรายการที่ต้องการ (ใช้equals()วิธี)

กล่าวอีกนัยหนึ่งถ้าคุณมีแฮชโค้ดที่สมบูรณ์แบบการเข้าถึงแฮชแมปนั้นคงที่คุณจะไม่ต้องวนซ้ำผ่านที่เก็บข้อมูล (โดยทางเทคนิคคุณต้องมีถังเก็บ MAX_INT การใช้งานจาวาอาจแบ่งปันรหัสแฮชสองสามอันในถังเดียวกัน ลดความต้องการพื้นที่) หากคุณมีรหัสแฮชโค้ดที่แย่ที่สุด (มักจะส่งกลับหมายเลขเดิมเสมอ) การเข้าถึงแฮชแมพของคุณจะเป็นแบบเชิงเส้นเนื่องจากคุณต้องค้นหาทุกรายการในแผนที่

ส่วนใหญ่แฮชโค้ดที่เขียนได้ดีนั้นไม่สมบูรณ์แบบ แต่มีเอกลักษณ์ที่จะให้คุณเข้าถึงได้มากขึ้นหรือน้อยลง


11

คุณเข้าใจผิดในจุดที่สาม สองรายการสามารถมีรหัสแฮชเดียวกัน แต่ไม่เท่ากัน ลองดูที่การดำเนินการที่HashMap.get จาก OpenJDK คุณจะเห็นว่ามันตรวจสอบว่าแฮชเท่ากันและปุ่มนั้นเท่ากัน มีจุดสามจริงแล้วมันจะไม่จำเป็นในการตรวจสอบว่าคีย์เท่ากัน โค้ดแฮชจะถูกเปรียบเทียบก่อนคีย์เนื่องจากโค้ดเก่าเป็นการเปรียบเทียบที่มีประสิทธิภาพมากกว่า

หากคุณสนใจที่จะเรียนรู้เพิ่มเติมเกี่ยวกับเรื่องนี้ลองดูที่บทความ Wikipedia เกี่ยวกับการแก้ปัญหาการชนแบบเปิดที่อยู่ซึ่งฉันเชื่อว่าเป็นกลไกที่การใช้งานของ OpenJdk ใช้ กลไกนั้นแตกต่างจาก "ถัง" อย่างละเอียดซึ่งเป็นหนึ่งในคำตอบอื่น ๆ ที่กล่าวถึง


6
import java.util.HashMap;

public class Students  {
    String name;
    int age;

    Students(String name, int age ){
        this.name = name;
        this.age=age;
    }

    @Override
    public int hashCode() {
        System.out.println("__hash__");
        final int prime = 31;
        int result = 1;
        result = prime * result + age;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        System.out.println("__eq__");
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Students other = (Students) obj;
        if (age != other.age)
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }

    public static void main(String[] args) {

        Students S1 = new Students("taj",22);
        Students S2 = new Students("taj",21);

        System.out.println(S1.hashCode());
        System.out.println(S2.hashCode());

        HashMap<Students,String > HM = new HashMap<Students,String > (); 
        HM.put(S1, "tajinder");
        HM.put(S2, "tajinder");
        System.out.println(HM.size());
    }
}

Output:

__ hash __

116232

__ hash __

116201

__ hash __

__ hash __

2

ดังนั้นที่นี่เราจะเห็นว่าหากวัตถุทั้ง S1 และ S2 มีเนื้อหาที่แตกต่างกันเราค่อนข้างมั่นใจว่าวิธีการ Hashcode ที่ถูกแทนที่ของเราจะสร้าง Hashcode ที่แตกต่างกัน (116232,11601) สำหรับวัตถุทั้งสอง ตอนนี้เนื่องจากมีรหัสแฮชที่แตกต่างกันดังนั้นจึงไม่ต้องเรียกวิธี EQUALS เนื่องจาก Hashcode ที่แตกต่างกันรับประกันเนื้อหาที่แตกต่างในวัตถุ

    public static void main(String[] args) {

        Students S1 = new Students("taj",21);
        Students S2 = new Students("taj",21);

        System.out.println(S1.hashCode());
        System.out.println(S2.hashCode());

        HashMap<Students,String > HM = new HashMap<Students,String > (); 
        HM.put(S1, "tajinder");
        HM.put(S2, "tajinder");
        System.out.println(HM.size());
    }
}

Now lets change out main method a little bit. Output after this change is 

__ hash __

116201

__ hash __

116201

__ hash __

__ hash __

__ eq __

1
We can clearly see that equal method is called. Here is print statement __eq__, since we have same hashcode, then content of objects MAY or MAY not be similar. So program internally  calls Equal method to verify this. 


Conclusion 
If hashcode is different , equal method will not get called. 
if hashcode is same, equal method will get called.

Thanks , hope it helps. 

3

วัตถุสองชิ้นมีค่าเท่ากันหมายความว่าพวกเขามีแฮชโค้ดเดียวกัน แต่ไม่ตรงกันข้าม

2 วัตถุที่เท่ากัน ------> มันมีแฮชโค้ดเดียวกัน

2 วัตถุมีแฮชโค้ดเดียวกัน ---- xxxxx -> ไม่เท่ากัน

อัปเดต Java 8 ใน HashMap-

คุณทำการดำเนินการนี้ในรหัสของคุณ -

myHashmap.put("old","old-value");
myHashMap.put("very-old","very-old-value");

ดังนั้นสมมติว่า hashcode ของคุณส่งคืนสำหรับทั้งสองคีย์"old"และ"very-old"เหมือนกัน แล้วจะเกิดอะไรขึ้น

myHashMapคือ HashMap และสมมติว่าเริ่มแรกคุณไม่ได้ระบุความจุ ดังนั้นความจุเริ่มต้นตาม java คือ 16 ดังนั้นทันทีที่คุณเริ่มต้น hashmap โดยใช้คำหลักใหม่มันสร้าง 16 buckets ตอนนี้เมื่อคุณดำเนินการคำสั่งแรก -

myHashmap.put("old","old-value");

จากนั้น"old"คำนวณhashcode สำหรับและเนื่องจาก hashcode อาจเป็นจำนวนเต็มมากเกินไปดังนั้น java ทำสิ่งนี้ภายใน - (แฮชคือ hashcode ที่นี่และ >>> เป็นกะที่ถูกต้อง)

hash XOR hash >>> 16

ดังนั้นเพื่อให้เป็นภาพที่ใหญ่กว่ามันจะกลับดัชนีบางอย่างซึ่งจะอยู่ระหว่าง 0 ถึง 15 ตอนนี้คู่ค่าคีย์ของคุณ"old"และ"old-value"จะถูกแปลงเป็นคีย์ของวัตถุรายการและตัวแปรอินสแตนซ์ค่าตัวแปร และจากนั้นวัตถุรายการนี้จะถูกเก็บไว้ในที่ฝากข้อมูลหรือคุณสามารถพูดได้ว่าที่ดัชนีเฉพาะวัตถุรายการนี้จะถูกเก็บไว้

FYI- รายการเป็นคลาสในแผนที่อินเตอร์เฟส - Map.Entry พร้อมลายเซ็น / คำจำกัดความเหล่านี้

class Entry{
          final Key k;
          value v;
          final int hash;
          Entry next;
}

ตอนนี้เมื่อคุณรันคำสั่งถัดไป -

myHashmap.put("very-old","very-old-value");

และ"very-old"ให้แฮชโค้ดเดียวกัน"old"ดังนั้นคู่ค่าคีย์ใหม่นี้จะถูกส่งไปยังดัชนีเดียวกันหรือที่ฝากข้อมูลเดียวกันอีกครั้ง แต่เนื่องจากที่ฝากข้อมูลนี้ไม่ว่างเปล่าดังนั้นnextตัวแปรของรายการวัตถุจึงถูกใช้เพื่อเก็บคู่ค่าคีย์ใหม่นี้

และสิ่งนี้จะถูกเก็บไว้เป็นรายการที่เชื่อมโยงสำหรับทุกวัตถุที่มี hashcode เดียวกัน แต่ TRIEFY_THRESHOLD ถูกระบุด้วยค่า 6 ดังนั้นหลังจากถึงรายการนี้รายการที่เชื่อมโยงจะถูกแปลงเป็นต้นไม้ที่สมดุล (ต้นไม้สีแดงดำ) ที่มีองค์ประกอบแรกเป็น ราก.


คำตอบที่น่าประทับใจ (y)
Sudhanshu Gaur

2

แต่ละรายการวัตถุหมายถึงคู่ของคีย์ - ค่า ฟิลด์ถัดไปอ้างถึงออบเจ็กต์อื่นถ้าที่ฝากข้อมูลมีมากกว่า 1 รายการ

บางครั้งมันอาจเกิดขึ้นที่ hashCodes สำหรับ 2 วัตถุที่แตกต่างกันเหมือนกัน ในกรณีนี้ 2 วัตถุจะถูกบันทึกไว้ในที่เก็บข้อมูลเดียวและจะถูกนำเสนอเป็น LinkedList จุดเริ่มต้นเป็นวัตถุที่เพิ่มขึ้นเมื่อเร็ว ๆ นี้ วัตถุนี้หมายถึงวัตถุอื่นที่มีเขตข้อมูลถัดไปและสิ่งหนึ่ง รายการสุดท้ายหมายถึง null เมื่อคุณสร้าง HashMap ด้วย Constructor เริ่มต้น

Array ได้รับการสร้างขึ้นด้วยขนาด 16 และความสมดุลโหลดเริ่มต้น 0.75

ป้อนคำอธิบายรูปภาพที่นี่

(ที่มา)


1

แผนที่แฮชทำงานบนหลักการของการแฮช

HashMap get (Key k) วิธีการเรียกวิธี hashCode บนวัตถุที่สำคัญและนำกลับ hashValue กลับไปที่ฟังก์ชั่นแฮชแบบคงที่ของตัวเองเพื่อค้นหาตำแหน่งถัง (อาร์เรย์สำรอง) ที่คีย์และค่าจะถูกเก็บไว้ในรูปแบบของชั้นซ้อน รายการ) ดังนั้นคุณจึงสรุปได้ว่าจากบรรทัดก่อนหน้านี้ทั้งคีย์และค่าถูกเก็บไว้ในที่ฝากข้อมูลเป็นรูปแบบของวัตถุรายการ ดังนั้นการคิดว่ามีเพียงค่าที่เก็บไว้ในที่เก็บข้อมูลไม่ถูกต้องและจะไม่สร้างความประทับใจที่ดีให้กับผู้สัมภาษณ์

  • เมื่อใดก็ตามที่เราเรียกวิธีรับ (Key k) บนวัตถุ HashMap ก่อนอื่นตรวจสอบว่าคีย์เป็นโมฆะหรือไม่ โปรดทราบว่าสามารถมีได้เพียงหนึ่งคีย์ null ใน HashMap

หากคีย์เป็นโมฆะปุ่ม Null จะจับคู่กับแฮช 0 เสมอดังนั้นดัชนี 0

หากคีย์ไม่เป็นโมฆะมันจะเรียก hashfunction บนวัตถุสำคัญดูบรรทัดที่ 4 ในวิธีการด้านบนเช่น key.hashCode () ดังนั้นหลังจาก key.hashCode () ส่งคืน hashValue แล้วบรรทัดที่ 4 จะมีลักษณะดังนี้

            int hash = hash(hashValue)

และตอนนี้มันใช้ hashValue ที่ส่งคืนไปยังฟังก์ชันการแฮชของตัวเอง

เราอาจสงสัยว่าทำไมเราจึงคำนวณ hashvalue อีกครั้งโดยใช้ hash (hashValue) คำตอบคือมันปกป้องฟังก์ชันแฮชที่มีคุณภาพต่ำ

ตอนนี้ hashvalue สุดท้ายถูกใช้เพื่อค้นหาตำแหน่งฝากข้อมูลที่เก็บรายการวัตถุ รายการที่เก็บวัตถุในถังเช่นนี้ (แฮ, กุญแจ, ค่า, bucketindex)


1

ฉันจะไม่เข้าไปดูรายละเอียดว่า HashMap ทำงานอย่างไร แต่จะยกตัวอย่างเพื่อให้เราจำได้ว่า HashMap ทำงานอย่างไรโดยเกี่ยวข้องกับความเป็นจริง

เรามี Key, Value, HashCode และ bucket

สำหรับบางครั้งเราจะเชื่อมโยงแต่ละรายการดังต่อไปนี้:

  • ถัง -> A Society
  • HashCode -> ที่อยู่ของสังคม (ไม่ซ้ำกันเสมอ)
  • ค่า -> บ้านในสังคม
  • คีย์ -> ที่อยู่บ้าน

ใช้ Map.get (กุญแจ):

Stevie ต้องการไปที่บ้าน (Josse) เพื่อนของเขาที่อาศัยอยู่ในบ้านพักในสังคม VIP ให้เป็น JavaLovers Society ที่อยู่ของ Josse คือ SSN ของเขา (ซึ่งแตกต่างกันสำหรับทุกคน) มีการบำรุงรักษาดัชนีที่เราค้นหาชื่อของสังคมตาม SSN ดัชนีนี้ถือได้ว่าเป็นอัลกอริทึมในการค้นหา HashCode

  • ชื่อ SSN Society
  • 92313 (Josse's) - JavaLovers
  • 13214 - AngularJSLovers
  • 98080 - JavaLovers
  • 53808 - ชีววิทยาเงินทอง

  1. SSN (คีย์) นี้ให้ HashCode (จากตารางดัชนี) ก่อนซึ่งเป็นชื่อของ Society
  2. ตอนนี้บ้านขนาดเล็กสามารถอยู่ในสังคมเดียวกันได้ดังนั้น HashCode จึงเป็นเรื่องธรรมดา
  3. สมมติว่าสังคมเป็นเรื่องปกติสำหรับบ้านสองหลังเราจะระบุบ้านที่เราจะไปได้อย่างไรโดยใช้คีย์ (SSN) ซึ่งไม่มีอะไรนอกจากบ้านที่อยู่

ใช้ Map.put (คีย์, ค่า)

สิ่งนี้จะค้นหาสังคมที่เหมาะสมสำหรับค่านี้โดยการค้นหา HashCode แล้วเก็บค่าไว้

ฉันหวังว่านี่จะช่วยได้และสิ่งนี้เปิดสำหรับการแก้ไข


0

มันจะเป็นคำตอบที่ยาวคว้าเครื่องดื่มและอ่านต่อ ...

การแปลงข้อมูลเป็นข้อมูลเกี่ยวกับการจัดเก็บคู่คีย์ - ค่าในหน่วยความจำที่สามารถอ่านและเขียนได้เร็วขึ้น มันเก็บคีย์ในอาร์เรย์และค่าใน LinkedList

ให้บอกว่าฉันต้องการเก็บค่าคีย์ 4 คู่ -

{
girl => ahhan , 
misused => Manmohan Singh , 
horsemints => guess what”, 
no => way
}

ดังนั้นเพื่อเก็บกุญแจเราต้องมีองค์ประกอบ 4 อย่าง ตอนนี้ฉันจะแมปหนึ่งใน 4 คีย์เหล่านี้กับดัชนีอาร์เรย์ 4 รายการ (0,1,2,3) ได้อย่างไร

ดังนั้นจาวาจะค้นหา hashCode ของแต่ละคีย์และแมปไปยังดัชนีอาเรย์เฉพาะ สูตร Hashcode คือ -

1) reverse the string.

2) keep on multiplying ascii of each character with increasing power of 31 . then add the components .

3) So hashCode() of girl would be –(ascii values of  l,r,i,g are 108, 114, 105 and 103) . 

e.g. girl =  108 * 31^0  + 114 * 31^1  + 105 * 31^2 + 103 * 31^3  = 3173020

Hash and girl !! ฉันรู้ว่าคุณกำลังคิดอะไรอยู่ ความหลงใหลในเพลงคู่นั้นอาจทำให้คุณพลาดสิ่งสำคัญ

ทำไมจาวาทวีคูณมันด้วย 31?

เป็นเพราะ 31 เป็นเลขคี่ในรูปแบบ 2 ^ 5 - 1 และไพรม์ไพรม์ช่วยลดโอกาสที่ Hash Collision

ตอนนี้รหัสแฮชนี้ถูกแมปกับดัชนีอาร์เรย์อย่างไร

Hash Code % (Array length -1) คำตอบคือ ดังนั้น“girl”จะถูกแมป(3173020 % 3) = 1ในกรณีของเรา ซึ่งเป็นองค์ประกอบที่สองของอาร์เรย์

และค่า“ ahhan” จะถูกเก็บไว้ใน LinkedList ที่เชื่อมโยงกับดัชนีอาร์เรย์ 1

HashCollision - หากคุณพยายามที่จะหาhasHCodeกุญแจ“misused”และ ใช้สูตรที่อธิบายข้างต้นคุณจะเห็นทั้งสองให้เราเหมือนกัน“horsemints” 1069518484Whooaa !! บทเรียนที่ได้เรียนรู้ -

2 วัตถุที่เท่ากันต้องมี hashCode เดียวกัน แต่ไม่มีการรับประกันว่า hashCode ตรงกันแล้ววัตถุนั้นจะเท่ากัน ดังนั้นจึงควรเก็บค่าทั้งสองที่สอดคล้องกับ "ผิด" และ "horsemints" เพื่อฝากข้อมูล 1 (1069518484% 3)

ตอนนี้แผนที่แฮชดูเหมือน -

Array Index 0 
Array Index 1 - LinkedIst (“ahhan , Manmohan Singh , guess what”)
Array Index 2  LinkedList (“way”)
Array Index 3  

ตอนนี้ถ้าร่างกายบางส่วนพยายามที่จะหาค่าสำหรับคีย์“horsemints”, Java อย่างรวดเร็วจะพบ hashCode ของมันโมดูลมันและเริ่มต้นการค้นหาสำหรับค่าของมันใน LinkedList index 1ที่สอดคล้องกัน ด้วยวิธีนี้เราไม่จำเป็นต้องค้นหาดัชนีทั้ง 4 แถวทำให้การเข้าถึงข้อมูลเร็วขึ้น

แต่รอหนึ่งวินาที มี 3 ค่าใน linkedList ที่สอดคล้องกันรายการที่ 1 ดัชนีว่าจะหาว่าอันไหนที่เป็นค่าสำหรับคีย์ "horsemints"?

ที่จริงฉันโกหกเมื่อฉันพูด HashMap เพียงแค่เก็บค่าไว้ใน LinkedList

มันเก็บทั้งคู่ค่าคีย์เป็นรายการแผนที่ ดังนั้นแผนที่จะเป็นแบบนี้

Array Index 0 
Array Index 1 - LinkedIst (<”girl => ahhan”> , <” misused => Manmohan Singh”> , <”horsemints => guess what”>)
Array Index 2  LinkedList (<”no => way”>)
Array Index 3  

ตอนนี้คุณสามารถเห็นได้ว่าในขณะที่ผ่าน TravellingList ที่สอดคล้องกับ ArrayIndex1 จริง ๆ แล้วจะเปรียบเทียบคีย์ของแต่ละรายการกับ LinkedList นั้นกับ“ horsemints” และเมื่อพบว่ามันเพิ่งส่งคืนค่าของมัน

หวังว่าคุณจะสนุกในขณะที่อ่าน :)


ฉันคิดว่ามันผิด: "มันเก็บคีย์ไว้ในอาร์เรย์และค่าใน LinkedList"
ACV

แต่ละองค์ประกอบในรายการสำหรับที่เก็บข้อมูลแต่ละชุดประกอบด้วยคีย์และค่ารวมถึงการอ้างอิงไปยังโหนดถัดไป
ACV

0

ตามที่กล่าวไว้รูปภาพมีค่า 1,000 คำ ฉันพูดว่า: บางรหัสดีกว่า 1,000 คำ นี่คือซอร์สโค้ดของ HashMap รับวิธีการ:

/**
     * Implements Map.get and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @return the node, or null if none
     */
    final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            if ((e = first.next) != null) {
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

ดังนั้นจึงเป็นที่ชัดเจนว่ามีการใช้แฮชเพื่อค้นหา "bucket" และองค์ประกอบแรกจะถูกตรวจสอบเสมอในที่ฝากข้อมูล มิequalsฉะนั้นจะมีการใช้คีย์เพื่อค้นหาองค์ประกอบจริงในรายการที่เชื่อมโยง

ลองดูput()วิธีการ:

  /**
     * Implements Map.put and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

มันซับซ้อนกว่าเล็กน้อย แต่เห็นได้ชัดว่าองค์ประกอบใหม่ถูกวางไว้ในแท็บที่ตำแหน่งที่คำนวณโดยใช้แฮช:

i = (n - 1) & hashนี่iคือดัชนีที่จะวางองค์ประกอบใหม่ (หรือเป็น "bucket") nคือขนาดของtabอาร์เรย์ (อาร์เรย์ของ "buckets")

อันดับแรกจะพยายามใส่เป็นองค์ประกอบแรกใน "bucket" หากมีองค์ประกอบอยู่แล้วให้ผนวกโหนดใหม่เข้ากับรายการ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.