包路径:package java.util;
import java.io.*;
一、基本属性
1、static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; 默认初始容量16
2、static final int MAXIMUM_CAPACITY = 1 << 30; 限制数组最大值
3、static final float DEFAULT_LOAD_FACTOR = 0.75f; 默认加载因子,0.75f是根据经验所得
4、static final Entry<?,?>[] EMPTY_TABLE = {}; 初始默认空数组
5、transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE; 数组,用来存储entry数据
6、transient int size; 存储数据量
7、int threshold; 阈值
8、final float loadFactor; 加载因子
9、transient int modCount; 版本号
10、static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;
默认哈希阈值
11、transient int hashSeed = 0; 哈希种子
二、继承关系
三、构造方法(共四种构造方法)
构造方法一、传入两个参数:初始容量,加载因子。首先进行传入参数的合法性检验
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);
this.loadFactor = loadFactor;
threshold = initialCapacity;
init();
}
构造方法二、传入一个参数:初始容量
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
构造方法三、无参构造
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
构造方法四、传入集合对象m,通过集合构造新的集合
public HashMap(Map<? extends K, ? extends V> m) {
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
inflateTable(threshold);
putAllForCreate(m);
}
四、核心方法
1、添加元素:put()
public V put(K key, V value) { 传入的参数是键值对:key和value
if (table == EMPTY_TABLE) { 首先检查table数组是否为空,为空的话进行初始化
inflateTable(threshold);
}
if (key == null) 如果key为空,调用特殊处理的方法
return putForNullKey(value);
int hash = hash(key); 根据键值key进行哈希计算得到对应的哈希码
int i = indexFor(hash, table.length); 根据哈希码得到映射到table数组的索引下标
遍历第i个链表查找是否存在key的entry,存在则替换到entry下的value值并返回旧的value值
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
在这里比较key和获取元素比较key思想是一样的,需要比较三者!!!
V oldValue = e.value;
e.value = value;
e.recordAccess(this); 替换存在的entry的value值需要调用
return oldValue;
}
}
若不存在key相同的entry,则添加新的entry到HashMap中
modCount++; 添加元素,HashMap的结构发生变化,版本号modCount++
addEntry(hash, key, value, i);
return null;
}
添加元素,当Key为null时,调用此方法
private V putForNullKey(V value) { 直接在table的第0号索引下标的链表中查找替换或者进行添加
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(0, null, value, 0);
return null;
}
在table的第bucketIndex个链表上添加哈希码为hash,键值为key,映射为value的entry
void addEntry(int hash, K key, V value, int bucketIndex) {
首先进行合法性检查,当HashMap的size大于阈值,并且table数组的第bucketIndex个链表为null的时候,进行2倍的扩容操作。
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0; 重现计算key的哈希码
bucketIndex = indexFor(hash, table.length);
重新计算哈希码应该对应放到table数组的哪个链表上
}
createEntry(hash, key, value, bucketIndex);
创建entry对象,并插入到第bucketIndex个索引链表的第一个位置上
}
创建entry对象,并插入到第bucketIndex个索引链表的第一个位置上,相当于头插法
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
final int hash(Object k) {
int h = hashSeed;
if (0 != h && k instanceof String) { 当哈希种子不为0,且k是string时
return sun.misc.Hashing.stringHash32((String) k); 调用特殊的哈希算法
}
h ^= k.hashCode();
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
static int indexFor(int h, int length) {
return h & (length-1);
}
2、获取元素:get()
public V get(Object key) { 获取键值为key的entry的value值
if (key == null) 键值key为null,特殊处理,调用特殊的方法
return getForNullKey();
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
private V getForNullKey() { key为null时
if (size == 0) { 首先检查数组大小
return null;
}
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
在table数组的第0号索引链表下查找
if (e.key == null)
return e.value;
}
return null;
}
final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
}
int hash = (key == null) ? 0 : hash(key);
根据Key计算哈希码,根据哈希码找到table数组下的索引链表,进行查找
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
这里查找的时候,需要使用“==”比较找到的entey的hash是否相等,然后使用“==”比较entry的key和已知的key是否相等,或者使用“.equals()”方法比较两者的key是否相等且key不能为null
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}
3、删除元素:
public V remove(Object key) { 根据键值key进行删除
Entry<K,V> e = removeEntryForKey(key);
return (e == null ? null : e.value);
}
final Entry<K,V> removeEntryForKey(Object key) {
if (size == 0) {
return null;
}
int hash = (key == null) ? 0 : hash(key); 首先根据key计算哈希码
int i = indexFor(hash, table.length); 根据哈希码找到数组对应的索引的链表
Entry<K,V> prev = table[i];
Entry<K,V> e = prev;
while (e != null) { 查找链表
Entry<K,V> next = e.next;
Object k;
找到key对应的entry
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) { 比较!!!
modCount++; HashMap数据结构发生改变,版本号modCunt++
size--;
if (prev == e) 如果移除的是table索引的第一个entry,直接修改table[i]
table[i] = next;
else
prev.next = next;
移除的不是第一个entry,将移除entry的前一个entry的next指向移除entry的next
e.recordRemoval(this); 记录移除的entry
return e;
}
prev = e; 顺序后移
e = next; 再次循环
}
return e; 返回移除的entry
}
五、遍历方式(共用三种遍历方式)
这里直接进行示例代码演示如下:
HashMap<Integer,Integer> hashMap=new HashMap<Integer,Integer>();
hashMap.put(1,11);
hashMap.put(2,22);
hashMap.put(3,33);
hashMap.put(4,44);
System.out.print("通过键值对进行遍历:");
Iterator<Map.Entry<Integer,Integer>> iterator1=hashMap.entrySet().iterator();
while (iterator1.hasNext()){
Map.Entry<Integer,Integer> entry=iterator1.next();
Integer key1=entry.getKey();
Integer value1=entry.getValue();
System.out.print(key1+"==>"+value1+" ");
}
System.out.println();
System.out.print("通过键进行遍历:");
Iterator<Integer> key=hashMap.keySet().iterator();
while (key.hasNext()){
Integer key2=key.next();
System.out.print(key2+" ");
}
System.out.println();
System.out.print("通过值进行遍历:");
Iterator<Integer> value=hashMap.values().iterator();
while(value.hasNext()){
Integer value2=value.next();
System.out.print(value2+" ");
}
System.out.println();
}
运行结果:
六、总结
1、底层数据结构:数组+链表,是以键值对的形式进行存储。
2、key可以为null,且最多只能有一个为null,且不可重复,value可以有多个为null,可以存储重复的数据。
3、数组是按照2倍指数进行扩容增长。
4、存储的数据不能保证有序性。
5、非线程安全的。