0Pricing
Java Academy · บทเรียน

การทำงานของ HashMap

บักเก็ต การแฮช และการชนกัน

การทำงานของ HashMap เป็นบทเรียน Java Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Java Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Java Academy มีบทเรียนทั้งหมด 4 บทเรียน

สิ่งที่ HashMap จัดเก็บ

HashMap จัดเก็บคู่คีย์-ค่า และให้การค้นหา การแทรก และการลบโดยเฉลี่ยที่มีประสิทธิภาพระดับ O(1)

ภายในจะเก็บอาร์เรย์ที่เรียกว่า ตาราง แต่ละตำแหน่งในอาร์เรย์นี้เรียกว่า ช่อง

  • คีย์จะกำหนดว่ารายการจะไปอยู่ในช่องใด
  • ค่าคือสิ่งที่คุณได้รับกลับมาเมื่อค้นหาด้วยคีย์
import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> ages = new HashMap<>();
        ages.put("Alice", 30);
        ages.put("Bob", 25);
        System.out.println(ages.get("Alice"));
    }
}

การทำแฮชของคีย์

เมื่อคุณเรียก put(key, value) แมปจะเรียก key.hashCode() เพื่อรับค่า int

จากนั้น HashMap จะ กระจาย บิตเหล่านั้นด้วยฟังก์ชันภายใน เพื่อให้แม้รหัสแฮชจะมีคุณภาพต่ำ ก็ยังกระจายไปตามช่องต่าง ๆ ได้

  • ตัวเลขสุดท้ายจะถูกลดค่าด้วย hash & (table.length - 1) เพื่อให้ได้ดัชนีของช่อง
  • ความยาวของตารางเป็นเลขยกกำลังของสองเสมอ ดังนั้นมาสก์จึงใช้งานได้
public class Main {
    public static void main(String[] args) {
        String key = "Alice";
        int h = key.hashCode();
        int spread = h ^ (h >>> 16);
        int index = spread & (16 - 1);
        System.out.println("hashCode: " + h);
        System.out.println("bucket index: " + index);
    }
}

ช่องต่าง ๆ ในการทำงาน

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

การชนกันเป็นเรื่องปกติและคาดหมายได้ HashMap จัดการโดยเชื่อมรายการต่าง ๆ เข้าด้วยกันภายในช่อง

import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        Map<Integer, String> m = new HashMap<>();
        for (int i = 0; i < 5; i++) {
            m.put(i, "v" + i);
        }
        System.out.println(m.size() + " entries stored");
    }
}

การชนกันและการเชื่อมโยง

ก่อน Java 8 รายการทั้งหมดที่ชนกันจะอยู่ใน รายการเชื่อมโยงทางเดียว ภายในช่อง

การค้นหาจะไล่ดูรายการโดยเรียก equals() จนกว่าจะพบคีย์ที่ตรงกัน

  • การชนกันน้อย: ยังคงมีประสิทธิภาพโดยรวมเทียบเท่า O(1)
  • การชนกันจำนวนมากในช่องเดียว: ประสิทธิภาพของช่องนั้นจะลดลงเข้าใกล้ O(n)
import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> m = new HashMap<>();
        m.put("FB", 1);
        m.put("Ea", 2);
        System.out.println("FB hash: " + "FB".hashCode());
        System.out.println("Ea hash: " + "Ea".hashCode());
        System.out.println(m.get("FB") + ", " + m.get("Ea"));
    }
}

เหตุใด FB และ Ea จึงชนกัน

สตริง "FB" และ "Ea" มีค่า hashCode() เท่ากันใน Java นี่คือตัวอย่างคลาสสิกของการชนกัน

แม้รหัสแฮชจะเหมือนกันทุกประการ แต่แมปก็ยังเก็บทั้งสองรายการแยกจากกันได้ เพราะ equals() ใช้แยกความแตกต่างภายในช่อง

public class Main {
    public static void main(String[] args) {
        System.out.println("FB".hashCode() == "Ea".hashCode());
        System.out.println("FB".equals("Ea"));
    }
}

อัตราการใช้งาน

อัตราการใช้งานจะควบคุมว่าตารางจะเต็มเพียงใดก่อนขยายขนาด ค่าเริ่มต้นคือ 0.75

  • ความจุ 16 และอัตราการใช้งาน 0.75 หมายความว่าการปรับขนาดจะเริ่มเมื่อมี 12 รายการ
  • อัตราการใช้งานที่ต่ำกว่าจะใช้หน่วยความจำมากขึ้น แต่ลดการชนกัน
  • อัตราการใช้งานที่สูงกว่าจะประหยัดหน่วยความจำ แต่เพิ่มการชนกัน
import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        Map<Integer, Integer> m = new HashMap<>(16, 0.75f);
        for (int i = 0; i < 12; i++) m.put(i, i);
        System.out.println("Stored " + m.size() + " entries");
    }
}

การปรับขนาดตาราง

เมื่อจำนวนรายการมากกว่า capacity * loadFactor ตารางจะมีขนาด เพิ่มเป็นสองเท่า

รายการเดิมทุกรายการจะถูก คำนวณแฮชใหม่ แล้วใส่ลงในตารางที่ใหญ่ขึ้น การดำเนินการนี้ใช้ทรัพยากรมาก ดังนั้นการกำหนดขนาดล่วงหน้าจึงสำคัญสำหรับแมปขนาดใหญ่

import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        // Pre-size to avoid repeated resizes
        Map<Integer, Integer> m = new HashMap<>(1024);
        for (int i = 0; i < 800; i++) m.put(i, i * 2);
        System.out.println("size = " + m.size());
    }
}

การกำหนดขนาดล่วงหน้าเพื่อประสิทธิภาพ

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

หลักคร่าว ๆ คือ ความจุเริ่มต้น = expectedSize / 0.75 + 1

import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        int expected = 1000;
        int capacity = (int) (expected / 0.75) + 1;
        Map<Integer, String> m = new HashMap<>(capacity);
        System.out.println("Initial capacity hint: " + capacity);
        m.put(1, "ok");
        System.out.println(m.get(1));
    }
}

คีย์และค่าที่ไม่มีค่า

HashMap อนุญาตให้มี คีย์ที่มีค่าเป็นค่าว่างหนึ่งรายการ และ ค่าที่เป็นค่าว่างได้หลายรายการ

  • คีย์ที่มีค่าเป็นค่าว่างจะไปอยู่ที่ช่อง 0 เสมอ (แฮชของคีย์นี้จะถือเป็น 0)
  • ใช้ getOrDefault เพื่อหลีกเลี่ยงความกำกวมระหว่างคีย์ที่ไม่มีอยู่กับค่าที่ไม่มีค่า
import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        Map<String, String> m = new HashMap<>();
        m.put(null, "nullKeyValue");
        m.put("a", null);
        System.out.println(m.get(null));
        System.out.println(m.getOrDefault("missing", "default"));
    }
}

ไม่รับประกันลำดับการวนซ้ำ

HashMap ไม่รับประกันลำดับการวนซ้ำ ลำดับจะขึ้นอยู่กับรหัสแฮชและโครงสร้างของช่อง

หากต้องการลำดับที่คาดเดาได้ ให้ใช้ LinkedHashMap (ลำดับการแทรก) หรือ TreeMap (ลำดับที่เรียงแล้ว)

import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> m = new HashMap<>();
        m.put("one", 1);
        m.put("two", 2);
        m.put("three", 3);
        for (Map.Entry<String, Integer> e : m.entrySet()) {
            System.out.println(e.getKey() + "=" + e.getValue());
        }
    }
}

สรุปเส้นทาง get()

การค้นหาดำเนินตามขั้นตอนต่อไปนี้:

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

ค่า hashCode ที่ดีและ equals ที่ถูกต้องทำให้ทุกขั้นตอนรวดเร็ว

import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> stock = new HashMap<>();
        stock.put("apple", 50);
        stock.put("pear", 20);
        String key = "apple";
        Integer qty = stock.get(key);
        System.out.println(key + " -> " + qty);
    }
}

ตรวจสอบความเข้าใจ

ทดสอบความเข้าใจเกี่ยวกับวิธีที่ HashMap ค้นหาช่อง

ทบทวน

คุณได้เรียนรู้การทำงานภายในของ HashMap ดังนี้:

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

ต่อไป เราจะดูว่าเหตุใด hashCode เพียงอย่างเดียวจึงไม่เพียงพอหากไม่มี equals ที่ถูกต้อง

import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> m = new HashMap<>(64);
        m.put("recap", 1);
        System.out.println("HashMap basics complete: " + m.get("recap"));
    }
}

คำถามที่พบบ่อย

บทเรียน “การทำงานของ HashMap” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “การทำงานของ HashMap” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Java Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Java Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “การทำงานของ HashMap”

บักเก็ต การแฮช และการชนกัน คุณปฏิบัติ Java Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Java Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน Java Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน

บทเรียน “การทำงานของ HashMap” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน Java Academy นี้ได้ไหม

ได้ บทเรียน Java Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. การทำงานของ HashMap
  2. สัญญาของ equals/hashCode
  3. การสร้าง hashCode
  4. การเปลี่ยนเป็นต้นไม้และประสิทธิภาพ
← กลับไปที่ Java Academy