HashMap ①是一个key-value的映射容器,key不重复 ②jdk8中的HashMap基于数组+链表+红黑树实现 ③不保证键值的顺序 ④可以存入null值 ⑤非线程安全,多线程环境下可能存在问题
HashMap基于哈希表的原理,提供了一种快速存储和查找键值对(key-value pairs)的方式 * * HashMap通过一个数组(称为‘桶’或‘槽’)来存储键值对,数组的每个元素都是一个链表(在Java8之后 * 的版本中,单链表的长度超过一定的阈值时,链表会转换成红黑树以提高性能).每个键值对通过其键的 * 哈希码来确定它在数组中的位置(即桶的索引),如果两个键的哈希码相同(称为哈希冲突),他们会被 * 放在同一个桶的链表中 * * 哈希冲突: * 是指在哈希表中,两个或更多个不同的键(或输入)被映射到了同一个哈希桶(或存储位置)的情况。 * * 哈希冲突的解决方法: * ①开放地址法: * .当发生冲突时,继续寻找下一个可用的位置,直到找到空闲的位置为止 * ②链地址法: * .在哈希表的每一个位置维护一个链表(将具有相同哈希值的键值对存储在同一个链表中) * .当发生冲突时,新的键值对被添加到对应位置的链表中 * ③再哈希法 * .当发生冲突时,使用另一个哈希函数对键进行再次哈希,以确定下一个位置 * ④建立公共溢出区 * .将哈希表分为基本表和溢出表两部分 * .凡是和基本表发生冲突的元素,一律填入溢出表 * ⑤使用更复杂的数据结构 * .使用平衡二叉树或跳表等数据结构来解决问题
浅拷贝(Shallow Copy) * 浅拷贝是指创建一个新的对象,新对象的内容是原始对象内容的引用。也就是说,浅拷贝只复制了对象本身和 * 对象中包含的引用,而不复制引用的对象。因此,如果原始对象中的某个字段是引用类型(比如另一个对象、 * 列表、字典等),那么浅拷贝后的新对象会持有对该字段相同的引用。 * * 优点:浅拷贝通常比深拷贝更快,因为它不需要递归地复制对象中的所有嵌套对象。 * 缺点:由于浅拷贝不会复制引用的对象,因此对原始对象或拷贝对象中引用的对象的修改会影响到另一个对象。 * 深拷贝(Deep Copy) * 深拷贝是指创建一个新的对象,并递归地复制原始对象中的所有对象(包括嵌套的、引用的对象)。这意味着 * ,深拷贝后的新对象与原始对象在内存中是完全独立的,修改新对象不会影响原始对象,反之亦然。 * * 优点:深拷贝保证了对象之间的完全独立,对一个对象的修改不会影响到另一个对象。 * 缺点:深拷贝需要更多的时间和内存,因为它需要递归地复制对象中的所有嵌套对象。
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>,Cloneable, Serializable {
//SerialVersionUID是Java序列化机制中的版本控制号,它是用来确保序列化的对象和反序列化的对象版本兼容
private static final long serialVersionUID = 362498820763181265L;
//0001左移4位,10000,16,所以DEFAULT_INITIAL_CAPACITY初始值为16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; //aka 16
//设置最大容量1073741824
static final int MAXIMUM_CAPACITY = 1 << 30;
//负载因子,在HashMap中如果默认的负载因子是0.75f,并且哈希表的当前容量是16,
//那么当哈希表中的条目数超过了16*0.75=12时,HashMap就会进行扩容操作
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//作为决定何时将链表转换为红黑树
// ①这个转换的目的是为了优化哈希表的性能,特别是在哈希冲突较多,链表较长的情况下,通过
// 转换为红黑树可以降低查找时间复杂度,从O(n)降低到O(log n)
//②但是当哈希表的容量小于MIN_TREEIFY_CAPACITY时,即使链表的长度超过了TREEIFY_THRESHOLD,
// 也不会进行树化操作,而是会先进行扩容操作,增加哈希表的容量,然后在检查是否需要树化
static final int TREEIFY_THRESHOLD = 8;
//解除树化阈值
//当元素数量减少到低于UNTREEIFY_TURESHOLD=6时,为了提高空间利用率和
// 降低红黑树的开销,会将这些这些红黑数结构转换回链表结构
static final int UNTREEIFY_THRESHOLD = 6;
//用作决定是否将链表转换为红黑树的容量阈值
//当链表中的元素数量超过TREEIFY_THRESHOLD(默认为8)时,并不会立即将链表转换为红黑树,
// 只有当哈希表的容量(即桶的数量,或说是可以存放元素的位置的数量)大于等于MIN_TREEIFY_CAPACITY时,
//链表才会被转换为红黑树
//这样做的目的是为了避免在哈希表比较小的情况下频繁的进行链表和红黑树之间的转换
//MIN_TREEIFY_CAPACITY的存在是为了确保只有在哈希表足够大且链表足够长时,才会进行链表到红黑树
// 的转换,从而优化性能减少不必要的转换开销
static final int MIN_TREEIFY_CAPACITY = 64;
HashMap<k,v>继承自AbstractMap<k,v>.AbstractMap是一个抽象类,它提供了一些Map接口的默认实现 ,但本身并不直接实现任何Map的具体功能。HashMap<k,v>通过继承AbstractMap,HashMap可以复用一些基础的Map集合。 而无需自己从头实现。 实现的接口 ·Map<k,v>HashMap实现了Map接口,这意味着它必须提供Map接口中定义的所有的方法 .Cloneable:通过实现Cloneable接口,HashMap提供了clone()方法的一个实现,允许创建当前HashMap 实例的一个浅拷贝,浅拷贝意味着新的HashMap实例将包含与原始实例相同的键值对,但这些键值对本身并没有被复制 .Serializable:实现Serializable接口允许HashMap的实例被序列化,即可以将HashMap的状态保存 一个字节流中,然后可以从这个字节流中恢复HashMap的状态。这对于对象的持久化(如何保存到文件中)或在 网络中传输对象非常有用。
Node类做存储键值对的基本单元每个Node对象都包含一个键key,一个值value,一个哈希码hash以及一个指向 * 下一个Node的引用(next),当两个键的hash码相同时,(即发生了hash冲突),这些Node对象会通过next引用 * 链接在一起,形成一个链表 * <p> * 在 Java 8 及更高版本的 HashMap 实现中,当链表中的节点数量超过一定阈值(默认为 8)且哈希表的容量大 * 于等于 MIN_TREEIFY_CAPACITY(默认为 64)时,链表会被转换为红黑树(TreeNode 类,它通常也是 Node * 的一个子类或者实现相同接口的类),以优化性能。
static class Node<K, V> implements Map.Entry<K, V> {
final int hash; //哈希码
final K key;
V value;
Node<K, V> next;
//构造器
Node(int hash, K key, V value, Node<K, V> next) {
//将传入的哈希值赋给当前对象的Hash属性
this.hash = hash;
this.key = key;
this.value = value;
//这行代码将传入的下一个Node对象的引用赋给当前对象的next属性,用于建立链表的下一个节点
this.next = next;
}
@Override
public K getKey() {
return key;
}
@Override
public V getValue() {
return value;
}
public final String toString() {
return key + "=" + value;
}
/**
* 在Java中,重写hashCode方法时通常也需要重写equals方法,
* 这是因为这两个方法之间有一个重要的约定,这个约定是由Object类的规范所定义的,并且对于正确地使用
* 基于哈希的数据结构(如HashMap、HashSet和Hashtable)至关重要。
* <p>
* 这个约定是:
* <p>
* 如果两个对象根据equals(Object)方法是相等的,那么调用这两个对象的hashCode方法必须产生相同的整数结果。
* 如果两个对象根据equals(Object)方法是不相等的,那么调用这两个对象的hashCode方法不一定产生不同的整数结果。
* 但是,程序员应该意识到,为不相等的对象产生不同整数结果可以提高哈希表的性能。
*/
//生成对象的哈希码
//用final修饰,表示这个方法不能被子类重写,这通常用与确保哈希码的计算方式在整个类层次结构中保持一致
public final int hashCode() {
//调用Objects类的静态方法hashcode来计算键和值的哈希码
//如果键或值为null,Objects.hashCode方法会返回一个特定的哈希码(在Java中,通常null的哈希码被定义为0)
//但Objects.hashCode对于null输入的处理可能依赖于具体的实现,不过通常它会返回一个非0的哈希码以避免冲突
//哈希码的计算
//是按位异或运算符,它将两个整数的二进制表示进行异或运算,对应位上如果相同则结果为0,不同则结果为1
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
@Override
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
//比较两个Map.Entry对象是否相等
public final boolean equals(Object o) {
//自反性
//equals方法首先检查传入的对象o是否就是当前对象this的引用,如果是,那么根据equals方法的定义,
// 任何对象都应该与其自身相等,所以返回true
if (o == this)
return true;
//类型检查
//代码通过instanceof关键字检查传入的对象o是否是Map.Entry的实例,这是为了确保只有Map.Entry
// 类型的对象才能与当前对象进行比较,如果o不是Map.Entry的实例,那么他们显然不相等,方法返回false
if (o instanceof Map.Entry) {
//如果o是Map.Entry的实例,那么o的类型在运行时是Map.Entry的某个具体实现类的实例
//在多态的情况下,一个父类类型的引用可以指向一个子类类型的对象。此时,编译时类型是父类,
// 但运行时类型是子类,这允许我们在运行时根据对象的实际类型来执行不同的行为
//强制类型转换
Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
//属性比较
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}