详细介绍java中hashmap
hello,大家好,这里是可傥。好久不见,之前一段时间因为在忙一些事情,然后好久没有更新了,今天继续更新。闲话不多说,今天,我们讲一下java中hashmap的底层原理和实现。
概述 HashMap是基于哈希表实现的,每一个元素是一个键值对允许使用null值和null键,属于非线性安全的无序集合,
多线程环境可以采用并发包下的concurrentHashMap。
HashMap 实现了Serializable接口,因此它支持序列化,实现了Cloneable接口,所以可以被克隆。
数据结构 相信大家都听过这么一句话,叫做,jdk1.7之前,hashmap底层是数组+链表的形式组成,jdk1.8之后,hashmap底层是数组+链表+红黑树组成。
但是如果看过java底层,你会发现,官方说明了,其实转化为红黑树的概率,也只是不到一千万分之一。下面为官方说明:
/* factorial(k)). The first values are:
*
* 0:0.60653066
* 1:0.30326533
* 2:0.07581633
* 3:0.01263606
* 4:0.00157952
* 5:0.00015795
* 6:0.00001316
* 7:0.00000094
* 8:0.00000006
* more: less than 1 in ten million 超过8的概率不到1千万分之1
* */
【详细介绍java中hashmap】超过8才会转化为红黑树(且数组大于64)。那么接下来,我将结合画图以及源码的·方式将数据结构展开:
如下图:
文章图片
图中,jdk1.8之后的数据就是很清晰的表达了底层数据结构。接下来,把hashmap的参数展示出来就可以更直观的说明问题,可傥把源码和注释全部放出来了,有一定阅读能力的可以看英文,或者就直接看可傥的汉字注释,如下代码:
/**
* The default initial capacity - MUST be a power of two.
* 默认容量,默认为16,必须为2的倍数
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
// aka 16/**
* The maximum capacity, used if a higher value is implicitly specified
* by either of the constructors with arguments.
* MUST be a power of two <= 1<<30.
* 最大容量,为2的30次方
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* The load factor used when none specified in constructor.
* 装载因子//后续会提到为什么是0.75
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* 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.
* 链表转化为红黑树树的阈值为8
*/
static final int TREEIFY_THRESHOLD = 8;
/**
* 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.
* 红黑树重新转化为链表的阈值为6
*/
static final int UNTREEIFY_THRESHOLD = 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.
* 转红黑树时, table的最小长度
*/
static final int MIN_TREEIFY_CAPACITY = 64;
/**
* Basic hash bin node, used for most entries.(See below for
* TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
* 链表结点,用来存放键值对,继承自Entey
*/
static class Node implements Map.Entry {
final int hash;
final K key;
V value;
Node next;
Node(int hash, K key, V value, Node next) {
this.hash = hash;
this.key = key;
this.value = https://www.it610.com/article/value;
this.next = next;
}
//... 省略部分代码
}/**
* Entry for Tree bins. Extends LinkedHashMap.Entry (which in turn
* extends Node) so can be used as extension of either regular or
* linked node.
* 红黑树结点,用来存放红黑树的键值对
*/static final class TreeNode extends LinkedHashMap.Entry {
TreeNode parent;
// red-black tree links
TreeNode left;
TreeNode right;
TreeNode prev;
// needed to unlink next upon deletion
boolean red;
TreeNode(int hash, K key, V val, Node next) {
super(hash, key, val, next);
}/**
* Returns root of tree containing this node.
*/
final TreeNode root() {
for (TreeNode r = this, p;
;
) {
if ((p = r.parent) == null)
return r;
r = p;
}
}/**
* The table, initialized on first use, and resized as
* necessary. When allocated, length is always a power of two.
* (We also tolerate length zero in some operations to allow
* bootstrapping mechanics that are currently not needed.)
* 存储数据的数组
*/
transient Node[] table;
/**
* Holds cached entrySet(). Note that AbstractMap fields are used
* for keySet() and values().
* 遍历的映射
*/
transient Set> entrySet;
/**
* The number of key-value mappings contained in this map.
* 键值对的数量
*/
transient int size;
/**
* 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;
/**
* The next size value at which to resize (capacity * load factor).
*
* @serial
*/
// (The javadoc description is true upon serialization.
// Additionally, if the table array has not been allocated, this
// field holds the initial array capacity, or zero signifying
// DEFAULT_INITIAL_CAPACITY.)
//下次扩容操作的大小
int threshold;
/**
* The load factor for the hash table.
*
* @serial
* 扩容阈值 为 size * DEFAULT_LOAD_FACTOR
*/
final float loadFactor;
结合画图和源代码,参数具体含义已经在注释上说明,接下来,将会具体讲解数据结构。
hashmap在一开始并没有初始化,而是在put(K,V)第一个元素的时候,初始化一个大小为16的数组,然后根据哈希计算存放存入的值在初始化数组的位置,当存入的元素个数超过扩容阈值的时候,数据会进行扩容,之前的数组个数 *2,扩容阈值和size都会变更。当数组大于64,且某个数组下的链表个数超过8的时候,该数组下的数据将会转化为红黑树。而扩容后若该数组下的链表个数小于6的时候,又会重新从红黑树转为链表。这就是hashmap的数据结构。
HashMap元素存储问题 那么,元素在存放的时候,是如何确定存放在哪个数组上的呢?咱们结合源码进行分析。
/**
* Computes key.hashCode() and spreads (XORs) higher bits of hash
* to lower.Because the table uses power-of-two masking, sets of
* hashes that vary only in bits above the current mask will
* always collide. (Among known examples are sets of Float keys
* holding consecutive whole numbers in small tables.)So we
* apply a transform that spreads the impact of higher bits
* downward. There is a tradeoff between speed, utility, and
* quality of bit-spreading. Because many common sets of hashes
* are already reasonably distributed (so don't benefit from
* spreading), and because we use trees to handle large sets of
* collisions in bins, we just XOR some shifted bits in the
* cheapest possible way to reduce systematic lossage, as well as
* to incorporate impact of the highest bits that would otherwise
* never be used in index calculations because of table bounds.
*
*/
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
文章图片
从源码中可得知,通过hashCode()的高16位异或低16位实现获取hash值:(h = k.hashCode()) ^ (h >>> 16),然后通过与数组容量(n-1)进行与操作获取数组下标。如下图:
文章图片
存放的时候,put中调用了putVal()方法,结合源码进行分析:
文章图片
HashMap的put方法执行过程可以通过下图来理解
文章图片
扩容机制 扩容还是结合源代码来讲会更加清晰。
文章图片
初始化为16的数组,然后在元素填充到装载因子 数组容量的时候,就会触发扩容。第一次扩容为 16 0.75 =12 ,每次扩容都为2的倍数,结合hash计算数组位置就可以知道,为啥需要2的倍数。而扩容之后,与操作向左在进行一位计算,所以每次扩容,原数组上的成员都是要么还在原数组,要么就在原数组的下标加上n的位置,第一次为加16的位置,第二次扩容为加32的位置,以此类推。而源码中也可以看到,不需要重新去计算hash值,直接拿原来的hash值就可以确定接下来的位置。
装载因子默认为什么为0.75 加载因子过高,虽然减少了空间开销,提高了空间利用率,但同时也增加了查询时间成本;
加载因子过低,虽然可以减少查询时间成本,但是空间利用率很低,同时提高了rehash操作的次数。
默认0.75是提高空间利用率和减少查询成本的折中,主要是泊松分布,0.75的话碰撞最小。
hashmap先讲到这里,后续可能会将内部的方法都讲一遍,这里是可傥,会分享自己的所学和所得,大家晚
安csdn地址为:https://blog.csdn.net/kaneand...。
推荐阅读
- JAVA(抽象类与接口的区别&重载与重写&内存泄漏)
- 事件代理
- Java|Java OpenCV图像处理之SIFT角点检测详解
- java中如何实现重建二叉树
- 数组常用方法一
- 【Hadoop踩雷】Mac下安装Hadoop3以及Java版本问题
- Apache多路复用模块(MPMs)介绍
- Java|Java基础——数组
- RxJava|RxJava 在Android项目中的使用(一)
- java之static、static|java之static、static final、final的区别与应用