源码冲浪之HashMap

HashMap是咱们最经常使用到的集合之一,是java很是典型的数据结构。学习它的源码是很是只有必要的,咱们所要了解的并不单单是“HashMap不是线程安全的,HashTable是线程安全的,经过synchronized实现的。HashMap取值很是快”等等。java

了解hashmap必需要先对hashmap的存储结构有个了解
复制代码

它是属于数组及链表相结合的存储结构。如上图 x轴为数组,y轴为链表。 数组存储方式在内存地址是连续大小固定,一旦分配没法被其余引用占用,查询迅速,时间复杂度O(1),插入删除比较慢,时间复杂度为O(n)。 而链表存储方式则与数组相反,属于非连续性,大小非固定,插入及删除块,查询速度慢。 因此HashMap相对中庸。算法

1 HashMap的数据结构是啥?数据结构上存储的数据对象结构是啥? HashMap是一个存储数据对象<封装了K,V属性的对象>的集合,而这个集合是数组+链表类型的数据结构。编程

2 根据源码来分析hashMap内部的精髓 hash算法如何保证散列均匀冲突的解决方式数组

谈到hash 一般咱们jdk的equals在比较的时候就会使用hash算法,此算法会定位到对象的存储位置 具体hash的原理是: hash函数:找到存储过程 被重写的hashCode(key)安全

index=h=Hash(int hashCode)bash

(key.hashCode)&&length -1数据结构

length 2^n 经过h就能够找到数组下标的位置ide

例子以下: 2^4=16 length-1 =15 二进制为 01111 h返回的是 10101 数组上存储的位置为: 00101 【上下都是1才是1】函数

好处: 1 散列的范围被低位限制---》散列位置必定在咱们的索引范围(即length-1)以内。 2 低位的0若是越多 表明咱们散列的结果越固定。【想象一个如果非length-1就会发生 10000 低位0较多,致使散列结果几乎就是一致】,致使冲突越多,致使数组位置的利用率不高。学习

3 手写一个本身的hashmap集合

首先咱们会写一个本身的接口 面向接口编程【接口内部只保留最基础的put及get方法,而后定义内部接口】

而后就是写自定义的hashMap来继承此接口

定义参数而且补充了Spring门面模式的构造【便可以传参,若是不传参的话调用此类上面定义好的参数】

书写put方法

其中注意的是扩容的方法及原理

获取数组下标的方法而且重写hash算法

定义内部类,重写·Entry类

而后就是写get方法

代码清单以下:

MyMap接口

package com.epoint.HashMap;



/**

 * ,面向接口编程

 * 

 * @author lulf

 *

 * @param <K>

 * @param <V>

 */

public interface MyMap<K, V> {

 // MyMap 基本功能是快速存

 public V put(K k, V v);



 // 快速取

 public V get(K k);



 // 定义一个内部的接口

 public interface Entry<K, V> {



  public K getKey();



  public V getValue();

 }



}`

myhashMap
`package com.epoint.HashMap;



import java.util.ArrayList;

import java.util.List;



public class MyHashMap<K, V> implements MyMap<K, V> {



 // 定义数组大小 16

 // 结合着下面的扩容因子来解释一波:假如数组用了 4 usesize/defaulLenth =4/16=0.25 即便用率<0.75,不会扩容

 private static int defaulLenth = 1 << 4;



 // 扩容标准 所使用的useSize / 数组长度 >0.75

 // defaulAddSizeFactor 过大 形成扩容几率变低 存储小 可是就是存与取的效率下降

 // 0.9 有限的数组长度空间位置内会造成链表 在存与取值中都必须进行大量的遍历和判断(逻辑)

 // 太小 内存使用比较多,使用率不高,形成浪费

 private static double defaulAddSizeFactor = 0.75;



 // 使用数组位置的总数

 private int useSize;



 // 定义Map 骨架 只要 数组之一 数组

 private Entry<K, V>[] table = null;



 // Spring 门面模式运用

 public MyHashMap() {

  this(defaulLenth, defaulAddSizeFactor);

 }



 public MyHashMap(int length, double defaulAddSizeFactor) {

  if (length < 0)

   throw new IllegalArgumentException("参数不能为负数" + length);

  if (defaulAddSizeFactor <= 0 || Double.isNaN(defaulAddSizeFactor)) {

   throw new IllegalArgumentException("扩容标准必须是大于0的数字" + defaulAddSizeFactor);

  }

  this.defaulLenth = length;

  this.defaulAddSizeFactor = defaulAddSizeFactor;

  table = new Entry[defaulLenth];

 }



 @Override

 public V put(K k, V v) {

  // 存储是判断是否须要扩容

  if (useSize > defaulAddSizeFactor * defaulLenth) {

   up2Size();

  }

  // 获取数组下标

  int index = getIndex(k, table.length);

  Entry<K, V> entry = table[index];

  // 判断这个entry是否为空,为空意味着未被散列到

  if (entry == null) {

   table[index] = new Entry(k, v, null);

   useSize++;

  } else if (entry != null) {

   // 造成了链表结构

   table[index] = new Entry(k, v, entry);

  }

  return table[index].getValue();

 }



 // 寻找数组的下标

 private int getIndex(K k, int length) {

  int m = length - 1;

  int index = hash(k.hashCode()) & m;

  return index;

 }



 // 自定义写本身的hash算法

 private int hash(int hashCode) {

  hashCode = hashCode ^ ((hashCode >>> 20) ^ (hashCode >>> 12));

  return hashCode ^ ((hashCode >>> 7) ^ (hashCode >>> 4));

 }



 // 扩容

 private void up2Size() {

  // 如何扩容,无非就是新建一个2倍空间的数组

  Entry<K, V>[] newTable = new Entry[2 * defaulLenth];

  // 老数组的内容拿到新数组中

  againHash(newTable);



 }



 // 将老数组内容散列到新数组中

 private void againHash(MyHashMap<K, V>.Entry<K, V>[] newTable) {

  List<Entry<K, V>> entryList = new ArrayList<MyHashMap<K, V>.Entry<K, V>>();

  // for循环 即老数组内容被所有遍历到了entryList中

  for (int i = 0; i < table.length; i++) {

   if (table[i] == null) {

    continue;

   }

   // 继续找存到数组上的entry对象

   foundEntryByNext(table[i], entryList);

  }

  // 设置entryList

  if (entryList.size() > 0) {

   useSize = 0;

   defaulLenth = 2 * defaulLenth;

   for (Entry<K, V> entry : entryList) {

    if (entry.next != null) {

     entry.next = null;

    }

    put(entry.getKey(), entry.getValue());

   }

  }



 }



 private void foundEntryByNext(MyHashMap<K, V>.Entry<K, V> entry, List<MyHashMap<K, V>.Entry<K, V>> entryList) {

  // 造成了链表结构

  if (entry != null && entry.next != null) {

   entryList.add(entry);

   // 递归,不断地一层层取存entry

   foundEntryByNext(entry.next, entryList);

  } else {

   // 没有链表的状况

   entryList.add(entry);

  }



 }



 public int getUseSize() {

  return useSize;

 }



 @Override

 public V get(K k) {

  // hashCode (new Person(10,'llf'))--->hash---getindex--->最终位置

  int index = getIndex(k, table.length);

  if (table[index] == null) {

   throw new NullPointerException();

  }

  return findByValueByEqualKey(k, table[index]);

 }



 private V findByValueByEqualKey(K k, MyHashMap<K, V>.Entry<K, V> entry) {

  if (k == entry.getKey() || k.equals(entry.getKey())) {

   return entry.getValue();

  } else if (entry.next != null) {

   return findByValueByEqualKey(k, entry.next);

  }

  return null;

 }



 // 建立一个内部存储的对象类型

 class Entry<K, V> implements MyMap.Entry<K, V> {

  K k;

  V v;

  // 指向那被this挤压辖区的Entry对象

  Entry<K, V> next;



  public Entry(K k, V v, Entry<K, V> next) {

   this.k = k;

   this.v = v;

   this.next = next;

  }



  @Override

  public K getKey() {

   return k;

  }



  @Override

  public V getValue() {

   return v;

  }

 }
 
}
复制代码
相关文章
相关标签/搜索