hashmap是单向链表吗_Java集合,HashMap底层实现和原理(1.7数组+链表与1.8+的数组+链表+红黑树)...

HashMap是基于哈希表实现的,通过key的hashCode找到bucket位置存储Entry对象。当两个key的hashcode相同,会通过链表或红黑树解决冲突。1.7中使用单链表,1.8及以上版本当链表长度超过8时转换为红黑树,以提高查找效率。HashMap非线程安全,线程安全可使用ConcurrentHashMap。
摘要由CSDN通过智能技术生成

概述

文章的内容基于JDK1.7进行分析,之所以选用这个版本,是因为1.8的有些类做了改动,增加了阅读的难度,虽然是1.7,但是对于1.8做了重大改动的内容,文章也会进行说明。

HashMap基于Map接口实现,元素以键值对的方式存储,并且允许使用null 建和null 值, 因为key不允许重复,因此只能有一个键为null,另外HashMap不能保证放入元素的顺序,它是无序的,和放入的顺序并不能相同。HashMap是线程不安全的。

数据结构

继承关系

public class HashMapextends AbstractMap

implements Map, Cloneable, Serializable

实现接口

Serializable, Cloneable, Map

基本属性

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; //默认初始化大小 16

static final float DEFAULT_LOAD_FACTOR = 0.75f; //负载因子0.75

static final Entry,?>[] EMPTY_TABLE = {}; //初始化的默认数组

transient int size; //HashMap中元素的数量

int threshold; //判断是否需要调整HashMap的容量

源码解析

什么是链表

链表是由一系列非连续的节点组成的存储结构,简单分下类的话,链表又分为单向链表和双向链表,而单向/双向链表又可以分为循环链表和非循环链表,下面简单就这四种链表进行图解说明。

1.单向链表

单向链表就是通过每个结点的指针指向下一个结点从而链接起来的结构,最后一个节点的next指向null。

2.单向循环链表

单向循环链表和单向列表的不同是,最后一个节点的next不是指向null,而是指向head节点,形成一个“环”。

3.双向链表

从名字就可以看出,双向链表是包含两个指针的,pre指向前一个节点,next指向后一个节点,但是第一个节点head的pre指向null,最后一个节点的tail指向null。

4.双向循环链表

双向循环链表和双向链表的不同在于,第一个节点的pre指向最后一个节点,最后一个节点的next指向第一个节点,也形成一个“环”。而LinkedList就是基于双向循环链表设计的。

在进行源码解析之前,先从总体上对HashMap的数据存储结构进行一个大体上的说明。

存储结构如上图所示。

HashMap采用Entry数组来存储key-value对,每一个键值对组成了一个Entry实体,Entry类实际上是一个单向的链表结构,它具有Next指针,可以连接下一个Entry实体,依次来解决Hash冲突的问题,因为HashMap是按照Key的hash值来计算Entry在HashMap中存储的位置的,如果hash值相同,而key内容不相等,那么就用链表来解决这种hash冲突。

public class HashMap extends AbstractMap

implements Map, Cloneable, Serializable {

//默认初始化的容量

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

//最大的容量

static final int MAXIMUM_CAPACITY = 1 << 30;

//负载因子,当容量达到75%时就进行扩容操作

static final float DEFAULT_LOAD_FACTOR = 0.75f;

//当数组还没有进行扩容操作的时候,共享的一个空表对象

static final Entry,?>[] EMPTY_TABLE = {};

//table,进行扩容操作,长度必须2的n次方

transient Entry[] table = (Entry[]) EMPTY_TABLE;

//Map中包含的元素数量

transient int size;

//阈值,用于判断是否需要扩容(threshold = 容量*负载因子)

int threshold;

//加载因子实际的大小

final float loadFactor;

//HashMap改变的次数

transient int modCount;

static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;

//内部类,通过vm来修改threshold的值

private static class Holder {

/**

* Table capacity above which to switch to use alternative hashing.

*/

static final int ALTERNATIVE_HASHING_THRESHOLD;

static {

String altThreshold = java.security.AccessController.doPrivileged(

new sun.security.action.GetPropertyAction(

"jdk.map.althashing.threshold")); //读取值

int threshold;

try {

threshold = (null != altThreshold) //修改值

? Integer.parseInt(altThreshold)

: ALTERNATIVE_HASHING_THRESHOLD_DEFAULT;

// disable alternative hashing if -1

if (threshold == -1) {

threshold = Integer.MAX_VALUE; //设置为Integer能表示的最大值

}

if (threshold < 0) {

throw new IllegalArgumentException("value must be positive integer.");

}

} catch(IllegalArgumentException failed) {

throw new Error("Illegal value for 'jdk.map.althashing.threshold'", failed);

}

ALTERNATIVE_HASHING_THRESHOLD = threshold; //返回

}

}

//HashCode的初始值为 0

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);

}

//构造函数,参数为指定的Map集合

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);

}

//选择合适的容量值,最好是number的2的幂数

private static int roundUpToPowerOf2(int number) {

// assert number >= 0 : "number must be non-negative";

return number >= MAXIMUM_CAPACITY

? MAXIMUM_CAPACITY

: (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;

}

//扩充表,HashMap初始化时是一个空数组,此方法执行重新复制操作,创建一个新的Entry[]

private void inflateTable(int toSize) {

// Find a power of 2 >= toSize

int capacity = roundUpToPowerOf2(toSize); //capacity为2的幂数,大于等于toSize

threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);

table = new Entry[capacity]; //新建数组,并重新赋值

initHashSeedAsNeeded(capacity); //修改hashSeed

}

// internal utilities

//初始化

void init() {

}

//与虚拟机设置有关,改变hashSeed的值

final boolean initHashSeedAsNeeded(int capacity) {

boolean currentAltHashing = hashSeed != 0;

boolean useAltHashing = sun.misc.VM.isBooted() &&

(capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);

boolean switching = currentAltHashing ^ useAltHashing;

if (switching) {

hashSeed = useAltHashing

? sun.misc.Hashing.randomHashSeed(this)

: 0;

}

return switching;

}

//计算k 的 hash值

final int hash(Object k) {

int h = hashSeed;

if (0 != h && k instanceof String) {

return sun.misc.Hashing.stringHash32((String) k);

}

h ^= k.hashCode();

// This function ensures that hashCodes that differ only by

// constant multiples at each bit position have a bounded

// number of collisions (approximately 8 at default load factor).

h ^= (h >>> 20) ^ (h >>> 12);

return h ^ (h >>> 7) ^ (h >>> 4);

}

//根据hashcode,和表的长度,返回存放的索引

static int indexFor(int h, int length) {

// assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";

return h & (length-1);

}

//返回Map中键值对的数量

public int size() {

return size;

}

//判断集合是否为空

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值