HashMap学习
基于JDK 1.7版本学习源码实现(1.7和1.8的区别)
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
继承了AbstractMap,主要是实现map接口,当前HashMap也能实现克隆及序列化
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
默认初始容量:初始容量即可以传参给定,也可以给默认值,主要作用是对数据大小进行初始化
static final int MAXIMUM_CAPACITY = 1 << 30;
容量最大值:容量的大小是对数组设定上限
static final float DEFAULT_LOAD_FACTOR = 0.75f;
加载因子:在扩容时使用(使用方法put),默认值大小是0.75
static final Entry<?,?>[] EMPTY_TABLE = {};
底层数据是数组,数组的数据类型是Entry
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
int hash;
}
entry的数据类型包含存储的key-value数据,hash,还有entry类型的next属性,这可以看出entry数据是要通过链表来组织连接
底层数据结构就是:数组+链表
threshold = (int) Math.min(capacity * loadFactor
threshold扩容阈值:计算方式如上:容量*加载因子得到
1、成员变量
// 默认的初始容量是16,必须是2的幂。
static final int DEFAULT_INITIAL_CAPACITY = 16;
// 最大容量(必须是2的幂且小于2的30次方,传入容量过大将被这个值替换)
static final int MAXIMUM_CAPACITY = 1 << 30;
// 默认加载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 存储数据的Entry数组,长度是2的幂。
// HashMap是采用拉链法实现的,每一个Entry本质上是一个单向链表
transient Entry[] table;
// HashMap的大小,它是HashMap保存的键值对的数量
transient int size;
// HashMap的阈值,用于判断是否需要调整HashMap的容量(threshold = 容量*加载因子)
int threshold;
// 加载因子实际大小
final float loadFactor;
// HashMap被改变的次数,由于HashMap非线程安全,在对HashMap进行迭代时,
如果期间其他线程参与导致HashMap的结构发生变化了(比如put,remove等操作),需要抛出异常
transient volatile int modCount;
2、构造方法
//指定“容量大小”和“加载因子”的构造函数
public HashMap(int initialCapacity, float loadFactor) {
//容量小于0则抛异常
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
//指定最大容量只能是MAXIMUM_CAPACITY
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
// Find a power of 2 >= initialCapacity
//找出“大于”initialCapacity的最小的2的幂
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
//设置加载因子
this.loadFactor = loadFactor;
//设置阈值,阈值=容量乘以加载因子,当HashMap中存储数据的数量达到threshold时,
//就需要将HashMap的容量增加
threshold = (int)(capacity * loadFactor);
//创建Entry数组,用来保存数据
table = new Entry[capacity];
init();
}
//指定容量大小的构造函数
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
//默认构造函数
public HashMap() {
//默认加载因子
this.loadFactor = DEFAULT_LOAD_FACTOR;
//默认阈值
threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
//默认Entry数组的大小
table = new Entry[DEFAULT_INITIAL_CAPACITY];
init();
}
//包含子Map的构造函数
public HashMap(Map<? extends K, ? extends V> m) {
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
//将m中的全部元素逐个添加到HashMap中
putAllForCreate(m);
}
3、put方法(添加元素)
**public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
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++;
addEntry(hash, key, value, i);
return null;
}**
4、put添加元素过程中
1、判断table是否为空数组,是则创建数组(注意:threshold值得改变)
2、key为null的添加操作特殊处理,将该key对应的entry实体放入table[0]的位置(存在该key为null的实体则实体value值覆盖操作)
3、哈希过程通过key进行哈希
4、通过indexfor方法来定位数据存储的索引位置
5、遍历该位置的链表,判断是否存在该key的entry实体(k.hashcode == equals),有则将value替换
6、将该元素插入到对应索引的链表中
扩容(2*table.length)方式扩容(扩容时机)
插入位置(第一个位置)
hash算法:[https://www.cnblogs.com/killbug/p/4560000.html]
5、get获取元素流程
注意点:
哪些情况返回null
正常处理流程:
先对key进行哈希,并找到key存在的数组table的索引位置
对该索引位置的链表进行遍历,判断key是否相等(key的Hashcode,== equals)
remove删除元素过程
1、数组元素size为0,即不存在元素,直接返回
2、对key进行哈希,找到在数组table中的索引位置
3、对该位置的链表进行从头开始遍历
4、prev,e,next定义变量,找出key的位置
分两种情况:
key对应的entry实体在链表头节点,直接将该节点的next的置为头结点
entry实体在链表中,该判断结点的后一个节点的next直接指向该节点的next节点
containsValue判断key是否存在方法
containsKey判断value是否存在方法
两者区别点:
1、判断相等方式不同:containsKey中key需要比较Hashcode、== equals
containsValue中value直接equals
2、遍历数组大小不同:containsKey中通过key找到该key存在的数组索引位置,遍历该索引位置的链表即可
containsValue中需要对该链表所有数组的所有链表全部遍历
6、手动实现HashMap
import java.util.Arrays;
/**
* Description: 自定义hashmap
*
* @Author jr
* @Data 2018/10/28
*/
public class MyHashMap {
/**
*内部类,Entry 用来存储key value数据,和Entry类型的next节点.
*/
class Entry{
int key;
String value;
Entry next;
public Entry(int k, String v,Entry next){
this.key = k;
this.value = v;
this.next = next;
}
}
// 用来存储Entry的数组
Entry[] table;
// 记录table中每个索引位置的链表长度
int[] linkLen;
//各链表最长长度的限定
int maxLen;
// 默认构造
public MyHashMap(){
this(16);
}
// 带参数构造
public MyHashMap(int size){
this.table = new Entry[size];
this.linkLen = new int[size];
maxLen = size;
}
/**
* 哈希函数
*/
public int hash(int key,Entry[] table){
if(key < 0){
throw new IllegalArgumentException("wrong key!");
}
return key % table.length;
}
/**
* 使用key和value插入的方法
*/
public String put(int key,String value){
return put(key,value,this.table,this.linkLen);
}
/**
* 内部使用的插入方法
*/
private String put(int key,String value,Entry[] table,int[] linkLen){
//1.先得到在数组中的索引位置
int hash = hash(key,table);
//2.得到索引位置的第一个节点,供后面遍历
Entry entry = table[hash];
//3.对该节点的链表进行遍历.如果找到了key对应的节点,对value进行覆盖
for (;entry != null;entry = entry.next) {
if(key == entry.key){
String oldValue = entry.value;
entry.value = value;
return oldValue;
}
}
// 考虑扩容
if(linkLen[hash] == maxLen){
resize();
}
//4.如果走到这一步,上面的肯定没有return,表示没有找到key,所以要插入到头节点
Entry e = new Entry(key,value,table[hash]);
table[hash] = e;
linkLen[hash]++;
return table[hash].value;
}
/**
* 扩容原数组长度并且将里面的数据重新哈希.
*/
private void resize(){
//1.先创建一个新数组,长度为原来数组的二倍
Entry[] newTable = new Entry[table.length*2];
//1.定义一个新的数组,来记录新table的各个节点链表长度.
int[] newLinkLen = new int[table.length*2];
//1.更改每个链表最长长度也乘以2
maxLen = table.length*2;
//2.对原来table进行循环遍历,把每个节点都重新哈希插入到新的数组中.
for (int i = 0; i < table.length; i++) {
Entry entry = table[i]; //得到原table的每个位置的头节点;
while (entry != null){
int key = entry.key;
String value = entry.value;
put(key,value,newTable,newLinkLen);
entry = entry.next;
}
}
//3.数据重新插入完后,将新数组地址赋值给原数组.
table = newTable;
linkLen = newLinkLen;
}
public String remove(int key){
if(key < 0){
throw new IllegalArgumentException("wrong key!");
}
//找到索引位置
int hash = hash(key,table);
if(linkLen[hash] == 0){
throw new IllegalArgumentException("No such key!");
}
Entry entryPre = table[hash]; //父节点
Entry entry = table[hash]; //遍历的节点
while (entry != null){
if(entry.key == key){
if(entryPre == entry){
table[hash] = entry.next;
}else{
entryPre.next = entry.next;
return entry.value;
}
linkLen[hash]--;
}
entryPre = entry;
entry = entry.next;
}
return null;
}
public static void main(String[] args) {
MyHashMap myHashMap = new MyHashMap(3);
myHashMap.put(1,"a");
myHashMap.put(4,"b");
myHashMap.put(7,"c");
System.out.println(myHashMap.linkLen[1]);
myHashMap.put(10,"d");
System.out.println(myHashMap.linkLen[1]);
}
}