java进阶
首发于java进阶

Hash表的前世今生

Hi,HashMap~

1、介绍

1.1 在计算机中数据的存储结构的根本方法只有两种:数组和链表。这两种存储方法各有各自的优点和劣势。

1.1.1 数组的优势是数组长度是固定的,它的每一个下标都指向着唯一的一个值,所以知道该元素在数组的索引,就能在数组中找到一个元素。但长度固定同时又成了他的缺点,当用一个数组来存数据时,需要指定长度,但又不确定数据是否就能被该数组全部装下,一旦超出了数组的长度就会报错。同时也没有必要定义一个长度足够长的数组,因为这样会占用很大一部分的内存,造成计算机的资源浪费。而且当删除不必要的元素时,该索引位置的内存空间是不能被释放的。

1.1.2 链表的优势在于存储数据时,只需将数据不断地接在节点上,这就意味着链表的长度没有限制,所以很方便对链表中的数据进行插入,删除等操作。但由于链表的每一个数据都是环环相扣的,所以当需查询某一个元素时就不得不遍历所有的节点。这时效率就会变低了。

~~~所以我们是否能够找到一个综合了数组和链表各自的优点的一种数据存储方式呢?答案就是哈希算法。

1.1.3 HashMap是一个用”KEY”-“VALUE”来实现数据存储的类。可以用一个”key”去存储数据。当想获得数据的时候,就可以通过”key”去得到数据。

像不像字典。

1.2那么HashMap的名字从何而来呢?

-其实HashMap的由来是基于Hasing技术(Hasing),Hasing就是将很大的字符串或者任何对象转换成一个用来代表它们的很小的值,这些更短的值就可以很方便的用来方便索引、加快搜索。

1.3在讲解HashMap的之前还需要提到一个知识点 (我在写hash表的时候疑惑了很久的点)
1.3.1 hash值与hashcode:原来在Java中每个对象都有一个hashcode()方法用来返回该对象的 hash值(映射值)。HashMap中将会用到对象的hashcode()方法来获取对象的hash值。返回的是int类型的散列值。

1.3.2 由hashcode得到h值,再由h值得优化hash值:只要hash表的函数映射比较松散,一般碰撞会比较少,但是散列值分布再松散,要是只取最后几位的话,碰撞也会很多,所以这里就要用到“扰动函数”(jdk)。

/**
	 * 直接使用JDK8 散列值优化函数
	 * @param key关键字
	 * @return key优化后的散列值
	 */
	public int hash(Object key) {
		int h;
		return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);// key.hashCode()函数调用的是key键值类型自带的哈希函数,返回int型散列值。
	}

>>16,自己的高16位和低16位做抑或,混合后的低位加上了高位的特征,所以保留了高位。

1.3.3.取模:但是理论上散列值是一个int型,如果直接拿散列值作为下标访问HashMap主数组的话,就要先考虑一个范围的问题:二进制32位带符号的int值得范围是-2147483648~2147483648.很大的范围,近40亿的映射空间,长度为40亿的数组,内存放得下吗?no。所以不能直接用这个散列值,在用之前我们要对hash值取模运算,用余数来访问数组下标。

	public int indexFor(int hash, int INT_SIZE) {
		return hash & (INT_SIZE - 1);
	}

2、内部介绍

hash表像一个火车,火车里坐了很多旅客,每节一个旅客,他们手里拿着自己的信息和他下一节车厢的旅客的名片。

2.1、和上图显示的一样,HashMap内部包含了一个Entry类型的数组container, container里的每一个数据都是一个Entry对象。

2.2、再来看container里面存储的Entry类型,Entry类里包含了hash变量,key,value 和另外一个Entry对象。为什么要有一个Entry对象呢?如果可爱的读者们看了我上一篇关于链表的博客,就会知道这是一个链式结构。通过我找到你,你再找到他。

2.3、那么Entry是一个单链表结构的意义又是什么呢?在我们了解了HashMap的存储过程之后,你就会很清楚了,接着让我们来看HashMap怎么工作的。

3、HashMap的处理过程

那么看了hash表的处理过程后,要实现hash表就是要解决这三个关键问题

1 . Hash构造函数的方法


2. Hash处理冲突方法


3. Hash查找过程

4.构建hash表

4.1 Hash构造函数的方法

实际造表时,采用什么方法取决于建表的关键字集合的范围和形态,以及哈希表长度(哈希地址范围),总的原则是使产生冲突的可能性降到尽可能地小。

  • 直接定址法:哈希函数为关键字的线性函数,H(key) = key 或者 H(key) = a ´ key + b
  • 数字分析法:假设关键字集合中的每个关键字都是由 s 位数字组成 (u1, u2, …, us),分析关键字集中的全体,并从中提取分布均匀的若干位或它们的组合作为地址。
  • 平方取中法: 以关键字的平方值的中间几位作为存储地址。
  • 折叠法:将关键字分割成若干部分,然后取它们的叠加和为哈希地址。
  • 除留余数法: 设定哈希函数为:H(key) = key MOD p ( p≤m ),其中 m为表长。
  • 随机数法 :设定哈希函数为:H(key) = Random(key)其中,Random 为伪随机函数。

4.2 Hash处理冲突方法

“处理冲突” 的实际含义是:为产生冲突的关键字寻找下一个哈希地址。

  • 开放定址法: 关键字地址 H(key) 求得一个地址序列: H0, H1, …, Hs 1≤s≤m-1,Hi = ( H(key) +di ) MOD m,其中: i=1, 2, …, s,H(key)为哈希函数;m为哈希表长;
  • 再哈希法:构造若干个哈希函数,当发生冲突时根据另一个哈希函数计算下一个哈希地址,直到冲突不再发生。即:Hi=Rhi(key) i=1,2,……k,其中:Rhi——不同的哈希函数,特点:计算时间增加
  • 挂链法: 将所有哈希地址相同的记录都链接在同一链表中。

4.3介绍完后,要确定用什么方法来构建hash表和处理冲突

这里我用到的是除留余数构建,挂链罚处理冲突。

5.实现hash表的基本思路

由于hash表其实是一个数组+链表的结合体,所以要实现它必须结合数组和链表的知识构建。下面是本人对构建hash表的基本思路。

1.我们需要指定一个具有初始容量的数组,这个数组的每一个元素实际上是一个链表。

        private int size;// 当前容量
	private static int INT_SIZE = 16;// 默认容量
	private static float LOAD_FACTOR = 0.75f;// 装载因子
	private int sumMax;// 实际存储的数据的最大量=size*LOAD_FACTOR
	private Entry<K, V>[] container;// 实际用于存数据的数组 Returns the key corresponding
 // 使用默认参数的构造器  (jdk)
    public MyHashMap() {  
        this(INT_SIZE, LOAD_FACTOR);  
    }  							// to this entry. 此时相当于一张hash表

2.我们还需要一个节点类

public class Entry <K,V>{

	private K key;//关键字
	private V value;//数据域
	private Entry<K,V> next;//下一个节点
	private Entry<K,V> last;//上一个节点
	public Entry<K, V> getLast() {
		return last;
	}
	public void setLast(Entry<K, V> last) {
		this.last = last;
	}
	private int hash;//hash函数的值,得到hashcode(地址),hash值再生成索引,这里用了indexFor()方法生成索引是因为:hash值一般都很大,不适合数组的
	public Entry(K key, V value, int hash) {
	
		this.key = key;
		this.value = value;
		this.hash = hash;
	}………………
………………

其中K,V就是键值对,所以用过hashMap的人们都知道。

3.接下来就是最关键的步骤了,即map中添加元素的方法。

其实思路也很简单,我们可以用用一张图很直观的看到:

3.1添加

3.1.1向hash表(数组)添加元素

public boolean put(K key, V value) {
		int hash = hash(key);

		// 创建一个包含hash,key,value信息的Entry类,作为数组的一个节点
		Entry<K, V> entrycode = new Entry<K, V>(key, value, hash);// 注意
																	// entrycode和myhashnap,entry是什么
		// 判断是否要重建
		if (size == sumMax) {
			rehash(container.length * 5);
		}
		if (add(container, entrycode)) {
			size++;
			return true;
		}

		return false;
	}

3.1.2实际是在数组每一个索引位置添加链表

考虑几个问题:

当有与该插入元素相同的元素存在于链表的时候,覆盖

当没有与该插入元素相同的元素存在于链表的时候:

1.该索引位置没有链表,新建链表,该元素作为头节点

2该索引位置有链表,该元素挂在表尾

public boolean add(Entry[] container, Entry<K, V> entrycode) {
		// 找到下标位置
		int index = indexFor(entrycode.getHash(), container.length);
		// 找到该下标对应元素
		Entry<K, V> entry = container[index];
		if (entry != null) {
			// 遍历整个链表判断是否存在entry
			while (entry != null) {
				// 判断相等的条件时应该注意,除了比较地址相同外,引用传递的相等用equals()方法比较
				// 1.1若相等则不存,返回false
				if ((entrycode.getKey() == entry.getKey() || entrycode.getKey()
						.equals(entry.getKey()))
						&& (entrycode.getValue() == entry.getValue() || entrycode
								.getValue() == entry.getValue())
						&& entrycode.getHash() == entry.getHash())
					return false;
				// 1.2.不相等则比校下一个节点
				else if (entrycode.getKey() != entry.getKey()
						&& entrycode.getValue() != entry.getValue()) {
					if (entry.getNext() == null)
						break;// 到达表尾,结束循环

					else
						entry = entry.getNext();// 继续遍历下一个节点
				}
				// 1.3.如果遍历完了都没有这个节点则,把该元素挂在表尾
				add2only(entrycode, entry);

			}

		}
		// 2.如果数组中的索引处不存在链表,则生成新链表
		setfirstEntry(entrycode, index, container);

		return true;
	}

3.2查询

是否存在该元素,即索引位置是否有节点

public V getValue(K key) {
		Entry<K, V> entry = null;
		// 1.计算K的hash值
		int hash = key.hashCode();
		// 2.根据hash值找到下标
		int index = indexFor(hash, container.length);
		// 3.根据index找到链表
		entry = container[index];
		// 4.若链表为空,返回null
		if (entry == null) {
			return null;
		}
		// 5.1.若不为空,遍历链表,比较key是否相等,如果key相等,则返回该value
		while (entry != null) {
			if (key == entry.getKey() || entry.getKey().equals(key)) {
				return entry.getValue();
			}
			entry = entry.getNext();
		}
		// 5.2. 如果遍历完了不相等,则返回空
		return null;
	}

3.3移除

是否存在该元素,即索引位置是否有节点

public boolean remove(K key) {
		int hash = key.hashCode();
		int index = indexFor(hash, container.length);// 获取要搜索的索引
		// 如果哈希码一样的话则进入下一部 否则hash集合中就没有要找的元素
		if (container[index].getKey().equals(key)) {
			container[index] = null;// 如果要删除的元素是链表头
			return true;
		} else {
			Entry<K,V> next = container[index].getNext();// 获得下一个节点
			while (next != null) {// 遍历该条子链表
				if (next.getKey().equals(key)) {
					Entry<K,V> next2 = next.getNext();
					// 如果next不是最后一个节点
					if (next2 != null) {
						// 那么被删除的节点的父节点的子节点应该指向其子节点
						next.getLast().setNext(next2);
						return true;
					} else {// 如果next是最后一个节点
						next.getLast().setNext(null);
					}
				}
				next = next.getNext();
			}
		}
		return false;

	}
}

4..重建hashMap。这一步很重要,它是hash表的一个显著特点,因为关乎hash表的效率问题。首先要有一个控制量,就是hashMap的加载因子。jdk类库里面使用的0.75f,当然这个可以随自己的需要而定。当这个hash表中的数据量超过限度时,比如maxSize = init_Capacity*0.75f, 当size>sumMax时,就rehash()。

private void rehash(int newSize) {
		// 1.声明新的数组
		Entry[] newcontainer = new Entry[newSize];
		// 2.重新定义hash表最大可以存储的数据总量
		sumMax = (int) (newSize * LOAD_FACTOR);
		// 3. 遍历所有元素,每个元素在存一遍
		for (int i = 0; i < container.length; i++) {
			Entry<K, V> entry = container[i];
			// 创建新链表
			while (entry != null) {
				setfirstEntry(entry, i, newcontainer);
				entry = entry.getNext();

			}
		}
		// 4.所有新链表指向新数组,形成新hash表
		container = newcontainer;
	}

如此就实现了一个hash表

写好后可以建一个测试类,测试自己的hashmap和系统的hashmap,比较一下性能

以下是全部代码

/**
 * 泛型Entry类,链式结构存储数据
 * @author 慧
 *
 * @param <K>关键字
 * @param <V>数据域
 */
public class Entry <K,V>{

	private K key;//关键字
	private V value;//数据域
	private Entry<K,V> next;//下一个节点
	private Entry<K,V> last;//上一个节点
	public Entry<K, V> getLast() {
		return last;
	}
	public void setLast(Entry<K, V> last) {
		this.last = last;
	}
	private int hash;//hash函数的值,得到hashcode(地址),hash值再生成索引,这里用了indexFor()方法生成索引是因为:hash值一般都很大,不适合数组的
	public Entry(K key, V value, int hash) {
	
		this.key = key;
		this.value = value;
		this.hash = hash;
	}
	public void setHash(int hash) {
		this.hash = hash;
	}
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((key == null) ? 0 : key.hashCode());
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Entry other = (Entry) obj;
		if (key == null) {
			if (other.key != null)
				return false;
		} else if (!key.equals(other.key))
			return false;
		return true;
	}
	public void setNext(Entry<K, V> next) {
		this.next = next;
	}
	public K getKey() {
		return key;
	}
	public V getValue() {
		return value;
	}
	public Entry<K, V> getNext() {
		return next;
	}
	public int getHash() {
		return hash;
	}
	public void setKey(K key) {
		this.key = key;
	}
	public void setValue(V value) {
		this.value = value;
	}
	
	
	
}

/**
 * 实现哈希表 用一个泛型类作为节点 , 然后将该节点作为一个数组的元素, 所有的数据将存储在这个数组中, 即挂链式存储方法
 * 
 * @author 慧
 * 
 * @param <K>关键字
 * @param <V>数据域
 */
public class MyHashMap<K, V> {

	private int size;// 当前容量
	private static int INT_SIZE = 16;// 默认容量
	private static float LOAD_FACTOR = 0.75f;// 装载因子
	private int sumMax;// 实际存储的数据的最大量=size*LOAD_FACTOR
	private Entry<K, V>[] container;// 实际用于存数据的数组 Returns the key corresponding
									// to this entry. 此时相当于一张hash

    // 自己设置容量和装载因子的构造器  ,直接使用jdk的
    public MyHashMap(int init_Capaticy, float load_factor) {  
        if (init_Capaticy < 0)  
            throw new IllegalArgumentException("Illegal initial capacity: "  
                    + init_Capaticy);  
        if (load_factor <= 0 || Float.isNaN(load_factor))  
            throw new IllegalArgumentException("Illegal load factor: "  
                    + load_factor);  
        this.LOAD_FACTOR = load_factor;  
        sumMax = (int) (init_Capaticy * load_factor);  
        container = new Entry[init_Capaticy];  
    }  
  
    // 使用默认参数的构造器  (jdk)
    public MyHashMap() {  
        this(INT_SIZE, LOAD_FACTOR);  
    }  

	/**
	 * 直接使用JDK8 散列值优化函数
	 * 
	 * @param key
	 *            关键字
	 * @return key优化后的散列值
	 */
	public int hash(Object key) {
		int h;
		return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);// key.hashCode()函数调用的是key键值类型自带的哈希函数,返回int型散列值。
	}

	/**
	 * 根据hash码,容器数组的长度,计算该哈希码在容器数组中的下标值
	 * ,散列值得范围很大(40亿),HashMap扩容前的大小是16,散列值不能放入数组使用, 所以先对数组的长度取模运算,得到余数作为下标值
	 * 
	 * @param hash
	 *            key优化后的散列值
	 * @param INT_SIZE
	 *            数组长度
	 * @return 该哈希码在容器数组中的下标值
	 */
	public int indexFor(int hash, int INT_SIZE) {
		return hash & (INT_SIZE - 1);

	}

	/**
	 * 把成分(链表)加入到哈希表
	 * 
	 * @param key
	 *            关键字
	 * @param value
	 *            数据域
	 * @return
	 */
	public boolean put(K key, V value) {
		int hash = hash(key);

		// 创建一个包含hash,key,value信息的Entry类,作为数组的一个节点
		Entry<K, V> entrycode = new Entry<K, V>(key, value, hash);// 注意
																	// entrycode和myhashnap,entry是什么
		// 判断是否要重建
		if (size == sumMax) {
			rehash(container.length * 5);
		}
		if (add(container, entrycode)) {
			size++;
			return true;
		}

		return false;
	}

	/**
	 * 将指定元素添加到哈希表container中
	 * 添加时判断该索引位置是否为空,key和hash是否相等,即该元素是否存在于hash表中,存在返回faulse 添加成功返回true
	 * 
	 * @param container
	 *            数组容器
	 * @param entrycode
	 *            链表节点
	 * @return
	 */
	public boolean add(Entry[] container, Entry<K, V> entrycode) {
		// 找到下标位置
		int index = indexFor(entrycode.getHash(), container.length);
		// 找到该下标对应元素
		Entry<K, V> entry = container[index];
		if (entry != null) {
			// 遍历整个链表判断是否存在entry
			while (entry != null) {
				// 判断相等的条件时应该注意,除了比较地址相同外,引用传递的相等用equals()方法比较
				// 1.1若相等则不存,返回false
				if ((entrycode.getKey() == entry.getKey() || entrycode.getKey()
						.equals(entry.getKey()))
						&& (entrycode.getValue() == entry.getValue() || entrycode
								.getValue() == entry.getValue())
						&& entrycode.getHash() == entry.getHash())
					return false;
				// 1.2.不相等则比校下一个节点
				else if (entrycode.getKey() != entry.getKey()
						&& entrycode.getValue() != entry.getValue()) {
					if (entry.getNext() == null)
						break;// 到达表尾,结束循环

					else
						entry = entry.getNext();// 继续遍历下一个节点
				}
				// 1.3.如果遍历完了都没有这个节点则,把该元素挂在表尾
				add2only(entrycode, entry);

			}

		}
		// 2.如果数组中的索引处不存在链表,则生成新链表
		setfirstEntry(entrycode, index, container);

		return true;
	}

	/**
	 * 在数组索引处建立新链表
	 * 
	 * @param entrycode
	 *            新节点
	 * @param index
	 *            索引号
	 * @param container
	 *            hash表
	 */
	private void setfirstEntry(Entry<K, V> entrycode, int index,
			Entry[] container) {
		// 1.判断当前容量是否超出范围,如果超,调用扩容方法
		if (size > sumMax)
			rehash(container.length * 4);
		container[index] = entrycode;
		// 因为生成的是新链表所以要把表后面的节点去掉
		entrycode.setNext(null);

	}

	/**
	 * 在链表尾添加节点
	 * 
	 * @param entrycode
	 *            添加的元素
	 * @param entry
	 *            链表
	 */
	private void add2only(Entry<K, V> entrycode, Entry<K, V> entry) {
		// 1.判断当前容量是否超出范围,如果超,调用扩容方法
		if (size > sumMax)
			rehash(container.length * 4);
		// 2.没有超出范围
		entry.setNext(entrycode);// entry指向下一个新节点entrycode

	}

	/**
	 * 构建一个新的hash表
	 * 
	 * @param newSize
	 *            新的大小
	 */
	private void rehash(int newSize) {
		// 1.声明新的数组
		Entry[] newcontainer = new Entry[newSize];
		// 2.重新定义hash表最大可以存储的数据总量
		sumMax = (int) (newSize * LOAD_FACTOR);
		// 3. 遍历所有元素,每个元素在存一遍
		for (int i = 0; i < container.length; i++) {
			Entry<K, V> entry = container[i];
			// 创建新链表
			while (entry != null) {
				setfirstEntry(entry, i, newcontainer);
				entry = entry.getNext();

			}
		}
		// 4.所有新链表指向新数组,形成新hash表
		container = newcontainer;
	}

	/**
	 * 从hash表中取元素
	 * 
	 * @param key
	 *            关键字
	 * @return 数据域
	 */
	public V getValue(K key) {
		Entry<K, V> entry = null;
		// 1.计算K的hash值
		int hash = key.hashCode();
		// 2.根据hash值找到下标
		int index = indexFor(hash, container.length);
		// 3.根据index找到链表
		entry = container[index];
		// 4.若链表为空,返回null
		if (entry == null) {
			return null;
		}
		// 5.1.若不为空,遍历链表,比较key是否相等,如果key相等,则返回该value
		while (entry != null) {
			if (key == entry.getKey() || entry.getKey().equals(key)) {
				return entry.getValue();
			}
			entry = entry.getNext();
		}
		// 5.2. 如果遍历完了不相等,则返回空
		return null;
	}

	/**
	 * remove方法 从map中移除一个元素
	 */
	public boolean remove(K key) {
		int hash = key.hashCode();
		int index = indexFor(hash, container.length);// 获取要搜索的索引
		// 如果哈希码一样的话则进入下一部 否则hash集合中就没有要找的元素
		if (container[index].getKey().equals(key)) {
			container[index] = null;// 如果要删除的元素是链表头
			return true;
		} else {
			Entry<K,V> next = container[index].getNext();// 获得下一个节点
			while (next != null) {// 遍历该条子链表
				if (next.getKey().equals(key)) {
					Entry<K,V> next2 = next.getNext();
					// 如果next不是最后一个节点
					if (next2 != null) {
						// 那么被删除的节点的父节点的子节点应该指向其子节点
						next.getLast().setNext(next2);
						return true;
					} else {// 如果next是最后一个节点
						next.getLast().setNext(null);
					}
				}
				next = next.getNext();
			}
		}
		return false;

	}
}

import java.util.HashMap;

/**
 * 测试MyHashMap,并且和系统的hash表做对比
 * @author 慧
 *
 */
public class MyHashMapTest {
		    public static void main(String[] args) {  
		        //自己的hashmap  
		        MyHashMap<String, String> my = new MyHashMap<String, String>();  
		        System.out.println("测试自己写的hash表:");  
		        Long pBeginTime=System.currentTimeMillis();  //记录当前插入开始的时间,返回当前时间与协调世界时 1970 年 1 月 1 日午夜之间的时间差(以毫秒为单位)
		        //加入元素
		        for(int i=0;i<1000000;i++){    
		        my.put(""+i, ""+i*100);    
		        }    
		        Long pEndTime=System.currentTimeMillis();//记录插入元素的结束时间    
		        System.out.println("插入元素时间-->"+(pEndTime-pBeginTime)); //得出时间差为插入所用时间  
		            
		        Long gBeginTime=System.currentTimeMillis();//记录当前查找元素开始的时间    
		        my.getValue(""+100000);    
		        Long gEndTime=System.currentTimeMillis();//记录查找元素结束时间    
		        System.out.println("查找元素时间--->"+(gEndTime-gBeginTime));  
		          
		        Long rBeginTime=System.currentTimeMillis();//记录移除元素开始时间   
		        my.remove(""+10000);    
		        Long rEndTime=System.currentTimeMillis();//记录移除元素结束时间
		        System.out.println("移除元素时间--->"+(rEndTime-rBeginTime));  
		          
		        //系统的hashmap  
		      HashMap<String, String> sys = new HashMap<String, String>();     
		        Long pBeginTime1=System.currentTimeMillis();//记录BeginTime 
		        System.out.println("测试系统的hash表:");
		        for(int i=0;i<1000000;i++){    
		        sys.put(""+i, ""+i*100);    
		        }    
		        Long pEndTime1=System.currentTimeMillis();//记录EndTime    
		        System.out.println("插入元素时间-->"+(pEndTime1-pBeginTime1));    
		            
		        Long lBeginTime=System.currentTimeMillis();//记录BeginTime    
		        sys.get(""+100000);    
		        Long lEndTime=System.currentTimeMillis();//记录EndTime    
		        System.out.println("查找元素时间--->"+(lEndTime-lBeginTime));  
		          
		        Long rBeginTime1=System.currentTimeMillis();//记录BeginTime    
		        sys.remove(""+10000);    
		        Long rEndTime1=System.currentTimeMillis();//记录EndTime    
		        System.out.println("移除元素时间--->"+(rEndTime1-rBeginTime1));  
		    }  
		    

	}

运行结果

6.性能分析:

1.因为冲突的存在,其查找长度不可能达到O(1)。

2.用哈希表构造查找表时,可以选择一个适当的装填因子,使得平均查找长度限定在某个范围内。

3.查找和移除的时间都为0,插入时间也比较快。

我发现自己写的hash表插入时间比jdk的竟然快了,其实非常怀疑是不是哪里没有实现好,系统牺牲这么大性能,究竟为了什么,所以现在正在看这个问题,但是博客不能再拖了,所以先把博客写了,等研究出来后,再更新博客,么么哒~~~~~~

编辑于 2017-12-14

文章被以下专栏收录

    这是前期学习Java的总结,希望能帮助到小伙伴们,博主之后不会继续写了,但是欢迎对Java感兴趣的童鞋来投稿~