哈希表
散列表,也就是哈希表,是一种可以用常数时间执行插入、删除和查找的技术,但元素间的排序关系往往是得不到支持的。
一般来说,对字符串的哈希是最频繁的,在Java,hashCode作为一个java.lang.Object下的方法,对所有的Object都是可用的,其声明为:
public native int hashCode();
另外还有一个也声明为native的hash方法:
public static native int identityHashCode(Object x);
执行以下代码段,结果将返回true
//ss是一个类的实例 int i = ss.hashCode(); int ii = System.identityHashCode(ss); System.out.println(i == ii);
所常用的String哈希源码:
//String类的hash方法 public int hashCode() { int h = hash; if (h == 0 && value.length > 0) { char val[] = value; for (int i = 0; i < value.length; i++) { h = 31 * h + val[i]; } hash = h; } return
LinkedHashMap中对key的Hash方法(不是hashCode):
//LinkedHashMap中的hash static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
在哈希表中,一个优秀的哈希方法可以充分利用分配的内存,且不容易造成冲突。
解决冲突的方法:
1、分离链接
将散列值相同的元素合并为一个链表放进数组,即整个哈希表就是一个链表数组
2、开放地址
即允许同散列值的元素占用还未占用的数组位置,至于分配位置的方法,主要有三种:
2.1、线性探测法:即寻找紧挨着的下一个空闲位置
2.2、平方探测法:假设某元素分配的位置i已被占用,则该元素尝试占用位置i^2
2.3、双散列:即对被占用的位置i再进行一次散列(二次散列方法一般不同于第一次使用的散列方法),拿到一个新的位置
另外,还有完美散列、布谷鸟散列、跳房子散列等改进的算法。
布谷鸟散列:基本思路为创建N张散列表,并用N个散列函数对待插入的元素进行散列,这样该元素就能拿到在N个表中共N个期望的插入位置,并从第一张表开始尝试插入,若第一张表的期望位置已被占用,则1、替换原本在该位置的元素,让该元素换到其他空位;2、继续查找下一张表。布谷鸟散列可以通过并行实现,通过多线程提升效率
例如:
分离链接法如果采用单链表实现(文末会给出一个自己写的简单实现),则在面对大量数据的时候会导致所有基本操作的性能普遍下降,但可以对这个问题进行优化,目前想到的方法主要有三个:
初始化时根据数据量选择合适的初始化数组长度
当数据量慢慢增大时选择扩容(reHash),同时重新散列位置(用于数据量不可预见的增大)
同散列值的元素改用双链表(LinkedList就是一种双链表)模型储存
关于Java中的HashMap、HashSet、LinkedHashMap、HashTable以及ConcurrentHashMap的主要区别
HashMap:
使用分离链接法实现,线程不安全,在进行reHash时会发生线程竞争问题,key和Value可空
HashSet:
先看一段源码:
//hashset源码 private transient HashMap<E,Object> map; /** * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has * default initial capacity (16) and load factor (0.75). */ public HashSet() { map = new HashMap<>(); }
HashSet其实是基于HashMap实现的,存取速度比较高,线程不安全,可空
LinkedHashMap:
使用了双链表结构维持了插入顺序的结构,基本上就是在HashMap的基础上对插入顺寻使用双链表进行了维护,线程不安全,可空,有序性对比例子:
//hashmap和linkedhashmap顺序对比 HashMap<String,Integer> s = new HashMap<String,Integer>(); for(int i=0 ;i<10 ;i++) { s.put("aaa"+i, i); } System.out.println(s); LinkedHashMap<String,Integer> ss = new LinkedHashMap<String,Integer>(); for(int i=0 ;i<10 ;i++) { ss.put("aaa"+i, i); } System.out.println(ss);
结果:
HashTable:
线程安全版本的HashMap,对整个表进行了加锁,key和value不能为null
ConcurrentHashMap:
package com.ryo.structure.hash; /** * <p>分离链接模型的哈希表 * <p>这里的实现是将散列到同一位置的元素组成一个单链表<br>默认长度为103, * 但也可以通过向构造函数传入一个大于103的整数创建一个自定义大小的哈希表, * 传入小于103的整数将默认创建103大小的表<br>未实现Entry * <br>未实现根绝负载因子扩容的功能,使用可变长度的初始化来解决这个问题 * @author shiin * @param <K> key * @param <V> value */ @SuppressWarnings("unchecked") public class SCHashMap<K ,V> implements HashMap<K ,V>{ private static final int DEFAULT_CAPACITY = 103; private HashNode<K ,V>[] map; private int size; private int currentCapacity; public SCHashMap() { this(DEFAULT_CAPACITY); } public SCHashMap(int capacity) { if(capacity < DEFAULT_CAPACITY) capacity = DEFAULT_CAPACITY; map = new HashNode[capacity]; size = 0; currentCapacity = capacity; } @Override public boolean contains(K key) { HashNode<K ,V> node = map[hash(key)]; while(node != null) { if(node.key.equals(key)) { return true; } node = node.next; } return false; } @Override public V get(K key) { HashNode<K ,V> node = map[hash(key)]; while(node != null) { if(node.key.equals(key)) { return node.value; } node = node.next; } return null; } @Override public int put(K key, V value) { int index = hash(key); if(map[index] == null) { map[index] = new HashNode<K ,V>(key ,value); } else { HashNode<K ,V> node = map[index]; HashNode<K ,V> prev = null; while(node != null) { if(node.key.equals(key)) { node.value = value; return 1; } prev = node; node = node.next; } node = new HashNode<K ,V>(key ,value); prev.next = node; } size++; return 1; } @Override public int remove(K key) { HashNode<K ,V> node = map[hash(key)]; HashNode<K ,V> prev = null; while(node != null) { if(node.key.equals(key)) { if(prev == null) map[hash(key)] = node.next; else prev.next = node.next; node = null;//GC size--; return 0; } prev = node; node = node.next; } return 0; } @Override public int size() { return this.size; } @Override public void clearAll() { map = new HashNode[currentCapacity]; size = 0; } @Override public boolean isEmpty() { return size == 0; } /** * 对key的散列方法 * @param key 键值 * @return 散列得到的存储位置 */ private int hash(K key) { if(key == null) return 0; int hash = key.hashCode() % currentCapacity; if(hash < 0) hash += currentCapacity; return hash; } private static class HashNode<K ,V>{ K key; V value; HashNode<K ,V> next; HashNode(K key ,V value ,HashNode<K ,V> next){ this.key = key; this.value = value; this.next = next; } HashNode(K key,V value){ this(key ,value ,null); } } }