数组:
劣:存储区间连续、占用内存严重、空间复杂度大、难增删
优:时间复杂度小、易查找
链表:
劣:时间复杂度大、难查找
优:存储区间离散、占用内存小、空间复杂度小、易增删
哈希表:
结合数组合链表的优点,存储区间离散占用内存小、空间复杂度小易增删、时间复杂度小易查找
哈希表可以理解为链表的数组,如图:
从图中可知哈希表是链表和数组的组合。上图2为一个长度为16的数组(哈希表长度一般为2的次幂数)。所存储的元素的下标一般由hash(key)%len(数组长度)获取,也即是key.hashCode()与数组的长度len取模获得。哈希表是以桶式存储元素。
哈希表的基本方法实现:
package HashMap; /** * 基本map接口 * author Gsan */ public interface Map <K,V> { //添加元素 public V put(K k,V v); //查找元素 public V get(K k); //删除元素 public V remove(K key); public interface Entry<K,V> { //获取键 public K getKey(); //获取值 public V getValue(); } }
哈希表的方法实现通过基本接口Map。
package HashMap; import java.util.ArrayList; import java.util.List; /** * author Gsan */ public class MyHashMap<K,V> implements Map<K,V> { //数组的初始化长度 private int DEFAULT_INITIAL_CAPACITY = 16; //阈值比例,负载因子 private double DEFAULT_LOAD_FACTOR = 0.75; private int defaultInitSize; private double defaultLoadFactor; //Map当中数组位置的数量 private int useSize; //数组 private Entry<K, V>[] table = null; //构造方法 public MyHashMap() { this(16, 0.75); } public MyHashMap(int defaultInitCapacity, double defaultLoadFactor) { if (defaultInitCapacity < 0) throw new IllegalArgumentException("illegal initial capacity:" + defaultInitCapacity); if (defaultLoadFactor <= 0 || Double.isNaN(defaultLoadFactor)) throw new IllegalArgumentException("illegal load factor:" + defaultLoadFactor); this.defaultInitSize = defaultInitCapacity; this.defaultLoadFactor = defaultLoadFactor; table = new Entry[this.defaultInitSize]; } }
数组的处世长度一般为2的次幂数。负载因子方便扩容,当数组达到阈值时(defaultInitSize*defaultLoadFactor)进行扩容。
//用hashCode取模计算下标 private int indexFor(K k, int length) { return hash(k.hashCode()) & (length - 1); }
//计算hashCode
private int hash(int hashCode) {
hashCode=hashCode^((hashCode>>>20)^(hashCode>>>12));
return hashCode^((hashCode>>>7)^(hashCode>>>4));
}
元素存储的位置下标是通过hash(key)%len计算的,length从0开始所以len=length-1。计算hashCode使用移位来计算hashcode。移位运算参考:http://www.cnblogs.com/hongten/p/hongten_java_yiweiyunsuangfu.html
https://blog.csdn.net/qq_34364995/article/details/80544465
@Override public V put(K k, V v) { if (useSize > defaultInitSize * defaultLoadFactor) { //扩容 up2Size(); } //通过key来计算存储位置下标 int index = indexFor((Integer) k, table.length); Entry<K, V> entry = table[index]; Entry<K, V> newEntry = new Entry<K, V>(k, v, null); if (entry == null) { table[index] = newEntry; useSize++; } else { Entry<K, V> temp; while ((temp = table[index]) != null) { temp = temp.next; } temp.next = newEntry; } return newEntry.getValue(); }
存储元素,先进行数组容量判断,是否超出阈值进行扩容(调用扩容函数),再调用计算存储下标方法计算下标index,将table[index]赋值第三方entry进行判断是否为null,
创建所需讯西湖元素对象newEntry,若entry为null,即位置为空,将newEntry放到table[index]即可,数组位置数量+1。若entry不为null时即该位置有元素,应存储到next至位置为null为止。
//扩容数组 private void up2Size() { Entry<K, V>[] newTable = new Entry[defaultInitSize * 2]; //将原table中的entry重新散列到新的table中 againHash(newTable); } //将原table中的entry重新散列到新的table中 private void againHash(Entry<K, V>[] newTable) { //数组里的对象封装到list中,包括同一位置链表结构 List<Entry<K, V>> entryList = new ArrayList<>(); for (int i = 0; i < table.length; i++) { if (table[i] == null) { continue; } findEntryByNext(table[i],entryList); } if(entryList.size()>0){ useSize=0; defaultInitSize=defaultInitSize*2; table=newTable; for(Entry<K,V> entry:entryList){ if(entry.next!=null){ entry.next=null; } put(entry.getKey(),entry.getValue()); } } } private void findEntryByNext(Entry<K,V> entry,List<Entry<K,V>> entryList){ if(entry!=null && entry.next!=null){ //这个entry对象以已经形成链表结构 entryList.add(entry); //递归,将链表中的entry实体都一次封装到entryList链表中 findEntryByNext(entry.next,entryList); }else{ entryList.add(entry); } }
数组超出阈值(defaultInitSize*defaultLoadFactor)进行扩容,将旧的数组链表重新散列到新的数组链表,先遍历table放到entryList,再遍历entry将key,value放到新数组的entryList。
测试:
package HashMap; /** * author Gsan */ public class HashMapTest { public static void main(String[] args){ Map<String,String> map=new MyHashMap<>(); for(int i=0;i<5;i++){ map.put("key"+i,"value"+i); } for(int i=0;i<5;i++){ System.out.println("key"+i+",value is:"+map.get("key"+i)); } } }
本文参考:https://blog.csdn.net/mark2when/article/details/71434713
https://www.cnblogs.com/holyshengjie/p/6500463.html