Java集合相关学习——手写一个简单的Map接口实现类(HashMap)

简介: Java集合相关学习——手写一个简单的Map接口实现类(HashMap)

1.关于Map和HashMap


这两个东西想必大家都很熟悉了,简单的概括就是:面试中会问到、笔试中会考到、开发中会用到。

那么有关这块知识呢,大家可以参考我的这几篇文章:

HashMap常用方法举例

HashMap源码剖析

Java集合相关面试题


2.案例代码


要求是这样的:

请完善TestMap类,要求只实现getputremovesize四个方法

-要求不能使用第三方包,不能使用JDKMap实现类
-
请对完成的方法进行测试,在main方法中调用验证


因为在Map这种K-V键值对的存储结构中,其实也是一个哈希表,那么它每个节点都有自己的 keyvaluehash值、指向下一个节点的指针,所以我们也需要定义一个内部类来存储这些信息。

然后我们同时需要模仿jdk官方的Map接口,自定义我们自己的Map接口,其中包括一些常用方法。(具体的已经写在代码注释中了)

public interface Map<K, V> {
    int size();
    boolean isEmpty();
    boolean containsKey(Object key);
    boolean containsValue(Object value);
    V get(Object key);
    V put(K key, V value);
    V remove(Object key);
    void clear();
    interface Entry<K, V> {
        K getKey();
        V getValue();
    }
}
import java.util.Collection;
import java.util.Set;
public class TestMap<K, V> implements Map<K, V> {
    private static final int DEFAULT_INITIAL_CAPACITY = 16;
    private static final float DEFAULT_LOAD_FACTOR = 0.75f;
    private float loadFactor = 0;
    private int initCapacity = 0;
    private Entry<K, V>[] table = null;
    private int size = 0;
    public TestMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        this.initCapacity = DEFAULT_INITIAL_CAPACITY;
        table = new Entry[this.initCapacity];
    }
//    private int hash(K key) {
//        int h;
//        return (key == null) ? 0 : Math.abs((h = key.hashCode())) ^ (h >>> 16);
//    }
    @Override
    public int size() {
        return size;
    }
    @Override
    public V get(Object key) {
        Entry<K, V> e = null;   //默认不存在
        //计算哈希码
        int hash = key.hashCode();
        //计算存储的位置
        int index = hash % table.length;
        //存储到指定的位置
        if (table[index] != null) { //该位置存储了元素
            Entry<K, V> entry = table[index]; //先指向第一个元素
            while (entry != null) {
                //比较
                if (entry.hash == hash && entry.getKey().equals(key)){ //找到了这个key
                    e = entry;
                    break;
                }
                //指向下一个结点,继续循环判断
                entry = entry.next;
            }
        }
        return e == null ? null : e.value;
    }
    @Override
    public V put(K key, V value) {
        //计算哈希码
        int hash = key.hashCode();
        //计算存储的位置
        int index = hash % table.length;
        //存储到指定的位置
        if (table[index] == null) {
            //该位置还没有元素
            table[index] = new Entry<K, V>(key, value, null, hash);
            ++size;
        } else {  //该位置已经存储了元素
            //查询是否存在相同key的Entry
            Entry<K, V> entry = table[index];  //指向链表的第一个元素
            while (entry != null) {
                //比较
                if (entry.hash == hash && entry.getKey().equals(key)){
                    //如果找到了相同的key,使用新的value将旧的value替换掉
                    entry.value = value;
                    return entry.value;
                }
                //指向下一个结点,继续循环判断
                entry = entry.next;
            }
            //如果没有相同的key,添加新的结点,下一个结点就是原来的第一个结点,也就是table[index],添加到链表的第一个位置(table[index])
            table[index] = new Entry<K, V>(key, value, table[index], hash);
            ++size;
        }
        return value;
    }
    @Override
    public V remove(Object key) {
        //计算哈希码
        int hash = key.hashCode();
        //计算存储的位置
        int index = hash % table.length;
        //先定义一个prev表示待删除的元素的上一个
        Entry<K, V> prev = table[index];
        Entry<K, V> e = prev;
        while (e != null) {
            //定位出e的下一个元素,当找到我们要删除的元素时,将链表切断,将prev和next连接即可。
            Entry<K, V> next = e.next;
            //在链表上定位到这个元素,先判断hash值是否相等,再判断key的equals是否相等!
            if(e.hash == hash && (e.key == key || (key != null && key.equals(e.key)))) {
                --size;
                //这里,还得判断一个东西,即定位到的这个Entry是否是table[i],因为table[i]上的删除和链表上的删除有区别
                if(prev == e) {
                    table[index] = next;
                } else {
                    prev.next = next; //如果不是table位置的,则是链表后边的元素,此时,将prev的下一个置为e的next即可,此时,e已被切断,e的前后Entry被连接起来
                }
                return e.value;
            }
            //上边的if没进去,则执行下一次循环,指针向后移动一位!
            prev = e;
            e = next;
        }
        return null;
    }
    @Override
    public boolean isEmpty() {
        return size == 0;
    }
    @Override
    public boolean containsKey(Object key) {
        return get(key) != null;
    }
    @Override
    public boolean containsValue(Object value) {
        Entry<K, V>[] tab; V v;
        if ((tab = table) != null && size > 0) {
            for (Entry<K, V> e : tab) {
                for (; e != null; e = e.next) {
                    if ((v = e.value) == value ||
                            (value != null && value.equals(v)))
                        return true;
                }
            }
        }
        return false;
    }
    @Override
    public void clear() {
        Entry<K, V>[] tab;
        if ((tab = table) != null && size > 0) {
            size = 0;
            for (int i = 0; i < tab.length; ++i)
                tab[i] = null;
        }
    }
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("{");
        for (int i = 0; i < table.length; i++) {  //外层循环主数组
            if (table[i] != null) {
                Entry<K, V> entry = table[i];  //让entry指向链表的第一个元素
                while (entry != null) {    //内层循环循环链表
                    sb.append(entry.key + " = " + entry.value + ", ");
                    //指向下一个结点,继续循环判断
                    entry = entry.next;
                }
            }
        }
        if (size != 0){      //如果有元素就把最后一个逗号去掉
            sb.deleteCharAt(sb.length() - 1);
        }
        sb.setCharAt(sb.length() - 1, '}');
        return sb.toString();
    }
    // HashMap中存储节点信息的数据结构
    class Entry<K, V> implements Map.Entry<K, V> {
        K key;
        V value;
        Entry<K, V> next;
        int hash;  //记录下标
        Entry(K key, V val, Entry<K, V> next, int hash) {
            this.key = key;
            this.value = val;
            this.next = next;
            this.hash = hash;
        }
        @Override
        public K getKey() {
            return key;
        }
        @Override
        public V getValue() {
            return value;
        }
    }
    public static void main(String[] args) {
        TestMap<String, Object> map = new TestMap<>();
        map.put("姓名", "张三");
        map.put("年龄", "20");
        map.put("性别", "男");
        map.put("爱好", "打篮球");
        System.out.println(map.get("姓名"));
        System.out.println(map);
        System.out.println("map集合中键值对数量为:" + map.size());
        System.out.println("map集合中是否包含key:" + map.containsKey("姓名"));
        System.out.println("map集合中是否包含value:" + map.containsValue("小哥"));
        String s = (String) map.remove("性别");
        System.out.println("移除的key对应的value值为:" + s);
        System.out.println("移除之后map集合中键值对数量为:" + map.size());
        map.clear();
        System.out.println("map集合是否为空:" + map.isEmpty());
    }
}


相关文章
|
10天前
|
Java
Java 8 引入的 Streams 功能强大,提供了一种简洁高效的处理数据集合的方式
Java 8 引入的 Streams 功能强大,提供了一种简洁高效的处理数据集合的方式。本文介绍了 Streams 的基本概念和使用方法,包括创建 Streams、中间操作和终端操作,并通过多个案例详细解析了过滤、映射、归并、排序、分组和并行处理等操作,帮助读者更好地理解和掌握这一重要特性。
20 2
|
10天前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
14天前
|
存储 Java
判断一个元素是否在 Java 中的 Set 集合中
【10月更文挑战第30天】使用`contains()`方法可以方便快捷地判断一个元素是否在Java中的`Set`集合中,但对于自定义对象,需要注意重写`equals()`方法以确保正确的判断结果,同时根据具体的性能需求选择合适的`Set`实现类。
|
14天前
|
存储 Java 开发者
在 Java 中,如何遍历一个 Set 集合?
【10月更文挑战第30天】开发者可以根据具体的需求和代码风格选择合适的遍历方式。增强for循环简洁直观,适用于大多数简单的遍历场景;迭代器则更加灵活,可在遍历过程中进行更多复杂的操作;而Lambda表达式和`forEach`方法则提供了一种更简洁的函数式编程风格的遍历方式。
|
14天前
|
Java 开发者
|
27天前
|
安全 Java 程序员
深入Java集合框架:解密List的Fail-Fast与Fail-Safe机制
本文介绍了 Java 中 List 的遍历和删除操作,重点讨论了快速失败(fail-fast)和安全失败(fail-safe)机制。通过普通 for 循环、迭代器和 foreach 循环的对比,详细解释了各种方法的优缺点及适用场景,特别是在多线程环境下的表现。最后推荐了适合高并发场景的 fail-safe 容器,如 CopyOnWriteArrayList 和 ConcurrentHashMap。
53 5
|
27天前
|
存储 Java API
详细解析HashMap、TreeMap、LinkedHashMap等实现类,帮助您更好地理解和应用Java Map。
【10月更文挑战第19天】深入剖析Java Map:不仅是高效存储键值对的数据结构,更是展现设计艺术的典范。本文从基本概念、设计艺术和使用技巧三个方面,详细解析HashMap、TreeMap、LinkedHashMap等实现类,帮助您更好地理解和应用Java Map。
45 3
|
28天前
|
安全 Java 程序员
Java集合之战:ArrayList vs LinkedList,谁才是你的最佳选择?
本文介绍了 Java 中常用的两个集合类 ArrayList 和 LinkedList,分析了它们的底层实现、特点及适用场景。ArrayList 基于数组,适合频繁查询;LinkedList 基于链表,适合频繁增删。文章还讨论了如何实现线程安全,推荐使用 CopyOnWriteArrayList 来提升性能。希望帮助读者选择合适的数据结构,写出更高效的代码。
56 3
|
14天前
|
存储 Java 开发者
Java中的集合框架深入解析
【10月更文挑战第32天】本文旨在为读者揭开Java集合框架的神秘面纱,通过深入浅出的方式介绍其内部结构与运作机制。我们将从集合框架的设计哲学出发,探讨其如何影响我们的编程实践,并配以代码示例,展示如何在真实场景中应用这些知识。无论你是Java新手还是资深开发者,这篇文章都将为你提供新的视角和实用技巧。
12 0
|
19天前
|
Java API Apache
java集合的组内平均值怎么计算
通过本文的介绍,我们了解了在Java中计算集合的组内平均值的几种方法。每种方法都有其优缺点,具体选择哪种方法应根据实际需求和场景决定。无论是使用传统的循环方法,还是利用Java 8的Stream API,亦或是使用第三方库(如Apache Commons Collections和Guava),都可以有效地计算集合的组内平均值。希望本文对您理解和实现Java中的集合平均值计算有所帮助。
23 0