Hashmap
HashMap继承AbstractMap类,实现了Map接口(由下图可见),在java集合中,它是一个基本的存储数据的结构。他的底层是由 数组+链表 构成,通过特定的哈希函数从 键(key)来定位值。
HashMap的结构形式大概如图所示:
构造哈希函数
1.直接寻址法
f(key) = key
f(key) = a*key+y
2.除留余数法
f(x) = x%m
hash碰撞 m n f(m) = f(n) (m不等于n)
解决方式:
1.链地址法
2.探测发
hashmap的特点:
1、数据以key-value键值对形式存在
2、HashMap中键不能重复,即同一个key只能出现一次、若key相同,value会被覆盖
3、可以存在key为null的情况,value可以存在多个为null的情况
4、数据不能保证顺序性
hashmap的源码分析:
1.方法、属性、继承关系
HashMap的主干是一个Entry数组。Entry是HashMap的基本组成单元,每一个Entry包含一个key-value键值对。
继承关系:
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
继承了AbstractMap抽象类,主要实现了map接口,当前HashMap也能实现克隆序列化Cloneable。
属性:
//HashMap的初始容量为16;
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
//初始容量既可以传参给定,也可以给默认值,主要作用是对数据大小进行初始化,容量最大值 1<<30
static final int MAXIMUM_CAPACITY = 1 << 30;
//加载因子:在扩容时使用(使用方法put),默认大小是0.75(loadFactor:用来计算threshold)
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//底层存储数据,数组的数据类型是Entry
static final Entry<?,?>[] EMPTY_TABLE = {};
//threshold扩容阈值:计算方式如上:容量*加载因子得到, 决定map是否需要扩容,threshold = capacity * loadFactor(hashmap的size的最大值)
threshold = (int) Math.min(capacity * loadFactor)
//Entry的数据类型包含存储的key——value数据,hash,还有entry类型的next属性,还可以看出entry数据是通过链表来组织连接
底层数据结构是:数组+链表
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
int hash;
2.CRUD
put添加元素的过程:
1.判断table是否为空数组,是则创建数组(注意:threshold值得改变)
2.key为null的添加操作做特殊处理,将该key对应的entry实体放入table[0]的位置(存在该key为null的实体进行覆盖操作)
3.过程通过key进行hash
4.通过indexfor方法来定位数据存储的索引位置
5.遍历该位置的链表,判断是否存在该key的实体(k.hashcode == equals),有则将value替换
6.将该元素插入到对应的索引的链表 size
扩容(2*table.length)方式扩容(扩容时机)
插入位置(第一个位置,头插)
public V put(K key, V value) {
//如果table数组为空数组{},进行数组填充(为table分配实际内存空间),入参为threshold,此时threshold为initialCapacity 默认是1<<4(24=16)
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
//如果key为null,存储位置为table[0]或table[0]的冲突链上
if (key == null)
return putForNullKey(value);
int hash = hash(key);//对key的hashcode进一步计算,确保散列均匀
int i = indexFor(hash, table.length);//获取在table中的实际位置
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
//如果该对应数据已存在,执行覆盖操作。用新value替换旧value,并返回旧value
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;//保证并发访问时,若HashMap内部结构发生变化,快速响应失败
addEntry(hash, key, value, i);//具体的插入方法(扩容),头插法
return null;
}
remove删除元素的过程:
1、数组元素size为0,即不存在元素,直接返回
2、对key进行哈希,找到在数组table中的索引位置
3、对该位置的链表进行从头开始遍历
4、prev,e,next定义变量,找出key的位置
分两种情况:
key对应的entry实体在链表头节点,直接将该节点的next的置为头结点
entry实体在链表中,该判断结点的后一个节点的next直接指向该节点的next节点
public V remove(Object key) {
Entry<K,V> e = removeEntryForKey(key);
return (e == null ? null : e.value);
}
通过key删除数据并且返回以前的值
final Entry<K,V> removeEntryForKey(Object key) {
if (size == 0) {
return null;
}
//先找到节点的位置,key == null(table[0]) 否则(hash(key))
int hash = (key == null) ? 0 : hash(key);
int i = indexFor(hash, table.length);
Entry<K,V> prev = table[i];
Entry<K,V> e = prev;
while (e != null) {
Entry<K,V> next = e.next;
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
modCount++;
size--;
//如果要删除的节点是第一个只需要将第二个放入数组中就可以
if (prev == e)
table[i] = next;
else
//如果不是就需要将前一个节点的next置于next
prev.next = next;
e.recordRemoval(this);
return e;
}
prev = e;
e = next;
}
return e;
}
get获取元素的过程:
get的过程是先计算hash然后通过hash与table.length取摸计算index值,然后遍历table[index]上的链表,直到找到key,然后返回
注意点:
什么情况返回null
1.先对key进行哈希,并找到key存在的数组table的索引位置
2.对索引位置的链表进行遍历,判断key是否相等(key的hashcode,==,equals)
public V get(Object key) {
//如果key为null调用一个单独的方法,说明HashMap支持键为null
if (key == null)
return getForNullKey();
//先获取到Entry实体
Entry<K,V> entry = getEntry(key);
//返回值
return null == entry ? null : entry.getValue();
}
通过键取值
getForNullKey
private V getForNullKey() {
if (size == 0) {
return null;
}
//注意:null键所对应的实体在table[0]存储
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null)
return e.value;
}
return null;
}
containsValue判断key是否存在方法
public boolean containsKey(Object key) {
//直接调用getEntry看能不能找到对应的Entry实体即可
return getEntry(key) != null;
}
final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
}
int hash = (key == null) ? 0 : hash(key);
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}
containsKey判断value是否存在方法
public boolean containsValue(Object value) {
//特殊情况
if (value == null)
return containsNullValue(); //查找是否为值为null的value
//直接两层for循环遍历
Entry[] tab = table;
for (int i = 0; i < tab.length ; i++)
for (Entry e = tab[i] ; e != null ; e = e.next)
if (value.equals(e.value))
return true;
return false;
}
两者区别点:
1、判断相等方式不同:containsKey中key需要比较Hashcode、== equals
containsValue中value直接equals
2、遍历数组大小不同:containsKey中通过key找到该key存在的数组索引位置,遍历该索引位置的链表即可
containsValue中需要对该链表所有数组的所有链表全部遍历
下面将根据HashMap的基本特性和属性实现一个简单的MyHashMap
/**
* 描述:自己实现一个hashmap
* @Author administrator{GINO ZHANG}
* @Date2018/10/28
*/
class myhashmap {
private static int limit; //阈值,表示扩容时机,即当前数组链表长度是否到达阈值,到达即扩容
private int size; //元素个数
private int tableNum[]; //表示table上的每一个索引位置当前链表的数量个数
private Entry[] table; //entry类型的数组
class Entry { //entry类型的数组
private final Object key;
private String value;
private Entry next;
public Entry(){ //无参构造函数
this.key = 0;
this.value = null;
this.next = next;
}
public Entry(Object key, String value, Entry next) { //带参构造函数
this.key = key;
this.value = value;
this.next = next;
}
/**
* get,set方法
* @return
*/
public Object getKey() {
return key;
}
public Object getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public Entry getNext() {
return next;
}
public void setNext(Entry next) {
this.next = next;
}
}
public myhashmap(){ // myhashmap类的无参构造函数
this.table = new Entry[4];
this.size = 0;
this.tableNum = tableNum;
this.limit = table.length;
}
/**
* 根据key的hashcode和table长度取模计算key在table中的位置
* @param key
* @return
*/
public int hashCode(Object key) {
return key.hashCode() % table.length;
}
public void put(Object key, String value) {
int index = hashCode(key); //通过index方法计算key的hashCode,确定在数组中索引的位置
Entry entry = table[index]; //遍历index位置的entry,若找到重复key则覆盖对应entry的值,然后返回
while (entry != null) {
//三种判断是否存在key实体的方法
if (entry.getKey().hashCode() == key.hashCode() && (entry.getKey() == key || entry.getKey().equals(key))) {
entry.setValue(value);
}
entry = entry.getNext(); //在entry节点建立前驱next的联系
}
//若index位置没有entry或者未找到重复的key,则将新key添加到table的index位置
add(index, key, value);
size++;
}
private void add(int index, Object key, String value) {
//将新的entry放到table的index位置第一个,若原来有值则以链表形式存放
Entry entry = new Entry(key, value, table[index]);
table[index] = entry;
//判断size是否达到扩容阈值,若达到则进行扩容
if (key.hashCode() >= limit) {
resize();
}
}
/**
* 扩容
* @param
*/
private void resize() {
Entry[] newTable = new Entry[2*table.length];
//遍历原table,将每个entry都重新计算hash放入newTable中
for (int i = 0; i < table.length; i++) {
Entry old = table[i];
while (old != null) {
Entry next = old.getNext();
int index = hashCode(old.getKey());
old.setNext(newTable[index]);
newTable[index] = old;
old = next;
}
}
//用newTable替table
table = newTable;
//修改临界值
limit = newTable.length;
}
/**
* remove方法
* @param key
*/
public void remove(Object key) {
int index = hashCode(key); //通过index方法计算key的hashCode,确定在数组中索引的位置
Entry prev = null;
Entry entry = table[index];
while (entry != null) {
if (entry.getKey().hashCode() == key.hashCode() && (entry.getKey() == key || entry.getKey().equals(key))) {
if (prev == null) {
table[index] = entry.getNext(); //
} else{
prev.setNext(entry.getNext());
}
//如果成功找到并删除,修改size
size--;
}
prev = entry;
entry = entry.getNext();
}
}
/**@Override
public String toString() {
return "myhashmap{" +
"table=" + Arrays.toString(table) +
'}';
}*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
for (Entry entry : table) {
while (entry != null) {
sb.append(entry.getKey() + ":" + entry.getValue() + "\n");
entry = entry.getNext();
}
}
return sb.toString();
}
public static void main(String[] args) {
myhashmap hashMap = new myhashmap();
hashMap.put(0,"jiaruo");
hashMap.put(1,"Jenny");
hashMap.put(2,"Danny");
hashMap.put(3,"Corleone Z");
hashMap.put(4,"xiaoming");
hashMap.put(19,"xinan");
hashMap.remove(4);
System.out.println(hashMap);
System.out.println(hashMap.size);
}
}
打印结果
C:\java\java7\jdk1.7.0_80\bin\java.exe -javaagent:D:\ideaIU-2018.1.5.win\lib\idea_rt.jar=5940:D:\ideaIU-2018.1.5.win\bin -Dfile.encoding=UTF-8 -classpath
0:jiaruo
1:Jenny
2:Danny
3:Corleone Z
19:xinan
5
Process finished with exit code 0