HashMap是Map接口的实现类。
它的底层实现是:采用了哈希表。即:数组+单链表。
类似这样的:
写在前面:
1.HashMap中的主要变量:
Node<k,v>[] table; //桶数组 ,Node<K,V>类型的数组,里面的元素是链表,用于存放HashMap元素的实体
size; //记录键值对个数
loadFactor ; //负载因子
threshold ; //阈值,决定了HashMap何时扩容
public class Node<k,v> {
public int hash; //哈希值
public k key; //key值
public v value; // value值
Node<k,v> next; //指向下一个节点
}
2.主要的方法原理:
(1)构造方法。在JDK源码中当我们自定义HashMap初始容量大小时,构造函数并非直接把我们定义的数值当做HashMap容量大小,而是把该数值当做参数调用方法tableSizeFor,然后把返回值作为HashMap的初始容量大小。自己手写的则省略
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
此代码看到一篇博客讲解的比较好:https://blog.csdn.net/fan2012huan/article/details/51097331
(2)put方法。主要来存储数据元素到数组中。主要流程为:
在存储中还要考虑到扩容问题:
随着HashMap中元素的数量越来越多,发生碰撞的概率就越来越大,所产生的链表长度就会越来越长,这样势必会影响HashMap的速度,为了保证HashMap的效率,系统必须要在某个临界点进行扩容处理。
所谓的扩容就是,重新构建一个新的数组,遍历旧的数组取出所有元素,再装到新的数组中去。(!!重新计算每个元素在数组中的位置再装到新数组中去。即:重新计算每个数组哈希码(因为数组长度变了,哈希码也变了。))
static int indexFor(int h, int length) { return h & (length-1);}
JDK 1.6 当数量大于容量 * 负载因子即会扩充容量。(使用此方法)
JDK 1.7 初次扩充为:当数量大于容量时扩充;第二次及以后为:当数量大于容量 * 负载因子时扩充。
JDK 1.8 初次扩充为:与负载因子无关;第二次及以后为:与负载因子有关。其详细计算过程需要具体详解。原来旧的数组复制进去。
(3)get方法和put的方法比较类似。 获得外界传入的key值后,再通过算法得到哈希码,然后就去桶数组找就行了。
(4)其他方法原理相对比较简单省略。
附上自己写的源码:
package MyhashSet;
public class Node<k,v> {
public int hash; //哈希值
public k key; //key值
public v value; // value值
Node<k,v> next; //指向下一个节点
public Node(int hash, k key, v value) {
super();
this.hash = hash;
this.key = key;
this.value = value;
}
}
package MyhashSet;
public class MHashMap<k,v> {
private Node<k,v>[] table; //桶数组
private int size; //记录键值对个数
private double loadFactor ; //负载因子
private int threshold ; //阈值
//构造方法
//1.构造一个具有默认初始容量 (16) 和默认加载因子 (0.75) 的空 HashMap。
public MHashMap() {
this(16,0.75);
}
//2.构造一个带指定初始容量和默认加载因子 (0.75) 的空 HashMap。
public MHashMap(int capacity) {
this.table = new Node[capacity];
this.loadFactor = 0.75;
}
//3.构造一个带指定初始容量和加载因子的空 HashMap。
public MHashMap(int capacity,double loadFactor) {
this.table = new Node[capacity];
this.loadFactor = loadFactor;
}
//从此映射中移除所有映射关系。
public void clear() {
if(table!=null && size > 0) {
for(int i = 0;i < table.length;i++) {
table[i] = null;
size = 0;
}
}
}
//由哈希码得到哈希值
public int myhash(int m,int length){
return m&(length-1); //直接位运算
}
//返回指定键所映射的值;如果对于该键来说,此映射不包含任何映射关系,则返回 null。
public v get(k key) {
int hash = myhash(key.hashCode(),table.length); //由key值获得哈希值
Node newnode = table[hash];
v vaule = null;
while(newnode!=null) {
if(newnode.key.equals(key)) {
vaule = (v) newnode.value;
break;
}
else {
newnode = newnode.next;
}
}
return vaule;
}
//存储。如果该映射以前包含了一个该键的映射关系,则旧值被替换。
public void put(k key,v value) {
int hash = myhash(key.hashCode(),table.length);
Node<k,v> newnode = new Node(hash,key,value) ;//新节点初始化
Node temp = table[hash]; //相当于遍历指针
boolean judge = false;//用来记录key值是不是重复了
Node temporary = null;//临时用来保存最后一个结点
resize(); //数组扩容检测
if(temp == null)
{
table[hash] = newnode;
size++;
}
else
{
while(temp!=null)
{
if(temp.key.equals(key))
{
temp.value = value;
size++;
judge = true;
break;
}
else
{
temporary = temp;
temp = temp.next;
}
}
if(!judge)
{
temporary.next = newnode;
size++;
}
}
}
//返回此映射中的键-值映射关系数。
public int size() {
return size;
}
//如果此映射不包含键-值映射关系,则返回 true。
public boolean isEmpty() {
return size == 0;
}
//如果此映射包含对于指定键的映射关系,则返回 true。
public boolean containsKey(k key) {
int hash = myhash(key.hashCode(),table.length); //由key值获得哈希值
Node newnode = table[hash];
boolean judge = false;
while(newnode!=null) {
if(newnode.key.equals(key)) {
judge = true;
break;
}
else {
newnode = newnode.next;
}
}
return judge;
}
//从此映射中移除指定键的映射关系(如果存在)。
public void remove(k key) {
int hash = myhash(key.hashCode(),table.length); //由key值获得哈希值
Node temp = table[hash];//定位当前结点
Node front = table[hash];//永远指向temp前面一个结点
int n = 0;
while(temp!=null) {
n++;
if(temp.key.equals(key)) {
//找到此结点,删除
if(n==1)//首节点
{
table[hash] = temp.next;
size--;
break;
}
else if(temp.next == null)//尾结点
{
front.next = null;
}
else//位于中间的结点
{
front.next = temp.next;
temp.next = null;
}
}
else {//没有找到相同的
if(n==1)
{
temp = temp.next;
}
else
{
front = temp;
temp = temp.next;
}
}
}
}
//扩容操作函数
public void resize(){
threshold = (int)(loadFactor * table.length); //阀值计算公式
if(size > threshold) {
Node oldtablenode;//相当于一个指针(用于旧数组)
Node newtablenode;//(用于新数组)
Node newjudge=null;//(新数组最后一位标记)
Node[] newtable = new Node[table.length<<1]; //新数组扩大二倍
//遍历旧的数组取出所有元素,再装到新的数组中去。(!!重新计算每个元素在数组中的位置)
for(int i = 0; i<table.length;i++) {
while(table[i]!=null)
{
oldtablenode = table[i];
int hash = myhash(oldtablenode.key.hashCode(),newtable.length);//计算在新的数组中的位置
//存放旧数组一个元素
Node oldelement = new Node(hash,oldtablenode.key , oldtablenode.value);
//将取出的元素放入新数组
newtablenode = newtable[hash];
if(newtablenode == null)
{
newtable[hash] = oldelement;
}
else
{
while(newtablenode!=null)//找到最后一个结点
{
newjudge = newtablenode;
newtablenode = newtablenode.next;
}
newjudge.next = oldelement; //把旧数组元素插到最后一个结点上
}
oldtablenode = oldtablenode.next; //再移向下一个结点(旧数组)
}
}
table = newtable; //table数组进行改变
}
}
//改写toString方法
@Override
public String toString() {
//显示效果:{1:a ,2:b}
//定义一个数组对象
StringBuilder sb = new StringBuilder("{");
//先找到数组,再找对应的单链表
for(int i = 0;i<table.length;i++)
{
Node temp = table[i];
while(temp!=null)
{
//将指定的字符串追加到此字符序列
sb.append(temp.key+":"+temp.value+", ");
temp = temp.next;
}
}
//指定索引处的字符设置为ch
sb.setCharAt(sb.length()-1, '}');
//返回表示此序列中的数据的字符串
return sb.toString();
}
}