才疏学浅,希望有错能指出
HashMap
关于HashMap
这些特征很难背,但是在学习源码过程中学着学着印象就会深刻,理解第一
- JDK1.7使用数组+链表的数据结构,JDK1.8使用数组+链表+红黑树的数据结构,详见参数解析
- JDK1.7使用前插法,JDK1.8使用后插法,详见put方法解析
- HashMap可以接受null键值:但仅限一个,重复会覆盖,详见put方法解析
- HashMap不是顺序存储:计算key的hash值然后加入对应桶中,但遍历是按桶顺序遍历,详见put方法解析
- HashMap是线程不安全的:相比于线程安全的方法你会发现他没有用synchronized或cas等方法进行限制
- HashMap能被克隆:HashMap实现了Cloneable接口,但是这个接口也无内容,通过重写Object类的clone方法实现
- HashMap支持序列化:HashMap实现了Serializable接口,但这个接口是个空接口,对应的是writeObject方法
HashMap参数
private static final long serialVersionUID = 362498820763181265L;
- serialVersionUID适用于Java的序列化机制。简单来说,Java的序列化机制是通过判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即是InvalidCastException。[1]
/**
* 默认 初始化 容量 - 必须是 一个 2的指数幂 的数
* The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
- 默认容量大小是16( 1 << 4 是位运算,目的是提高计算效率),在你使用无参构造函数(new HashMap())时会用到,详见构造方法解析
- 在注释行中提到如果手动指定大小需要指定为2的整数幂,目的是为了方便hash计算,后文会提到
/**
* 最大容量 , 如果 一个 更高的 值 隐式 指定
* The maximum capacity, used if a higher value is implicitly specified
* 被 任意一个 有参构造函数 则使用这个最大容量值
* by either of the constructors with arguments.
* 容量必须小于 1<<30(约10亿)(这个MUST be a power of two代表当前容量大小,)
* MUST be a power of two <= 1<<30.
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
- 注意这里是1 << 30 不是1 << 31,所以指定的长度最大是10 7374 1824(空格以区分单位),为什么在后文会有提到
/**
* 这个 加载因子值 在 没有 特别指定值 的构造方法中使用
* The load factor used when none specified in constructor.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
- 0.75这个值兼顾了空间利用率和hash碰撞概率,解释起来有点烧脑,详见[2]
/**
* 太多了以后翻译,大概是说转成红黑树的阈值为8和为什么为8
* The bin count threshold for using a tree rather than list for a
* bin. Bins are converted to trees when adding an element to a
* bin with at least this many nodes. The value must be greater
* than 2 and should be at least 8 to mesh with assumptions in
* tree removal about conversion back to plain bins upon
* shrinkage.
*/
static final int TREEIFY_THRESHOLD = 8;
- 为什么阈值为8呢,8个节点刚好可以构建完全二叉树,且查询效率是O(logn)
- 为什么要用红黑树呢,因为有一种极端情况,如果所有key计算出hash值都一样,那么它们会放在同一个数组中,那么遍历时相当于遍历一个链表O(n),就大大了查找的效率,而红黑树在这样多节点的情况中有较好的查询效率O(logn)(本来最好情况大家各占一个坑找到坑就能找到你,现在要在坑里一个一个向后找),详见[3]
/**
* 虽然不多但还是以后翻译8,大概是说节点数小于6就会转回链表
* The bin count threshold for untreeifying a (split) bin during a
* resize operation. Should be less than TREEIFY_THRESHOLD, and at
* most 6 to mesh with shrinkage detection under removal.
*/
static final int UNTREEIFY_THRESHOLD = 6;
- 当数组里的红黑树节点数小于6时转回链表
/**
* 节点允许被树化的最小容量
* The smallest table capacity for which bins may be treeified.
* (Otherwise the table is resized if too many nodes in a bin.)
* Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
* between resizing and treeification thresholds.
*/
static final int MIN_TREEIFY_CAPACITY = 64;
- 当链表长度大于8调用了treeifyBin方法后该方法还会进行判断,当map的容量大于等于 64 才会发生转换。这是为了避免在哈希表建立初期,多个键值对恰好被放入了同一个链表中而导致不必要的转化,实例可见[4]
/**
* 最基础的键值对,用于大多数条目
* Basic hash bin node, used for most entries. (See below for
* TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
*/
static class Node<K,V> implements Map.Entry<K,V> {
final int hash; // key的hash值
final K key; // key
V value; // value
Node<K,V> next; // 下个Node的引用
/**
* 还有很多很多方法就不全部复制过来占位置了,但是很经常被用到,插眼
*/
}
- 内部类Node,存储键值对
/**
* table(这个数组), 在你第一次使用时初始化 , 并且 在必要时调整(数组长度)
* The table, initialized on first use, and resized as
* 使用时 长度 总是2的整数幂
* necessary. When allocated, length is always a power of two.
* 我们接受在某些操作中为了某些目前不需要用到的引导机制将长度设为为0(哪些操作我也不大清楚,插眼以后补上)
* (We also tolerate length zero in some operations to allow
* bootstrapping mechanics that are currently not needed.)
*/
transient Node<K,V>[] table;
- 这是数组+链表+红黑树中的数组,链表和红黑树的头部就是对应的数组元素
/**
* 为entrySet()这个方法做的缓存, 表示在keySet()和values()方法中被用到的AbstractMap的属性(插眼插眼)
* Holds cached entrySet(). Note that AbstractMap fields are used
* for keySet() and values().
*/
transient Set<Map.Entry<K,V>> entrySet;
- 是Entry对象的Set集合,也就是说map存储的键值对的集合
/**
* map中包含的key-value(键值对)的数量
* The number of key-value mappings contained in this map.
*/
transient int size;
- 实际存储数
/**
* 好多啊下次在翻译,大概是说记录HashMap被结构化修改的次数,然后解释什么是结构化修改和有什么用,插眼
* The number of times this HashMap has been structurally modified
* Structural modifications are those that change the number of mappings in
* the HashMap or otherwise modify its internal structure (e.g.,
* rehash). This field is used to make iterators on Collection-views of
* the HashMap fail-fast. (See ConcurrentModificationException).
*/
transient int modCount;
- 修改次数,在使用iterator遍历器时会用到
/**
* 下一次进行resize的阈值(计算方式capacity * load factor,容量*加载因子)
* The next size value at which to resize (capacity * load factor).
*
* @serial
*/
// @serial这个注解是正确的
// (The javadoc description is true upon serialization.
// 另外 ,如果 数组table 没有被分配空间 , 则这个
// Additionally, if the table array has not been allocated, this
// 变量 保存 初始化时指定的数组容量 ,或 0 用来标识默认容量
// field holds the initial array capacity, or zero signifying
// DEFAULT_INITIAL_CAPACITY.)
int threshold;
- 扩容阈(yù)值,实际容量大于等于这个值就会调用resize()方法扩容
/**
* hashtable的加载因子
* The load factor for the hash table.
*
* @serial
*/
final float loadFactor;
- 用来计算阈值:threshold = capacity * load factor,阈值=容量*加载因子
HashMap构造方法
参考资料
-
博客园用户 kabibo 的文章java类中serialversionuid 作用 是什么?举个例子说明
-
简书用户 Eric新之助 的文章为什么java Hashmap 中的加载因子是默认为0.75
-
CADN 用户 十步杀一人_千里不留行 的文章算法高级(26)-在Java8中为什么要使用红黑树来实现的HashMap?
-
IT610用户 fan2012huan 的文章HashMap的扩容及树化过程