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 <1 5 <1 58 <1
1w次 30 <1 63 <1 735 1
10w次 4827 5 5150 6 10994 5

2.Get測試

A:ArrayMap
H:HashMap
時間單位:ms

測試次數 A(1K) H(1K) A(1w) H(1w) A (10w) H(10w)
1k次 9 1 7 <1 52 <1
1w次 4 <1 41 <1 546 1
10w次 35 2 516 1 5320 3

從性能測試的結果看,數組實現的ArrayMap較HashMap有較大的性能差距,並且隨着Map中的元素數量增多,差距越大。
HashMap的性能受元素數量的影響不大,可能是由於hash碰撞較少,因此性能接近O(1)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章