我想绝大多数做Java
或者Android
的人都是很了解HashMap
的结构的,毕竟是一个经常使用到的类,当然也可能说不出来到底是一个怎样的结构。如果不了解的话可以考虑自己来实现一个HashMap
。
简单实现HashMap
试着想想下面的问题?
HashMap
的put
方法的时间复杂度是多少?HashMap
的get
方法的时间复杂度是多少?- 怎么存储不同类型数据?这个简单,范型。
思考片刻…
get
和put
的是时间复杂度肯定不能超过O(n),HashMap
第一个需要保证的就是快速的put
以及get
,那么如何做到put
和get
的时间复杂度为O(1)或者接近O(1)?
对于一个List来说,如果知道了index
的值确实是可以做到时间复杂度为O(1)。但是如何确定List的大小?是否可以将List里面的内容分段来放,这样就保证了时间复杂度小于O(n),同时也不至于初始化的List就过大。
那么直接来张HashMap的结构图,使用Gliffy
画的,可以凑合看看。
可以看出是一个数组,数组中每一项存储的是一个链表。当里面存储的内容比较的分散的时候(数组中每一项最多只存储一个数据),那么put
和get
的时间复杂度就是O(1)了。
那么就来简单实现一个HashMap
,仅仅实现put
和get
方法。
public class CustomHashMap<K, V> {
static final float DEFAULT_LOAD_FACTOR = 0.75f;
static final int DEFAULT_SIZE = 16;
Node<K, V>[] table; // 存储数据
float loadFactor; // 负载系数。 threshold = loadFactor * table.length
int threshold; // 阀值,超过就对CustomHashMap扩充
int size; // 存储数据的数量
public CustomHashMap() {
loadFactor = DEFAULT_LOAD_FACTOR;
}
public V getValue(K key) {
int hash = (key == null ? 0 : key.hashCode());
if (table == null || table.length == 0) {
return null;
}
int index = (hash & table.length - 1);
Node<K, V> e = table[index];
while (e != null) {
if (e.key == key && e.hash == hash) {
return e.value;
}
}
return null;
}
public void putValue(K key, V value) {
int hash = (key == null ? 0 : key.hashCode());
if (table == null || table.length == 0) {
resize();
}
Node<K, V> node = new Node<>(key, value, hash);
int index = (hash & table.length - 1);
Node<K, V> e = table[index];
if (e == null) {
node.next = null;
table[index] = node;
} else {
while (e != null) {
if (e.key == key && e.hash == hash) {
e.value = value;
return;
}
e = e.next;
}
node.next = table[index];
table[index] = node;
}
if (++size > threshold) {
resize();
}
}
/**
* 调整CustomHashMap的大小
*/
private void resize() {
int newCapacity, oldCapacity;
int newThr, oldThr;
if (table == null || table.length == 0) {
oldCapacity = 0;
oldThr = 0;
} else {
oldCapacity = table.length;
oldThr = threshold;
}
if (oldCapacity > 0) {
newCapacity = oldCapacity << 1; // 加一倍
newThr = oldThr << 1; // 加一倍
} else {
newCapacity = DEFAULT_SIZE;
newThr = (int) (DEFAULT_SIZE * DEFAULT_LOAD_FACTOR);
}
threshold = newThr;
Node<K, V>[] oldTable = table;
@SuppressWarnings({"rawtypes", "unchecked"})
Node<K, V>[] newTable = (Node<K, V>[]) new Node[newCapacity];
table = newTable;
if (oldTable != null) {
for (int i = 0; i < oldCapacity; i++) {
Node<K, V> e = oldTable[i];
if (e != null) {
newTable[i] = e;
}
}
}
}
static class Node<K, V> {
Node<K, V> next;
K key;
V value;
int hash;
Node(K key, V value, int hash) {
this.key = key;
this.value = value;
this.hash = hash;
}
}
}
简单测试
public class CustomHashMapTest {
public static void main(String[] args) {
CustomHashMap<String, String> map = new CustomHashMap<>();
map.putValue("Egos", "GG");
System.out.println(map.getValue("Egos"));
}
}
HashMap和HashTable区别
HashMap
中可以存储key
为null
或者value
为null
的数据。HashTable
中不允许key
以及value
为null
。HashTable
时线程安全的(put
和get
都是使用synchronized
同步),HashMap
则不是线程安全的。这样导致的结果是在单线程中使用HashTable
效率会比HashMap
低。
一些可以思考的问题
hashCode
的计算。怎么保证HashMap
中数据尽量散开存储,即Hash
冲突减少?从数组的容量以及hashCode
的算法中都可以考虑。- 源码中会默认的保证存储的数组
table
最大长度是2^k,为什么呢?提示hash&(length-1)
。 - 使用
HashMap
的结构来扩展?例如LinkedHashMap
(保证便利取数据和存数据的顺序一样)。
PS:最近经常的静不下心来,所以想着复习下老知识。当然只是比较浅显的分析了一下,但是觉得Java中的一些数据结构还是蛮有意思的。除了基本的java.util
包下面还有java.util.concurrent
都有很多封装类,有时候可以考虑下为什么这么实现或许对自己也有一些启发。