HashMap和HashTable是都是Map的实现类。
一. HashMap
在学习HashMap之前,首先要对哈希表这种数据结构有所了解。
哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
哈希表的构造函数:
直接寻址法:f(key)=key
当两个key相等时,会产生哈希冲突
除留余数法:f(p)=p%n(n<m) n的选取关键,卡槽的数量m要大于n
也会发生哈希冲突。
哈希冲突如何解决呢解决?这里介绍两种方法,分别是链地址法和探测法。
链地址法:用数组+单项链表的方法来解决哈希冲突
探测法:pos(n) = f(n)+p(n)
线性探测法:p(n)=1 -> p(n)=2 ->.......直到找到卡槽里有位置
随机探测法:p(n)= random() 随机找一个卡槽是否有值
一.HashMap
HashMap就是基于哈希表这种数据结构实现的,它存放的是key-value键值对。
1.HashMap的类声明
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
HashMap继承自Map下的AbstracyMap,实现了Map接口,Cloneable接口,Serializable接口。
//为什么是16?因为必须要满足2的次方问题
static final int DEFAULT_INITIAL_CAPACITY = 16;//默认的容量大小
static final int MAXIMUM_CAPACITY = 1 << 30;//最大的容量大小
static final float DEFAULT_LOAD_FACTOR = 0.75f;//加载因子
transient Entry[] table;//HashMap底层是一个表
transient int size;//当前存储数据数量
int threshold;//阈值
transient volatile int modCount;//HashpMap被改变的次数
final float loadFactor;//加载因子
2.HashMap构造函数
①带参构造函数
指定容量和加载因子
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
// 保证容量是2的次方
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
this.loadFactor = loadFactor;
threshold = (int)(capacity * loadFactor);
table = new Entry[capacity];
init();
}
②无参构造函数
构造函数容量大小是默认的16,加载因子是默认的0.75
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
table = new Entry[DEFAULT_INITIAL_CAPACITY];
init();
}
3.主要方法
①get():获得key对应的value值
public V get(Object key) {
if (key == null)//如果key为空
return getForNullKey();
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
private V getForNullKey() {
if (size == 0) {//当前HashMap中没有元素
return null;
}
for (Entry<K,V> e = table[0]; e != null; e = e.next) {//遍历table[0]链表,找到key 为null的
if (e.key == null)
return e.value;
}
return null;
}
②containsKey():是否包含某个key
// HashMap是否包含key
public boolean containsKey(Object key) {
return getEntry(key) != null;
}
③getEntry():返回键值为key的键值对
final Entry<K,V> getEntry(Object key) {
if (size == 0) {//无元素
return null;
}
// HashMap将“key为null”的元素存储在table[0]位置,“key不为null”的则调用hash()计算哈希值
int hash = (key == null) ? 0 : hash(key);
// 在hash值对应的链表”上查找键值等于key的元素
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
//比较哈希值和key值是否相等
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}
④put():添加元素
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
// 若“key为null”,则将该键值对添加到table[0]中。
if (key == null)
return putForNullKey(value);
int hash = hash(key);//计算哈希值
int i = indexFor(hash, table.length);//找索引
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
// 若“该key”对应的键值对已经存在,则用新的value取代旧的value
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
// 若“该key”对应的键值对不存在,则将“key-value”添加到table中
modCount++;
addEntry(hash, key, value, i);
return null;
}
⑤addEntry():添加一个节点
⑥remove()
// 删除“键为key”元素
public V remove(Object key) {
Entry<K,V> e = removeEntryForKey(key);
return (e == null ? null : e.value);//返回该key对应的value值
}
4.HashMap的三种遍历方式
①通过键值对遍历
Iterator<Map.Entry<String, String>> iterator2 = hashMap.entrySet().iterator();
while(iterator2.hasNext()){
Map.Entry<String, String> next = iterator2.next();
String s1 = next.getKey();
String s2 = next.getValue();
System.out.println(s1+":::"+s2);
}
②通过键遍历
Iterator iterator3 = hashMap.keySet().iterator();
while(iterator3.hasNext()){
System.out.println(iterator3.next());
}
③通过值遍历
Iterator iterator4 = hashMap.values().iterator();
while(iterator4.hasNext()){
System.out.println(iterator4.next());
}
5.例题
十万个数据的重复统计问题
1.十万个数据如何存储
用ArrayList 或 LinkedList???
需要用到ArrayList有参构造函数
2.数据统计
用HashMap
key:存放数据
val:存放次数
以下为代码:
/**
* 用集合存储十万个数据
*/
ArrayList<Integer> list1 = new ArrayList<Integer>(10000);
Random random = new Random();
for(int i =1;i<=10000;i++){
list1.add(random.nextInt(9));
}
HashMap<Integer, Integer> hashMap = new HashMap<Integer,Integer>();
/**
* 用迭代器对ArrayList遍历
*/
Iterator<Integer> iterator2 = list1.iterator();
while(iterator2.hasNext()){
int tmp = iterator2.next();
if(!hashMap.containsKey(tmp)){//若HashMap中没有这个值
hashMap.put(tmp, 0);
}else{
hashMap.put(tmp,hashMap.get(tmp)+1);//修改HashMap的value,使其+1
}
}
/**
* 键值对遍历
*/
Iterator<Map.Entry<Integer, Integer>> iterator = hashMap.entrySet().iterator();
while(iterator.hasNext()){
Map.Entry<Integer, Integer> next = iterator.next();
Integer i1 = next.getKey();
Integer i2 = next.getValue();
System.out.println(i1+":"+i2);
}
二. Hashtable
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable
Hashtable继承自Dictionary,实现了Map,Cloneable和Serializable接口。
Hashtable 的函数都是同步的,这意味着它是线程安全的。它的key、value都不可以为null。此外,Hashtable中的映射不是有序的。
1.Hashtable的构造函数
指定初始容量和加载因子
public Hashtable(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal Load: "+loadFactor);
if (initialCapacity==0)
initialCapacity = 1;
this.loadFactor = loadFactor;
table = new Entry[initialCapacity];
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
initHashSeedAsNeeded(initialCapacity);
}
// 指定“容量大小”的构造函数
public Hashtable(int initialCapacity)
//默认构造函数
public Hashtable() {
this(11, 0.75f);
}
2.主要方法
Hashtable里也提供了put,get,remove方法,与HashMap类似,此处不做详解。
三. HashMap和Hashtable异同点
相同点
1.HashMap和HashTable都是存储键值对的散列表
键值对通过table数组来存储,数组中的每个数都是一个entry节点,每个entry后面又连了一个单向链表。
HashMap:
HashTable:
2.添加元素时,判断key值是否相等的标准一致
HashTable
HashMap
不同点
1.继承和实现接口不同
HashMap:
可以从源码中看出,HashMap继承自AbstractMap,实现了Cloneable,Map,Serializable(序列化)接口。
HashTable:
HsahTable则继承自Dictionary,实现了Map,Cloneable还有java.io.Serializable接口。
可以看出,HashMap和HashTable都实现了Map,Cloneable、java.io.Serializable接口,
但继承方式却不同。
AbstractMap是一个抽象类,实现了Map的绝大多数方法,Dictionary直接继承于Object类,里面的函数比Map的少。
2.线程安全不同
HashMap不是线程安全的,HashTable是线程安全的,支持多线程。
HashTable:
可以看出HashTable里的方法都加了锁,运行到这个方法时,需要检查有没有其他线程正在使用这个方法。
3.键和值的允许null情况不同
HashMap允许键值为null
HashTable不允许键和值为null
HashMap的添加键值对方法:
HashTable添加方法
4.初始容量大小不同
HashMap:16
HashTable:11
5.添加键值对时,计算哈希值方法不同
HashMap:没有用hashCode(),而是自定义的计算哈希值
HashTable:使用了key的hashCode值
6.HashMap效率高,HashTable效率低