Java集合Collection之实现原理解读(Map)

一、简介

除了List,平时在项目中用的比较多的还有Map,Map是存放键值对的数据结构,键不允许重复,如果键重复会覆盖之前的值。比较常用到的有: HashMap和HashTable。本文将会模仿Map的源码实现两个自定义的Map(一个是没有用hashcode的效率低的版本,一个是用hashcode优化版),只是帮助理解Map底层实现原理,实际项目中没有必要去自定义Map。

二、实现原理

Map是存放键值对的数据结构键不允许重复,如果新增的时候已经存放相同键的元素,那么会覆盖之前相同键的值。Map底层是通过操作Entry[]数组来实现的,Entry里面主要就是存放我们的键值对。

三、自定义Map(效率低,没有使用hashcode)

Entry类:存放键值对的对象

public class CustomEntry {
    /**
     * 键
     */
    private Object key;
    /**
     * 值
     */
    private Object value;

    public CustomEntry() {

    }

    public CustomEntry(Object key, Object value) {
        this.key = key;
        this.value = value;
    }

    public Object getKey() {
        return key;
    }

    public void setKey(Object key) {
        this.key = key;
    }

    public Object getValue() {
        return value;
    }

    public void setValue(Object value) {
        this.value = value;
    }
}
package wsh.version1;

/**
 * 自定义Map(版本一,效率较低,需要循环遍历找相同的key)
 *
 * @author weishihuai
 * @date 2018/9/27 21:25
 * 说明:
 * 1. Map是存放键值对(key-value)的数据结构
 * 2. Map键值不允许重复,如果重复会覆盖之前的值
 * 3. 根据键值可以找到对应的值
 */
public class CustomMap {

    private CustomEntry[] array = new CustomEntry[1000];

    private int size;

    /**
     * 往Map中新增元素
     * 原理: 新建Entry对象,往数组中添加元素即可
     *
     * @param key   键
     * @param value 值
     */
    public void put(Object key, Object value) {
        CustomEntry customEntry = new CustomEntry(key, value);
        //判断是否存在相同的键,如果有相同的直接覆盖之前的值,size不用改变。
        for (int i = 0; i < size; i++) {
            if (array[i].getKey().equals(key)) {
                array[i].setValue(value);
                return;
            }
        }
        array[size] = customEntry;
        size++;
    }

    /**
     * 根据键找到相应的值
     * 原理: 循环遍历entry数组中的key集合,找到返回键对应的值,未找到返回null
     *
     * @param key 键
     * @return 键对应的值
     */
    public Object get(Object key) {
        for (int i = 0; i < size; i++) {
            if (array[i].getKey().equals(key)) {
                return array[i].getValue();
            }
        }
        return null;
    }

    /**
     * 判断是否存放指定key的元素
     * 原理: 循环遍历所有的key集合,找到返回true,找不到返回false
     *
     * @param key 键
     * @return 是否包含对应的键的元素
     */
    public boolean containsKey(Object key) {
        for (int i = 0; i < size; i++) {
            if (array[i].getKey().equals(key)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 判断是否包含指定值的元素
     * 原理: 循环遍历所有的value集合,找到返回true,找不到返回false
     *
     * @param value 值
     * @return 是否包含指定值的元素
     */
    public boolean containsValue(Object value) {
        for (int i = 0; i < size; i++) {
            if (array[i].getValue().equals(value)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 返回Map中元素的个数
     *
     * @return Map的元素个数
     */
    public int size() {
        return size;
    }

    /**
     * 判断Map是否为空
     *
     * @return map是否为空
     */
    public boolean isEmpty() {
        return size == 0;
    }


}

四、部分方法详解

【a】Entry类    主要属性就是键值对

public class CustomEntry {
    /**
     * 键
     */
    private Object key;
    /**
     * 值
     */
    private Object value;
}

【b】 put(Object key, Object value) {} 往Map插入新的元素

1. 实现原理:   通过传递的参数新建Entry对象,往数组中添加元素即可。

2. 注意需要判断是否存在相同的键,如果有相同的直接覆盖之前的值,size不用改变。

//判断是否存在相同的键,如果有相同的直接覆盖之前的值,size不用改变。
        for (int i = 0; i < size; i++) {
            if (array[i].getKey().equals(key)) {
                array[i].setValue(value);
                return;
            }
        }

这里是直接遍历整个数组查找是否有相同key的元素。

【c】get(Object key) {} 根据键取出对应的值

1. 实现原理:  循环遍历Entry数组中的key集合,找到返回键对应的值,未找到返回null。

2. 注意查找是否有相同key采用的是equals方法。

【d】containsKey(Object key) {}    判断Map是否存放某个键

1. 实现原理: 循环遍历所有的key集合,找到返回true,找不到返回false。

【e】containsValue(Object value) {} 判断是否包含指定值的元素

1. 实现原理:  循环遍历所有的value集合,找到返回true,找不到返回false。

【f】isEmpty(){}   判断Map是否为空

1. 实现原理:  return size == 0;

通过上面的代码可以看到,都是循环整个数组去遍历,如果数组过大,显然效率并不是很好,那么通过查看HashMap的源码可以看到,有一种优化方案,就是使用hashcode和equals()方法来优化,这样就不用整个数组遍历。

测试:

package wsh.version1;

/**
 * 测试自定义Map
 *
 * @author weishihuai
 * @date 2018/9/27 21:30
 */
public class TestCustomMap {

    public static void main(String[] args) {

        CustomMap customMap = new CustomMap();
        /***********************************put(key,value)*************************************/
        customMap.put("aaa", "zhangsan");
        customMap.put("bbb", "lisi1");
        customMap.put("bbb", "lisi2");
        customMap.put("ccc", "wangwu");

        /*******************************get(key)***********************************************/
        //zhangsan
        System.out.println(customMap.get("aaa"));

        /************************************size()********************************************/
        //3
        System.out.println(customMap.size());

        /**********************************get(key)********************************************/
        //lisi2
        System.out.println(customMap.get("bbb"));

        /*************************************containsKey(key)*********************************/
        //true
        System.out.println(customMap.containsKey("aaa"));
        //false
        System.out.println(customMap.containsKey("123"));

        /************************************containsValue(value)******************************/
        //true
        System.out.println(customMap.containsValue("zhangsan"));
        //false
        System.out.println(customMap.containsValue("123"));

        /************************************isEmpty()*****************************************/
        //false
        System.out.println(customMap.isEmpty());


    }


}

五、HashMap简介

HashMap也是存放键值对的结构,HashMap底层是通过数组+链表的方式实现的,通过键的hashcode确定在数组的某个位置,如果多个hashcode相同,那么这些都会存放到数组里面对应的一条链表上,链表里面存放是我们的Entry对象,即键值对。画了个简图:

六、自定义HashMap(效率高,使用hashcode)

package wsh.version2;

import java.util.LinkedList;

/**
 * 自定义Map(通过hashcode/equals方法实现,效率高)
 *
 * @author weishihuai
 * @date 2018/10/1 16:05
 * <p>
 * 说明:
 * Map底层结构是通过(数组 + 链表)实现,只是帮助理解一下Map大概实现原理,没必要自定义Map
 */
public class CustomMap {

    private LinkedList[] array = new LinkedList[1000];

    private int size;

    /**
     * 往Map中插入键值对元素
     *
     * @param key   键
     * @param value 值
     */
    public void put(Object key, Object value) {
        CustomEntry customEntry = new CustomEntry(key, value);
        int hashCode = key.hashCode();
        //根据hashCode计算出在bucket[]数组中的位置
        int index = hashCode % 1000;

        if (null == array[index]) {
            //空链表
            LinkedList linkedList = new LinkedList();
            linkedList.add(customEntry);
            array[index] = linkedList;
        } else {
            //不是空链表,拿出链表进行遍历,如果键值相同,值会被覆盖
            LinkedList list = array[index];
            for (int i = 0; i < list.size(); i++) {
                CustomEntry customEntry2 = (CustomEntry) list.get(i);
                if (customEntry2.getKey().equals(key)) {
                    //键值重复直接覆盖
                    customEntry2.setValue(value);
                    return;
                }
            }
            array[index].add(customEntry);
        }
        size++;
    }

    /**
     * 根据key找到对应的值
     *
     * @param key 键
     * @return 值
     * <p>
     * 原理: 根据key的hashCode计算出位置,如果该位置对应的链表不为空,循环遍历链表,找到对应的key,返回对应的值。如果未找到则返回null
     */
    public Object get(Object key) {
        int index = key.hashCode() % 1000;
        if (null != array[index]) {
            LinkedList list = array[index];
            for (int i = 0; i < list.size(); i++) {
                CustomEntry customEntry = (CustomEntry) list.get(i);
                if (customEntry.getKey().equals(key)) {
                    return customEntry.getValue();
                }
            }
        }
        return null;
    }

    public int size() {
        return size;
    }
}

七、部分方法详解

【a】 put(Object key, Object value) {}   往HashMap中插入新的键值对

1. 实现原理:通过 key.hashCode()计算出hashcode, 然后根据某种规则算出一个索引值,这个索引对应在bucket[]数组汇总的位置

a. 如果索引对应元素是一个空链表,那么新建一个LinkedList,把键值对存放到链表中。

b. 如果索引对应元素不是一个空链表,那么直接通过数组【index】取出链表对象,遍历链表,如果键值相同,值会被覆盖,并将键值对加入到已有的链表中。

【b】get(Object key) {} 根据key找到对应的值

1. 实现原理:  根据key的hashCode计算出位置,如果该位置对应的链表不为空,循环遍历链表,找到对应的key,返回对应的值。如果未找到则返回null。

八、测试

package wsh.version2;

/**
 * 测试自定义Map
 *
 * @author weishihuai
 * @date 2018/10/01 16:30
 */
public class TestCustomMap {

    public static void main(String[] args) {
        CustomMap customMap = new CustomMap();
        customMap.put("name", "zhangsan");
        customMap.put("name", "lisi");
        //lisi
        System.out.println(customMap.get("name"));
        //1
        System.out.println(customMap.size());

    }


}

九、总结

以上通过两个示例以及一些常用方法的讲解,相信可以帮助大家理解一下HashMap底层大概实现的原理,这里只实现了部分方法,有些方法有些问题暂时没有考虑。本文是作者在学习HashMap底层实现原理的一些总结以及方法讲解,仅供大家学习参考,一起学习一起进步。

猜你喜欢

转载自blog.csdn.net/Weixiaohuai/article/details/82935427
今日推荐