HashTables จัดการกับการชนกันอย่างไร?


102

ฉันเคยได้ยินในชั้นเรียนของฉันว่า a HashTableจะใส่รายการใหม่ในที่เก็บข้อมูล 'มีถัดไป' หากรายการคีย์ใหม่ชนกับรายการอื่น

จะHashTableยังคงส่งคืนค่าที่ถูกต้องได้อย่างไรหากการชนกันนี้เกิดขึ้นเมื่อเรียกหนึ่งกลับด้วยคีย์ชนกัน

ฉันสมมติว่าKeysเป็นStringประเภทและhashCode()ส่งคืนค่าเริ่มต้นที่สร้างโดยพูดว่า Java

หากฉันใช้ฟังก์ชันการแฮชของตัวเองและใช้เป็นส่วนหนึ่งของตารางค้นหา (เช่น a HashMapหรือDictionary) มีกลยุทธ์ใดบ้างในการจัดการกับการชน

ฉันเคยเห็นโน้ตที่เกี่ยวข้องกับจำนวนเฉพาะ! ข้อมูลไม่ชัดเจนจากการค้นหาของ Google

คำตอบ:


96

ตารางแฮชจัดการกับการชนด้วยวิธีใดวิธีหนึ่งจากสองวิธี

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

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

Java ใช้ทั้งอ็อพชัน 1 และ 2 ในการใช้งานตารางแฮช


1
ในกรณีของตัวเลือกแรกมีเหตุผลใดบ้างที่มีการใช้รายการที่เชื่อมโยงแทนอาร์เรย์หรือแม้แต่โครงสร้างการค้นหาแบบไบนารี?

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

3
หากมีการใช้โซ่เมื่อได้รับกุญแจเราจะรู้ได้อย่างไรว่ารายการใดที่จะได้รับคืน?
ChaoSXDemon

1
@ChaoSXDemon คุณสามารถสำรวจรายการในห่วงโซ่ด้วยคีย์คีย์ที่ซ้ำกันไม่ใช่ปัญหาปัญหาคือสองคีย์ที่แตกต่างกันที่มีแฮชโค้ดเหมือนกัน
AMS

1
@ams: ชอบอันไหน? มีข้อ จำกัด สำหรับการชนกันของแฮชหรือไม่หลังจากที่จุดที่ 2 ถูกดำเนินการโดย JAVA?
Shashank Vivek

79

เมื่อคุณพูดถึง "Hash Table จะใส่รายการใหม่ลงในที่เก็บข้อมูล" ถัดไปที่มี "หากรายการคีย์ใหม่ชนกับรายการอื่น" คุณกำลังพูดถึงกลยุทธ์การกำหนดที่อยู่แบบเปิดของการแก้ปัญหาการชนกันของตารางแฮช


มีหลายกลยุทธ์สำหรับตารางแฮชเพื่อแก้ไขความขัดแย้ง

วิธีการใหญ่ประเภทแรกต้องการให้คีย์ (หรือตัวชี้ไปที่พวกเขา) ถูกเก็บไว้ในตารางพร้อมกับค่าที่เกี่ยวข้องซึ่งรวมถึง:

  • แยกโซ่

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

  • เปิดการระบุที่อยู่

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

  • การแฮชแบบรวมกัน
  • แฮชนกกาเหว่า
  • โรบินฮู้ดแฮช
  • แฮช 2 ทางเลือก
  • แฮช Hopscotch

อีกวิธีหนึ่งที่สำคัญในการจัดการการชนกันคือการปรับขนาดไดนามิกซึ่งมีหลายวิธีดังนี้

  • การปรับขนาดโดยการคัดลอกรายการทั้งหมด
  • การปรับขนาดที่เพิ่มขึ้น
  • ปุ่มเสียงเดียว

แก้ไข : ข้างต้นยืมมาจากwiki_hash_tableซึ่งคุณควรไปดูเพื่อรับข้อมูลเพิ่มเติม


3
"[... ] กำหนดให้คีย์ (หรือพอยน์เตอร์) ถูกเก็บไว้ในตารางพร้อมกับค่าที่เกี่ยวข้อง" ขอบคุณนี่คือประเด็นที่ไม่ชัดเจนในทันทีเสมอไปเมื่ออ่านเกี่ยวกับกลไกในการจัดเก็บค่า
mtone

27

มีเทคนิคมากมายในการจัดการการชน ฉันจะอธิบายบางส่วนของพวกเขา

Chaining: ในการผูกมัดเราใช้ดัชนีอาร์เรย์เพื่อเก็บค่า หากรหัสแฮชของค่าที่สองชี้ไปที่ดัชนีเดียวกันเราจะแทนที่ค่าดัชนีนั้นด้วยรายการที่เชื่อมโยงและค่าทั้งหมดที่ชี้ไปยังดัชนีนั้นจะถูกเก็บไว้ในรายการที่เชื่อมโยงและดัชนีอาร์เรย์จริงจะชี้ไปที่ส่วนหัวของรายการที่เชื่อมโยง แต่ถ้ามีรหัสแฮชเพียงรหัสเดียวที่ชี้ไปที่ดัชนีของอาร์เรย์ค่านั้นจะถูกเก็บไว้ในดัชนีนั้นโดยตรง ใช้ตรรกะเดียวกันขณะดึงค่า ใช้ใน Java HashMap / Hashtable เพื่อหลีกเลี่ยงการชนกัน

การตรวจสอบเชิงเส้น:เทคนิคนี้ใช้เมื่อเรามีดัชนีในตารางมากกว่าค่าที่จะจัดเก็บ เทคนิคการตรวจสอบเชิงเส้นทำงานบนแนวคิดของการเพิ่มขึ้นเรื่อย ๆ จนกว่าคุณจะพบช่องว่าง รหัสหลอกมีลักษณะดังนี้:

index = h(k) 

while( val(index) is occupied) 

index = (index+1) mod n

เทคนิคการแฮชสองครั้ง:ในเทคนิคนี้เราใช้ฟังก์ชันแฮช 2 ฟังก์ชัน h1 (k) และ h2 (k) หากช่องที่ h1 (k) ถูกใช้งานฟังก์ชันแฮชที่สอง h2 (k) จะใช้เพื่อเพิ่มดัชนี รหัสหลอกมีลักษณะดังนี้:

index = h1(k)

while( val(index) is occupied)

index = (index + h2(k)) mod n

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

สำหรับข้อมูลเพิ่มเติมโปรดดูที่ไซต์นี้


18

ฉันขอแนะนำให้คุณอ่านโพสต์บล็อกนี้ซึ่งปรากฏใน HackerNews เมื่อเร็ว ๆ นี้: HashMap ทำงานอย่างไรใน Java

ในระยะสั้นคำตอบคือ

จะเกิดอะไรขึ้นถ้าออบเจ็กต์คีย์ HashMap สองอันที่แตกต่างกันมีแฮชโค้ดเดียวกัน

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


3
HashMaps น่าสนใจมากและเจาะลึก! :)
อเล็กซ์

1
ฉันคิดว่าคำถามเกี่ยวกับ HashTables ไม่ใช่ HashMap
Prashant Shubham

10

ฉันเคยได้ยินในชั้นเรียนระดับปริญญาของฉันว่า HashTable จะใส่รายการใหม่ลงในที่เก็บข้อมูล "มีอยู่ถัดไป" หากรายการคีย์ใหม่ชนกับรายการอื่น

สิ่งนี้ไม่เป็นความจริงอย่างน้อยสำหรับ Oracle JDK ( เป็นรายละเอียดการใช้งานที่อาจแตกต่างกันไปตามการใช้งาน API ที่แตกต่างกัน) แต่ละที่เก็บข้อมูลจะมีรายการที่เชื่อมโยงกันก่อน Java 8 และแผนผังสมดุลใน Java 8 ขึ้นไป

แล้ว HashTable จะยังคงส่งคืนค่าที่ถูกต้องได้อย่างไรหากการชนกันนี้เกิดขึ้นเมื่อเรียกหนึ่งกลับด้วยคีย์ชนกัน

ใช้equals()เพื่อค้นหารายการที่ตรงกันจริง

หากฉันใช้ฟังก์ชันการแฮชของตัวเองและใช้เป็นส่วนหนึ่งของตารางค้นหา (เช่น HashMap หรือ Dictionary) จะมีกลยุทธ์ใดบ้างในการรับมือกับการชน

มีกลยุทธ์การจัดการการชนที่หลากหลายซึ่งมีข้อดีและข้อเสียที่แตกต่างกัน รายการของ Wikipedia ในตารางแฮชให้ภาพรวมที่ดี


เป็นจริงสำหรับทั้งคู่HashtableและHashMapใน jdk 1.6.0_22 โดย Sun / Oracle
Nikita Rybak

@Nikita: ไม่แน่ใจเกี่ยวกับ Hashtable และตอนนี้ฉันไม่สามารถเข้าถึงแหล่งที่มาได้ แต่ฉันมั่นใจ 100% ว่า HashMap ใช้การเชื่อมโยงและไม่ใช่การตรวจสอบเชิงเส้นในทุกเวอร์ชันที่ฉันเคยเห็นในดีบักเกอร์
Michael Borgwardt

@ Michael อืมฉันกำลังดูที่มาของ HashMap public V get(Object key)อยู่ตอนนี้ (เวอร์ชันเดียวกับด้านบน) หากคุณพบเวอร์ชันที่แน่นอนที่รายการที่เชื่อมโยงเหล่านั้นปรากฏขึ้นฉันก็สนใจที่จะทราบ
Nikita Rybak

@ นิกิ: ตอนนี้ฉันกำลังดูวิธีการเดียวกันและฉันเห็นว่ามันใช้ for loop เพื่อวนซ้ำผ่านรายการที่เชื่อมโยงของ Entryวัตถุที่ :localEntry = localEntry.next
Michael Borgwardt

@ Michael ขอโทษมันเป็นความผิดพลาดของฉัน ฉันตีความรหัสผิดวิธี ธรรมชาติไม่ได้e = e.next ++index+1
Nikita Rybak

7

อัปเดตตั้งแต่ Java 8: Java 8 ใช้แผนผังที่สมดุลในตัวเองสำหรับการจัดการการชนกันโดยปรับปรุงกรณีที่เลวร้ายที่สุดจาก O (n) เป็น O (log n) สำหรับการค้นหา การใช้ต้นไม้ที่ปรับสมดุลในตัวเองได้รับการแนะนำใน Java 8 เป็นการปรับปรุงมากกว่าการเชื่อมโยง (ใช้จนถึง java 7) ซึ่งใช้รายการที่เชื่อมโยงและมี O (n) ในกรณีที่เลวร้ายที่สุดสำหรับการค้นหา (เนื่องจากจำเป็นต้องสำรวจ รายการ)

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

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

Collission-management นำประสิทธิภาพของการแทรกและการค้นหาจาก O (1) ที่เลวร้ายที่สุดในกรณีที่ไม่มีการจัดการ collission กับ O (n) สำหรับการเชื่อมโยง (รายการที่เชื่อมโยงใช้เป็นโครงสร้างข้อมูลรอง) และ O (log n) สำหรับต้นไม้ที่สมดุลในตัวเอง

อ้างอิง:

Java 8 มาพร้อมกับการปรับปรุง / เปลี่ยนแปลงของวัตถุ HashMap ต่อไปนี้ในกรณีที่มีการชนกันสูง

  • ฟังก์ชันแฮชสตริงทางเลือกที่เพิ่มใน Java 7 ถูกลบออก

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

การเปลี่ยนแปลงข้างต้นช่วยให้มั่นใจได้ถึงประสิทธิภาพของ O (log (n)) ในสถานการณ์ที่เลวร้ายที่สุด ( https://www.nagarro.com/en/blog/post/24/performance-improvement-for-hashmap-in-java-8 )


คุณช่วยอธิบายได้ไหมว่าการแทรกกรณีที่เลวร้ายที่สุดสำหรับ HashMap แบบรายการที่เชื่อมโยงเป็นเพียง O (1) ไม่ใช่ O (N) ได้อย่างไร สำหรับฉันแล้วดูเหมือนว่าหากคุณมีอัตราการชนกัน 100% สำหรับคีย์ที่ไม่ซ้ำกันคุณจะต้องสำรวจทุกวัตถุใน HashMap เพื่อค้นหาจุดสิ้นสุดของรายการที่เชื่อมโยงใช่ไหม? ฉันขาดอะไรไป?
mbm29414

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

4

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


4

เนื่องจากมีความสับสนเกี่ยวกับอัลกอริทึมที่ใช้ HashMap ของ Java (ในการใช้งาน Sun / Oracle / OpenJDK) ต่อไปนี้เป็นข้อมูลโค้ดซอร์สโค้ดที่เกี่ยวข้อง (จาก OpenJDK, 1.6.0_20 บน Ubuntu):

/**
 * Returns the entry associated with the specified key in the
 * HashMap.  Returns null if the HashMap contains no mapping
 * for the key.
 */
final Entry<K,V> getEntry(Object key) {
    int hash = (key == null) ? 0 : hash(key.hashCode());
    for (Entry<K,V> e = table[indexFor(hash, table.length)];
         e != null;
         e = e.next) {
        Object k;
        if (e.hash == hash &&
            ((k = e.key) == key || (key != null && key.equals(k))))
            return e;
    }
    return null;
}

วิธีนี้ (อ้างอิงจากเส้น 355-371) เรียกว่าเมื่อมองขึ้นรายการในตารางเช่นจากget(),containsKey()และคนอื่น ๆ สำหรับลูปที่นี่จะผ่านรายการที่เชื่อมโยงซึ่งสร้างโดยวัตถุรายการ

นี่คือรหัสสำหรับวัตถุรายการ (บรรทัด 691-705 + 759):

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

    /**
     * Creates new entry.
     */
    Entry(int h, K k, V v, Entry<K,V> n) {
        value = v;
        next = n;
        key = k;
        hash = h;
    }

  // (methods left away, they are straight-forward implementations of Map.Entry)

}

หลังจากนี้addEntry()วิธีการ:

/**
 * Adds a new entry with the specified key, value and hash code to
 * the specified bucket.  It is the responsibility of this
 * method to resize the table if appropriate.
 *
 * Subclass overrides this to alter the behavior of put method.
 */
void addEntry(int hash, K key, V value, int bucketIndex) {
    Entry<K,V> e = table[bucketIndex];
    table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
    if (size++ >= threshold)
        resize(2 * table.length);
}

สิ่งนี้จะเพิ่มรายการใหม่ที่ด้านหน้าของที่เก็บข้อมูลโดยมีลิงก์ไปยังรายการแรกแบบเก่า(หรือค่าว่างหากไม่มี) ในทำนองเดียวกันremoveEntryForKey()วิธีนี้จะผ่านรายการและดูแลการลบเพียงรายการเดียวปล่อยให้รายการที่เหลือเหมือนเดิม

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

(รหัสนี้คือ (c) Sun Microsystems ปี 1997-2007 และพร้อมใช้งานภายใต้ GPL แต่สำหรับการคัดลอกให้ดีขึ้นให้ใช้ไฟล์ต้นฉบับที่อยู่ใน src.zip ในแต่ละ JDK จาก Sun / Oracle และใน OpenJDK ด้วย)


1
ฉันทำเครื่องหมายว่านี่เป็นวิกิชุมชนเนื่องจากมันไม่ใช่คำตอบจริงๆขอให้พูดคุยเกี่ยวกับคำตอบอื่น ๆ ในความคิดเห็นมีพื้นที่ไม่เพียงพอสำหรับการอ้างอิงโค้ดดังกล่าว
Paŭlo Ebermann

3

นี่คือการใช้งานตารางแฮชที่ง่ายมากใน java ในการดำเนินการเท่านั้นput()และget()แต่คุณสามารถเพิ่มสิ่งที่คุณต้องการ มันอาศัยhashCode()วิธีการของ java ที่ใช้กับวัตถุทั้งหมด คุณสามารถสร้างอินเทอร์เฟซของคุณเองได้อย่างง่ายดาย

interface Hashable {
  int getHash();
}

และบังคับให้ใช้งานด้วยคีย์หากคุณต้องการ

public class Hashtable<K, V> {
    private static class Entry<K,V> {
        private final K key;
        private final V val;

        Entry(K key, V val) {
            this.key = key;
            this.val = val;
        }
    }

    private static int BUCKET_COUNT = 13;

    @SuppressWarnings("unchecked")
    private List<Entry>[] buckets = new List[BUCKET_COUNT];

    public Hashtable() {
        for (int i = 0, l = buckets.length; i < l; i++) {
            buckets[i] = new ArrayList<Entry<K,V>>();
        }
    }

    public V get(K key) {
        int b = key.hashCode() % BUCKET_COUNT;
        List<Entry> entries = buckets[b];
        for (Entry e: entries) {
            if (e.key.equals(key)) {
                return e.val;
            }
        }
        return null;
    }

    public void put(K key, V val) {
        int b = key.hashCode() % BUCKET_COUNT;
        List<Entry> entries = buckets[b];
        entries.add(new Entry<K,V>(key, val));
    }
}

2

มีวิธีการต่างๆสำหรับการแก้ปัญหาการชนกันบางวิธีคือ Separate Chaining, Open addressing, Robin Hood hashing, Cuckoo Hashing เป็นต้น

Java ใช้ Separate Chaining ในการแก้ไขปัญหาการชนกันในตาราง Hash นี่คือลิงค์ที่ดีเยี่ยมว่ามันเกิดขึ้นได้อย่างไร: http://javapapers.com/core-java/java-hashtable/

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