前言
在Java编程语言中,最基本的结构就是两种,一种是数组,一种是模拟指针(引用),所有的数据结构都可以用这两个基本结构构造。
本文主题就是通过数组的方式实现Map的key-value存储(以下称为ArrayMap),然后在后续章节通过ArrayMap与Java自带的HashMap进行对比,来直观的了解Hash算法相对比数组存储性能的优势。
实现思路
ArrayMap的主要功能在Map接口中已经有过定义,实现Map接口后继承AbstractMap,避免所有功能都得重新写,完善功能部分功能。主要需要完善的功能如下:
- V put(K key, V value): 放置key-value对象,同时要考虑到数组的动态扩容。
- V get(Object key): 根据key获取value。
- int size():返回Map中元素的数量。AbstractMap中的size是需要把Map全部转化成Set然后计算size,比较消耗性能。ArrayMap中通过成员变量记录下map的元素数量。
- void clear():清空map。
- 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)。