Java容器—— 「通过数组实现自己的ArrayMap」

前言

在Java编程语言中,最基本的结构就是两种,一种是数组,一种是模拟指针(引用),所有的数据结构都可以用这两个基本结构构造。
本文主题就是通过数组的方式实现Map的key-value存储(以下称为ArrayMap),然后在后续章节通过ArrayMap与Java自带的HashMap进行对比,来直观的了解Hash算法相对比数组存储性能的优势。

实现思路

ArrayMap的主要功能在Map接口中已经有过定义,实现Map接口后继承AbstractMap,避免所有功能都得重新写,完善功能部分功能。主要需要完善的功能如下:

  1. V put(K key, V value): 放置key-value对象,同时要考虑到数组的动态扩容。
  2. V get(Object key): 根据key获取value。
  3. int size():返回Map中元素的数量。AbstractMap中的size是需要把Map全部转化成Set然后计算size,比较消耗性能。ArrayMap中通过成员变量记录下map的元素数量。
  4. void clear():清空map。
  5. entrySet(): 将元素转化成一个Set集合。

实现代码

public class ArrayMap<K, V> extends AbstractMap<K, V> implements Map<K, V> {

    /**
     * 默认数组长度
     */
    private static final int DEFAULT_LENGTH = 16;
    /**
     * 数据存储的数组
     */
    private Node[] tables;
    /**
     * map内元素的数量
     */
    private int index;

    public ArrayMap() {
        this(DEFAULT_LENGTH);
    }

    /**
     * @param length 初始化长度
     */
    public ArrayMap(int length) {
        if (length <= 0) {
            throw new IllegalArgumentException("非法参数");
        }
        tables = new Node[length];
    }

    /**
     * 从Map中获取元素
     *
     * @param key 获取的对象
     * @return 查询得到的对象,若是不存在,则返回Null
     */
    @Override
    public V get(Object key) {
        V value = null;
        for (int i = 0; i < index; i++) {
            Node<K, V> node = tables[i];
            K nodeKey = node.getKey();
            if ((nodeKey == null && key == null) || (key != null && key.equals(nodeKey))) {
                return node.getValue();
            }
        }
        return value;
    }

    /**
     * 在Map中放置Key-Value对
     *
     * @param key   放置的Key
     * @param value 放置的Value
     * @return 返回value
     */
    @Override
    public V put(K key, V value) {
        if (index >= tables.length) {
            throw new ArrayIndexOutOfBoundsException("数组越界");
        }
        for (int i = 0; i < index; i++) {
            Node<K, V> node = tables[i];
            K nodeKey = node.getKey();
            if ((nodeKey == null && key == null) || (key != null && key.equals(nodeKey))) {
                node.setValue(value);
                return value;
            }
        }
        tables[index] = new Node(key, value);
        if (++index == tables.length) {
            resize();
        }
        return value;
    }

    @Override
    public int size() {
        return index;
    }

    @Override
    public void clear() {
        index = 0;
        int parLength = tables.length;
        tables = new Node[parLength];
    }

    /**
     * 尚未实现的方法
     *
     * @return null
     */
    @Override
    public Set<Entry<K, V>> entrySet() {
        Set<Entry<K, V>> set = new HashSet<>();
        for (int i = 0; i < index; i++) {
            Node<K, V> node = tables[i];
            set.add(node);

        }
        return set;
    }

    /**
     * 数组扩容
     */
    private void resize() {
        int parLength = tables.length;
        Node<K, V>[] newTable = new Node[parLength * 3 / 2];
        for (int i = 0; i < parLength; i++) {
            newTable[i] = tables[i];
        }
        tables = newTable;
    }

    class Node<K, V> implements Entry<K, V> {
        private K key;
        private V value;

        public Node(K key, V value) {
            this.key = key;
            this.value = value;
        }

        @Override
        public K getKey() {
            return key;
        }

        @Override
        public V getValue() {
            return value;
        }

        @Override
        public V setValue(V value) {
            this.value = value;
            return value;
        }

        @Override
        public String toString() {
            return key + "=" + value;
        }

    }
}

单元测试:

public class ArrayMapTest {

    @Test
    public void testArray(){
        Map<String, String> map = new ArrayMap<>();
        for(int i=0;i<10;i++){
            map.put("floow"+i,"value"+i);
        }
        map.put("floow1","33");
        map.put(null,"tt");
        map.put("tt",null);
        //测试put/get
        Assert.assertEquals("tt",map.get(null));
        Assert.assertEquals(null,map.get("tt"));
        Assert.assertEquals("33",map.get("floow1"));
        //测试size()
        Assert.assertEquals(12,map.size());
        //测试clear()
        map.clear();
        Assert.assertEquals(0,map.size());
        Assert.assertTrue(map.isEmpty());
    }

    @Test
    public void testArrayIterator(){
        Map<String, String> map = new ArrayMap<>();
        for(int i=0;i<10;i++){
            map.put("floow"+i,"value"+i);
        }
        Set<Map.Entry<String,String>> entrySet = map.entrySet();
        Assert.assertEquals(10,entrySet.size());
    }

}

ArrayMap vs HashMap

实现好ArrayMap后,我们需要来对比下ArrayMap和HashMap的性能差别。首先从实现方式上看,ArrayMap就已经看出很多性能问题了,比如put和get都需要遍历,但是究竟差多少这个需要通过测试才能有比较直观的认知。

1.put测试

测试需要考虑Map初始数据量以及操作次数,通过小、中、大三个级别的初始数量来对比下操作消耗时间。

A:ArrayMap
H:HashMap
时间单位:ms

测试次数A(1K)H(1K)A(1w)H(1w)A (10w)H(10w)
1k次<1<15<158<1
1w次30<163<17351
10w次4827551506109945

2.Get测试

A:ArrayMap
H:HashMap
时间单位:ms

测试次数A(1K)H(1K)A(1w)H(1w)A (10w)H(10w)
1k次917<152<1
1w次4<141<15461
10w次352516153203

从性能测试的结果看,数组实现的ArrayMap较HashMap有较大的性能差距,并且随着Map中的元素数量增多,差距越大。
HashMap的性能受元素数量的影响不大,可能是由于hash碰撞较少,因此性能接近O(1)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值