hashmap
1、hashmap 与 hashtable 的区别
- 线程安全
- null值
- 执行效率
2、hashmap
hashmap是java中常用的集合类之一,
- 继承自AbstractMap类,实现了 Map、Cloneable、java.io.Serializable 接口。
- 使用哈希表来存储键值对(key-value),通过将键值映射成哈希码来进行高效的插入、删除操作。
- 具有很快的访问速度,最多允许一条记录的键为 null,不支持线程同步。
java.lang.Object
java.util.AbstractMap<K,V>
java.util.HashMap<K,V>
Type Parameters:
K - the type of keys maintained by this map
V - the type of mapped values
All Implemented Interfaces:
Serializable, Cloneable, Map<K,V>
Direct Known Subclasses:
LinkedHashMap, PrinterStateReasons
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
基本类型与包装类
HashMap 中的元素实际上是对象,一些常见的基本类型可以使用它的包装类。
基本类型对应的包装类表如下:
基本类型 | 引用类型 |
---|---|
boolean | Boolean |
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
常用实现
1、创建
import java.util.HashMap; // 引入 HashMap 类
HashMap<Integer, String> A = new HashMap<Integer, String>();
2、添加元素 put() 方法:
当调用put方法的时候,先通过hashCode() 获取key 的hashCode,然后找到bucket位置,将Entry对象 存储到bucket上面。
public class Test {
public static void main(Strings[] args) {
// 创建 HashMap 对象 A
HashMap<Integer, String> A = new HashMap<Integer, String>();
// 添加键值对
A.put(1, "apple");
A.put(2, "banana");
System.out.println(A);
}
}
3、访问元素 get(key) 方法:
通过get(key)获取对象时,也是通过hashCode()方法 获取key的hashCode,然后得到Entry对象。
public class Test{
public static void main(String[] args) {
// 创建 HashMap 对象 A
HashMap<Integer, String> A = new HashMap<Integer, String>();
// 添加键值对
A.put(1, "apple");
A.put(2, "banana");
// 访问
System.out.println(A.get(1));
}
}
4、删除元素 remove(key)方法:
5、删除所有元素 :clear()方法
public class Test{
public static void main(String[] args) {
// 创建 HashMap 对象 A
HashMap<Integer, String> A = new HashMap<Integer, String>();
// 添加键值对
A.put(1, "apple");
A.put(2, "banana");
// 删除
A.remove(1);
// 删除所有元素
A.clear();
}
}
6、计算 HashMap 中元素的数量 :size() 方法
7、 遍历
- 可以使用 for-each 来迭代 HashMap 中的元素
- 只想获得 key ,使用 KeySet() 方法,然后通过 get(key) 获取对应的 value
- 指向获得value,有getValue() 方法.
// 方法一:
Iterator iterator = hashMap.keySet().iterator();
while (iterator.hasNext()){
String key = (String)iterator.next();
System.out.println(key+"="+hashMap.get(key));
}
// 方法二:
Iterator iterator1 = hashMap.entrySet().iterator();
while (iterator1.hasNext()){
Map.Entry entry = (Map.Entry) iterator1.next();
String key = (String) entry.getKey();
Integer value = (Integer) entry.getValue();
System.out.println(key+"="+value);
}
变量介绍
该类提供了四种构造方法:
(1)HashMap()
Constructs an empty HashMap with the default initial capacity (16) and the default load factor (0.75).
public HashMap(){
this.loadFactor = DEFAULT_LOAD_FACTOR; //all other fields defaulted
}
这是我们用的比较频繁的一个构造方法了,没有特殊的写法,所有变量均是默认值
(2) HashMap(int initialCapacity)
Constructs an empty HashMap with the specified initial capacity and the default load factor (0.75).
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
该构造方法传入一个指定容量,然后调用另外一个重载的构造方法
(3) HashMap(int initialCapacity, float loadFactor)
Constructs an empty HashMap with the specified initial capacity and load factor.
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;
this.threshold = tableSizeFor(initialCapacity);
}
该构造方法指定一个容量值和一个负载因子;从方法内部逻辑可以看出容量值小于0将会抛出异常,大于最大容量值就重置为最大值;负载因子 如果小于等于0或者是个非法数字也将会抛出异常;最后一句代码的意思是计算HashMap下次扩容时的阈值,也就是说HashMap并不会等被填满时才会进行扩容,而是达到这个阈值时就开始提前扩容
(4) HashMap(Map<? extends K,? extends V> m)
Constructs a new HashMap with the same mappings as the specified Map.
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
// 将m中的所有键值对添加到HashMap
putMapEntries(m, false);
}
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
int s = m.size();
if (s > 0) {
if (table == null) { // pre-size
//数组没有初始化 重新计算阈值
float ft = ((float)s / loadFactor) + 1.0F;
int t = ((ft < (float)MAXIMUM_CAPACITY) ?
(int)ft : MAXIMUM_CAPACITY);
if (t > threshold)
threshold = tableSizeFor(t);
}
else if (s > threshold)
//已经初始化 如果大小大于阈值 需要扩容
resize();
//将m中所有键值对添加到本HashMap中
for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
K key = e.getKey();
V value = e.getValue();
putVal(hash(key), key, value, false, evict);
}
}
}
HashMap的内部变量
这些是哈希表内部的一些变量:
int DEFAULT_INITIAL_CAPACITY = 1 << 4
内部哈希表初始容量,值为16,值必须是2的幂次方
这里有一个常见的面试问题,即问你为什么HashMap的初始容量是16,为2的幂次?
参见后文《初始容量和负载因子》
int MAXIMUM_CAPACITY = 1 << 30
哈希表最大容量,一般情况下只要内存够用,哈希表不会出现问题
float DEFAULT_LOAD_FACTOR = 0.75f
默认的负载因子,因此初始情况下,当键值对的数量大于 16 * 0.75 = 12 时,就会触发扩容
int TREEIFY_THRESHOLD = 8
这个值表示当某个桶中,链表长度大于 8 时,将链表转化成红黑树;如果哈希函数不合理,导致过多的数据碰撞,即使扩容也无法减少箱子中链表的长度,因此将链表转换成红黑树
int UNTREEIFY_THRESHOLD = 6:
当哈希表在扩容时,如果桶中的链表长度小于 6,则会由树重新退化为链表
int MIN_TREEIFY_CAPACITY = 64:
即哈希表中的桶数量要大于64才会考虑将链表转换成树,也是避免在小容量的时候进行不必要的转换
Node<K,V>[] table
存放元素的数组,容量总是2的幂次方
Set<Map.Entry<K,V>> entrySet
存放具体元素的集合
int size
HashMap中键值对的数量
int modCount
扩容或者结构更改的计数器
int threshold
HashMap进行扩容的阈值,当实际大小超过该值就会进行扩容
float loadFactor
实际负载因子
初始容量和负载因子
这是影响哈希表的两个重要因素
红黑树和链表转化
HashMap的内部数据结构
HashMap内部哈希算法
以上四节均学习于此文章:
源码解析-深刻理解Hash HashTable HashMap原理及数据hash碰撞问题