โครงสร้างการจัดเก็บ
อันดับแรก HashMap จะถูกเก็บไว้ตามตารางแฮช มีอาร์เรย์อยู่ข้างใน เมื่อต้องจัดเก็บองค์ประกอบให้คำนวณค่าแฮชของคีย์ก่อนและค้นหาตัวห้อยที่สอดคล้องกันขององค์ประกอบในอาร์เรย์ตามค่าแฮช หากไม่มีองค์ประกอบที่ตำแหน่งนี้ให้ใส่องค์ประกอบปัจจุบันโดยตรง หากมีองค์ประกอบ (จำได้ว่าเป็นที่นี่) ให้เชื่อมโยงองค์ประกอบปัจจุบันไปที่ด้านหน้าขององค์ประกอบ A แล้วใส่องค์ประกอบปัจจุบันลงในอาร์เรย์ ดังนั้นใน HashMap อาร์เรย์จะบันทึกโหนดแรกของรายการที่เชื่อมโยง ด้านล่างเป็นรูปภาพจากสารานุกรม Baidu:
ดังที่แสดงในรูปด้านบนแต่ละองค์ประกอบเป็นวัตถุรายการซึ่งคีย์และค่าขององค์ประกอบจะถูกบันทึกและมีตัวชี้ที่สามารถใช้ชี้ไปที่วัตถุถัดไป คีย์ทั้งหมดที่มีค่าแฮชเดียวกัน (นั่นคือความขัดแย้ง) สตริงเข้าด้วยกันโดยใช้รายการที่เชื่อมโยงซึ่งเป็นวิธีซิป
ตัวแปรภายใน
// ค่าเริ่มต้นความจุเริ่มต้นคงที่ int final default_initial_capacity = 16; // ความจุสูงสุดคงที่ int สุดท้าย int maximum_capacity = 1 << 30; // ปัจจัยการโหลดเริ่มต้นสุดท้าย float float float_load_factor = 0.75f; // hast ตารางชั่วคราว Hash Array Final Float Loadfactor;
ในตัวแปรข้างต้นความจุหมายถึงความยาวของตารางแฮชนั่นคือขนาดของตารางและค่าเริ่มต้นคือ 16 ตัวโหลดปัจจัยโหลดเป็น "ระดับเต็ม" ของตารางแฮชและเอกสารของ JDK กล่าวว่า:
ปัจจัยการโหลดเป็นการวัดว่าตารางแฮชได้รับอนุญาตให้รับได้อย่างไรก่อนที่ความจุจะเพิ่มขึ้นโดยอัตโนมัติ เมื่อจำนวนรายการในตารางแฮชเกินกว่าผลผลิตของปัจจัยโหลดและความจุปัจจุบันตารางแฮชจะได้รับการปรับปรุงใหม่ (นั่นคือโครงสร้างข้อมูลภายในถูกสร้างขึ้นใหม่) เพื่อให้ตารางแฮชมีจำนวนถังประมาณสองเท่า
ความหมายทั่วไปคือ: ปัจจัยการโหลดคือการวัดว่าสามารถติดตั้งตารางแฮชได้อย่างไรก่อนการขยายตัว เมื่อจำนวน "คู่คีย์-ค่า" ในตารางแฮชเกินกว่าผลิตภัณฑ์ของความจุปัจจุบันและปัจจัยการโหลดตารางแฮชแฮช (นั่นคือโครงสร้างข้อมูลภายในถูกสร้างขึ้นมาใหม่) และความสามารถของตารางแฮชกลายเป็นสองเท่าของต้นฉบับ
ดังที่เห็นได้จากนิยามตัวแปรข้างต้นปัจจัยการโหลดเริ่มต้นเริ่มต้น _load_factor คือ 0.75 ยิ่งค่านี้มีขนาดใหญ่เท่าใดอัตราการใช้พื้นที่ที่สูงขึ้น แต่ความเร็วในการสืบค้น (รวมถึง Get and Put) จะชะลอตัวลง หลังจากทำความเข้าใจกับปัจจัยการโหลดเกณฑ์ก็สามารถเข้าใจได้ จริง ๆ แล้วมันเท่ากับปัจจัยความจุ* ปัจจัยการโหลด
ตัวสร้าง
public hashmap (int initialcapacity, float loadfactor) {ถ้า (initialcapacity <0) โยน unlegalargumentException ใหม่ ("ความจุเริ่มต้นที่ผิดกฎหมาย:" + initialcapacity); if (initialCapacity> maximum_capacity) การเริ่มต้นการเริ่มต้น = maximum_capacity; if (loadfactor <= 0 || float.isnan (loadfactor)) โยน unlegalargumentException ใหม่ ("ปัจจัยโหลดที่ผิดกฎหมาย:" + loadfactor); // ค้นหาพลังของ 2> = ความจุ intixcipacity int = 1; ในขณะที่ (ความจุ <การเริ่มต้นความจุ) // คำนวณพลังงานที่เล็กที่สุดของ 2 ที่สูงกว่าความจุที่ระบุ << = 1; this.loadfactor = loadfactor; threshold = (int) math.min (ความจุ * loadfactor, maximum_capacity + 1); ตาราง = รายการใหม่ [ความจุ]; // จัดสรรพื้นที่ไปยังตารางแฮช usealthashing = sun.misc.vm.isbooted () && (ความสามารถ> = holder.alternative_hashing_threshold); init ();}มีตัวสร้างหลายตัวและในที่สุดพวกเขาก็จะเรียกข้างต้น มันยอมรับพารามิเตอร์สองพารามิเตอร์หนึ่งคือความจุเริ่มต้นและอื่น ๆ คือปัจจัยการโหลด ในตอนแรกเราจะพิจารณาก่อนว่าการรวมค่านั้นถูกกฎหมายหรือไม่และหากมีปัญหาใด ๆ ข้อยกเว้นจะถูกโยนลงไป สิ่งที่สำคัญคือการคำนวณความจุด้านล่างตรรกะของมันคือการคำนวณกำลังที่เล็กที่สุดของ 2 มากกว่าการเริ่มต้น ในความเป็นจริงวัตถุประสงค์คือการสร้างความจุมากกว่าหรือเท่ากับความจุเริ่มต้นที่ระบุ แต่ค่านี้จะต้องเป็นทวีคูณแบบเอ็กซ์โปเนนเชียลของ 2 ซึ่งเป็นประเด็นสำคัญ เหตุผลนี้ส่วนใหญ่เป็นแผนที่ค่าแฮช ก่อนอื่นให้ดูที่วิธีแฮชใน HashMap:
แฮช int สุดท้าย (วัตถุ k) {int h = 0; if (usealthashing) {if (k อินสแตนซ์ k string) {return sun.misc.hashing.stringhash32 ((String) k); } h = hashseed; } h ^= k.hashCode (); // ฟังก์ชั่นนี้ทำให้มั่นใจได้ว่า hashcodes ที่แตกต่างกันโดย // ทวีคูณคงที่ในแต่ละตำแหน่งบิตมีขอบเขต // จำนวนการชน (ประมาณ 8 ที่ปัจจัยโหลดเริ่มต้น) h ^ = (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4);} int indexfor for (int h, ความยาว int) {return h & (length-1);}วิธีการแฮช () คำนวณค่าแฮชของคีย์ใหม่และใช้การดำเนินการบิตที่ค่อนข้างซับซ้อน ฉันไม่รู้ตรรกะเฉพาะ อย่างไรก็ตามมันเป็นวิธีที่ดีกว่าซึ่งสามารถลดความขัดแย้งหรืออะไรบางอย่างได้
indexfor () ด้านล่างเป็นตัวห้อยขององค์ประกอบในตารางแฮชตามค่าแฮช โดยทั่วไปในตารางแฮชคุณใช้ค่าแฮชเพื่อปรับความยาวของตาราง เมื่อความยาว (นั่นคือความจุ) เป็นพลังของ 2, H & (ความยาว -1) เป็นเอฟเฟกต์เดียวกัน ยิ่งไปกว่านั้นพลังของ 2 จะต้องเป็นตัวเลขที่สม่ำเสมอจากนั้นหลังจากลบ 1 มันเป็นตัวเลขคี่และบิตสุดท้ายของไบนารีจะต้องเป็น 1 จากนั้นบิตสุดท้ายของ H & (ความยาว -1) อาจเป็น 1 หรือ 0 ซึ่งสามารถแฮชได้อย่างสม่ำเสมอ หากความยาวเป็นตัวเลขคี่ดังนั้นความยาว -1 จะเป็นตัวเลขที่สม่ำเสมอและบิตสุดท้ายคือ 0 ในเวลานี้บิตสุดท้ายของ H & (ความยาว -1) อาจเป็น 0 เท่านั้นและตัวห้อยที่ได้ทั้งหมดนั้นมีอยู่ครึ่งหนึ่งของพื้นที่ที่สูญเปล่า ดังนั้นความจุใน HashMap จะต้องเป็นพลังของ 2 คุณจะเห็นได้ว่าค่าเริ่มต้นเริ่มต้น _initial_capacity = 16 และ maximum_capacity = 1 << 30 เป็นแบบนี้
วัตถุทางเข้า
คู่คีย์-ค่าใน HashMap ถูกห่อหุ้มไว้ในวัตถุรายการซึ่งเป็นคลาสภายในใน HashMap ลองมาดูการใช้งาน:
รายการคลาสคงที่ <k, v> ใช้ map.entry <k, v> {คีย์สุดท้าย k; ค่า V; รายการ <k, v> ถัดไป; แฮช int; รายการ (int h, k k, v v, รายการ <k, v> n) {value = v; ถัดไป = n; key = k; แฮช = h; } สาธารณะสุดท้าย k getKey () {Return Key; } สาธารณะสุดท้าย v getValue () {ค่าคืน; } สาธารณะสุดท้าย v setValue (v newValue) {v oldValue = ค่า; ค่า = newValue; กลับ OldValue; } บูลีนสุดท้ายสาธารณะเท่ากับ (วัตถุ o) {if (! (o อินสแตนซ์ของ map.entry)) ส่งคืน false; map.entry e = (map.entry) o; วัตถุ K1 = getKey (); Object K2 = E.getKey (); if (k1 == k2 || (k1! = null && k1.equals (k2))) {object v1 = getValue (); Object V2 = E.getValue (); if (v1 == v2 || (v1! = null && v1.equals (v2))) ส่งคืนจริง; } return false; } สาธารณะ int สุดท้าย hashCode () {return (key == null? 0: key.hashCode ()) ^ (value == null? 0: value.hashCode ()); } public สตริงสุดท้าย toString () {return getKey () + "=" + getValue (); } void recordAccess (hashmap <k, v> m) {}}การใช้งานคลาสนี้ง่ายและเข้าใจง่าย วิธีการเช่น getKey (), getValue () มีไว้สำหรับการโทร เพื่อกำหนดความเท่าเทียมกันจำเป็นต้องมีทั้งคีย์และค่าเท่ากัน
ใส่การดำเนินการ
ใส่ก่อนก่อนที่คุณจะได้รับดังนั้นดูที่วิธีการ put () ก่อน:
สาธารณะ V Put (k key, v value) {ถ้า (key == null) return putfornullkey (value); int hash = hash (คีย์); int i = indexfor (แฮช, table.length); สำหรับ (รายการ <k, v> e = ตาราง [i]; e! = null; e = e.next) {วัตถุ k; if (e.hash == hash && ((k = e.key) == key || key.equals (k))) {v oldValue = e.value; e.value = ค่า; E.RecordAccess (นี่); กลับ OldValue; }} ModCount ++; Addentry (แฮช, คีย์, ค่า, i); คืนค่า null;}ในวิธีนี้ก่อนอื่นให้พิจารณาว่าคีย์เป็นโมฆะหรือไม่ ถ้าใช่วิธีการโทร putfornullkey () ซึ่งหมายความว่า HashMap อนุญาตให้คีย์เป็นโมฆะ (อันที่จริงแล้วค่าสามารถเป็นได้) หากไม่เป็นโมฆะให้คำนวณค่าแฮชและรับตัวห้อยในตาราง จากนั้นไปที่รายการที่เชื่อมโยงที่สอดคล้องกันเพื่อสอบถามว่าคีย์เดียวกันนั้นมีอยู่แล้วหรือไม่ หากมีอยู่แล้วค่าจะได้รับการอัปเดตโดยตรง มิฉะนั้นการโทร Addentry () วิธีการแทรก
ลองดูที่วิธี putfornullkey ():
ส่วนตัว v putfornullkey (ค่า V) {สำหรับ (รายการ <k, v> e = ตาราง [0]; e! = null; e = e.next) {ถ้า (e.key == null) {v oldValue = e.value; e.value = ค่า; E.RecordAccess (นี่); กลับ OldValue; }} ModCount ++; Addentry (0, null, ค่า, 0); คืนค่า null;}จะเห็นได้ว่าเมื่อคีย์เป็นโมฆะค่าจะได้รับการอัปเดตหากมีอยู่มิฉะนั้น addentry () จะถูกเรียกให้แทรก
ต่อไปนี้คือการใช้วิธี Addentry ():
โมฆะ Addentry (int hash, k key, v ค่า V, int bucketindex) {ถ้า ((ขนาด> = threshold) && (null! = ตาราง [bucketindex])) {resize (2 * table.length); hash = (null! = key)? แฮช (กุญแจ): 0; BucketIndex = indexfor (แฮช, table.length); } createEntry (แฮช, คีย์, ค่า, bucketIndex);} เป็นโมฆะ createEntry (int hash, k key, v value, int bucketindex) {entry <k, v> e = ตาราง [bucketindex]; ตาราง [BucketIndex] = รายการใหม่ <> (แฮช, คีย์, ค่า, e); ขนาด ++;}ขั้นแรกให้พิจารณาว่าจะขยายความจุ (การขยายกำลังการผลิตจะคำนวณค่าตัวห้อยใหม่และคัดลอกองค์ประกอบ) จากนั้นคำนวณตัวห้อยอาร์เรย์และในที่สุดก็แทรกองค์ประกอบโดยใช้วิธีการแทรกส่วนหัวใน createEntry ()
รับการดำเนินการ
สาธารณะ v รับ (คีย์วัตถุ) {ถ้า (key == null) return getForNullKey (); รายการ <k, v> entry = getentry (คีย์); ส่งคืน null == รายการ? null: entry.getValue ();} ส่วนตัว v getfornullkey () {สำหรับ (รายการ <k, v> e = ตาราง [0]; e! = null; e = e.next) {ถ้า (e.key == null) ส่งคืน e.value; } return null;} รายการสุดท้าย <k, v> getentry (คีย์วัตถุ) {int hash = (key == null)? 0: แฮช (กุญแจ); สำหรับ (รายการ <k, v> e = ตาราง [indexfor (แฮช, table.length)]; e! = null; e = e.next) {object k; if (e.hash == hash && ((k = e.key) == key || (key! = null && key.equals (k)))) กลับ e; } return null;}มันง่ายกว่าใส่ () คุณต้องตรวจสอบว่าคีย์นั้นเป็นโมฆะหรือไม่จากนั้นสืบค้นแบบสำรวจของรายการที่เชื่อมโยง
การเพิ่มประสิทธิภาพประสิทธิภาพ
HashMap เป็นโครงสร้างข้อมูลที่มีประสิทธิภาพและเป็นสากลที่สามารถมองเห็นได้ทุกที่ในทุกโปรแกรม Java มาแนะนำความรู้พื้นฐานก่อน อย่างที่คุณอาจทราบได้ว่า HashMap ใช้วิธีการ HashCode () และ Equals () ของคีย์เพื่อแบ่งค่าออกเป็นถังต่าง ๆ จำนวนถังมักจะใหญ่กว่าจำนวนระเบียนเล็กน้อยในแผนที่เล็กน้อยเพื่อให้แต่ละถังจะมีค่าน้อยลง (โดยเฉพาะอย่างยิ่ง) เมื่อค้นหาคีย์เราสามารถค้นหาถังได้อย่างรวดเร็ว (ใช้ hashCode () เพื่อโมดูโลจำนวนถัง) และวัตถุที่เรากำลังมองหาในเวลาคงที่
คุณควรรู้สิ่งเหล่านี้ทั้งหมดแล้ว คุณอาจรู้ว่าการชนกันของแฮชอาจส่งผลกระทบอย่างรุนแรงต่อประสิทธิภาพของ HASHMAP หากค่า hashCode () หลายค่าอยู่ในถังเดียวกันค่าเหล่านี้จะถูกเก็บไว้ในรายการที่เชื่อมโยง ในกรณีที่เลวร้ายที่สุดคีย์ทั้งหมดจะถูกแมปเข้ากับถังเดียวกันดังนั้น HashMap จะลดลงในรายการที่เชื่อมโยง - เวลาการค้นหามาจาก O (1) ถึง O (N) ก่อนอื่นลองทดสอบประสิทธิภาพของ HashMap ใน Java 7 และ Java 8 ภายใต้สถานการณ์ปกติ เพื่อควบคุมพฤติกรรมของวิธีการ HashCode () เรากำหนดคลาสคีย์ดังนี้:
คีย์คลาสใช้การเปรียบเทียบ <key> {ค่า int สุดท้ายส่วนตัว; คีย์ (ค่า int) {this.value = value;}@overridepublic int compereto (key o) {return integer.compare (this.value, o.value); o.getClass ()) ส่งคืน false; key key = (key) o; ค่าส่งคืน == key.value;}@overridepublic int hashCode () {return value;}}การใช้งานคลาสคีย์นั้นค่อนข้างเป็นมาตรฐาน: มันเขียนวิธี Equals () ใหม่และให้วิธี HashCode () ที่เหมาะสมพอสมควร เพื่อหลีกเลี่ยง GC ที่มากเกินไปฉันแคชวัตถุคีย์ที่ไม่เปลี่ยนรูปแทนที่จะเริ่มสร้างอีกครั้งทุกครั้ง:
คีย์คลาสใช้การเปรียบเทียบ <key> {คีย์คลาสสาธารณะ {สาธารณะคงที่สุดท้าย int max_key = 10_000_000; คีย์สุดท้ายแบบคงที่ส่วนตัว [] keys_cache = คีย์ใหม่ [max_key]; คงที่ {สำหรับ (int i = 0; i <max_key; ++ i) {keys_cache keys_cache [value];}ตอนนี้เราสามารถเริ่มทดสอบได้ เกณฑ์มาตรฐานของเราใช้ค่าคีย์ต่อเนื่องเพื่อสร้างแฮชแมปที่มีขนาดต่างกัน (ตัวคูณสำหรับ 10 จาก 1 ถึง 1 ล้าน) ในการทดสอบเราจะใช้คีย์เพื่อค้นหาและวัดเวลาที่ใช้ในการแฮชแมปที่มีขนาดต่างกัน:
นำเข้า com.google.caliper.param; นำเข้า com.google.caliper.runner; นำเข้า com.google.caliper.simpleBenchmark; Mapbenchmark ระดับสาธารณะขยาย SimpleBenchmark {HashMap ส่วนตัว hashmap <> (mapsize); สำหรับ (int i = 0; i <mapsize; ++ i) {map.put (keys.of (i), i);}} โมฆะสาธารณะ timemapget (int reps) {สำหรับ (int i = 0;ที่น่าสนใจใน Hashmap.get () Java 8 นั้นเร็วกว่า Java 7 20% การแสดงโดยรวมก็ค่อนข้างดี: แม้ว่าจะมีบันทึกหนึ่งล้านรายการใน HashMap แต่การสืบค้นเดียวใช้เวลาน้อยกว่า 10 นาโนวินาทีซึ่งประมาณ 20 รอบซีพียูของฉัน น่าตกใจมาก! แต่นี่ไม่ใช่สิ่งที่เราต้องการวัด
สมมติว่ามีคีย์ที่ไม่ดีมันจะส่งคืนค่าเดียวกันเสมอ นี่เป็นสถานการณ์ที่เลวร้ายที่สุดและคุณไม่ควรใช้ HashMap เลย:
คีย์คลาสใช้การเปรียบเทียบ <key> {//...@overridepublic int hashCode () {return 0;}}คาดว่าจะได้ผลลัพธ์ของ Java 7 เมื่อขนาดของ HashMap เพิ่มขึ้นค่าใช้จ่ายของวิธี GET () จะใหญ่ขึ้นเรื่อย ๆ เนื่องจากระเบียนทั้งหมดอยู่ในรายการที่เชื่อมโยงยาวเป็นพิเศษในถังเดียวกันการค้นหาค่าเฉลี่ยของบันทึกหนึ่งจึงต้องใช้การสำรวจครึ่งหนึ่งของรายการ ดังนั้นจึงสามารถมองเห็นได้จากรูปที่ความซับซ้อนของเวลาคือ o (n)
อย่างไรก็ตาม Java 8 ทำงานได้ดีขึ้นมาก! มันเป็นเส้นโค้งบันทึกดังนั้นประสิทธิภาพของมันจึงเป็นคำสั่งของขนาดที่ดีกว่า แม้จะมีสถานการณ์กรณีที่เลวร้ายที่สุดของการชนกันอย่างรุนแรง แต่เกณฑ์มาตรฐานเดียวกันนี้มีความซับซ้อนของเวลาใน JDK8 ของ O (LOGN) หากคุณดูเส้นโค้งของ JDK 8 เพียงอย่างเดียวมันจะชัดเจนขึ้น นี่คือการกระจายเชิงเส้นลอการิทึม:
เหตุใดจึงมีการปรับปรุงประสิทธิภาพที่ยอดเยี่ยมแม้ว่าจะใช้สัญลักษณ์ O ขนาดใหญ่ที่นี่ (Big O อธิบายขอบเขตบน asymptotic)? ในความเป็นจริงการเพิ่มประสิทธิภาพนี้ได้รับการกล่าวถึงใน JEP-180 หากบันทึกในถังมีขนาดใหญ่เกินไป (ปัจจุบัน treeify_threshold = 8) HashMap จะแทนที่ด้วยการใช้งาน TreeMap พิเศษแบบไดนามิก ซึ่งจะส่งผลให้ผลลัพธ์ที่ดีขึ้น o (logn), ไม่เลว o (n) มันทำงานอย่างไร? ระเบียนที่สอดคล้องกับคีย์ที่มีความขัดแย้งอยู่ข้างหน้านั้นจะถูกผนวกเข้ากับรายการที่เชื่อมโยงและบันทึกเหล่านี้สามารถพบได้ผ่านการสำรวจเท่านั้น อย่างไรก็ตามหลังจากเกินเกณฑ์นี้ HashMap เริ่มอัพเกรดรายการเป็นต้นไม้ไบนารีโดยใช้ค่าแฮชเป็นตัวแปรสาขาของต้นไม้ หากค่าแฮชทั้งสองไม่เท่ากัน แต่ชี้ไปที่ถังเดียวกันค่าที่ใหญ่กว่าจะถูกแทรกลงในทรีย่อยด้านขวา หากค่าแฮชเท่ากัน HashMap หวังว่าค่าคีย์จะถูกนำไปใช้ที่ดีที่สุดโดยอินเทอร์เฟซที่เปรียบเทียบได้เพื่อให้สามารถแทรกตามลำดับ นี่ไม่จำเป็นสำหรับคีย์ของ HashMap แต่แน่นอนว่าดีที่สุดถ้านำไปใช้ หากอินเทอร์เฟซนี้ไม่ได้ใช้งานคุณไม่ควรคาดหวังว่าจะได้รับการปรับปรุงประสิทธิภาพในกรณีที่มีการชนกันอย่างรุนแรง
การใช้การปรับปรุงประสิทธิภาพนี้คืออะไร? ตัวอย่างเช่นโปรแกรมที่เป็นอันตรายหากรู้ว่าเรากำลังใช้อัลกอริทึมการแฮชมันอาจส่งคำขอจำนวนมากส่งผลให้เกิดการชนกันอย่างจริงจัง จากนั้นการเข้าถึงคีย์เหล่านี้อย่างต่อเนื่องอาจส่งผลกระทบต่อประสิทธิภาพของเซิร์ฟเวอร์อย่างมีนัยสำคัญซึ่งนำไปสู่การปฏิเสธการโจมตีบริการ (DOS) การกระโดดจาก O (n) ถึง O (logn) ใน JDK 8 สามารถป้องกันการโจมตีที่คล้ายกันได้อย่างมีประสิทธิภาพในขณะที่ยังช่วยเพิ่มประสิทธิภาพการคาดการณ์ของประสิทธิภาพ HASHMAP เล็กน้อย ฉันหวังว่าการปรับปรุงนี้จะโน้มน้าวให้เจ้านายของคุณตกลงที่จะอัพเกรดเป็น JDK 8
สภาพแวดล้อมที่ใช้สำหรับการทดสอบคือ: Intel Core i7-3635QM @ 2.4 GHz, หน่วยความจำ 8GB, ฮาร์ดดิสก์ SSD โดยใช้พารามิเตอร์ JVM เริ่มต้นทำงานบนระบบ Windows 8.1 64 บิต
สรุป
การใช้งานพื้นฐานของ HASHMAP นั้นได้รับการวิเคราะห์ข้างต้นและในที่สุดก็มีการสรุป: