# hashMap原理 #
HashMap是一个双列集合,是线程不安全的。以key、value的形式储存值。底层是由数组+链表+红黑树组成的,数组是HashMap的主干,链表则是主要为了解决哈希冲突而存在的,根据key计算出hash值,存储在数组里面,当hsah值冲突的时候,通过equals方法比较,如果不同就创建链表存储在链表里面。当链表长度超过8的时候,会自动转化为红黑树。他的容量initialCapacity默认为16,负载因子loadFactory默认为0.75。当存储的容量大于hashmap的容量乘以0.75的时候,就会自动扩容。

我们通过hash方法计算索引,得到数组中保存的位置,看一下源码

 static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

HashMap中的hash算法是通过key的hashcode值与其hashcode右移16位后得到的值进行异或运算得到的

扩展问题:为什么扩容的时候为啥一定必须是2的多少次幂?

因为如果只有2的n次幂的情况时最后一位二进制数才一定是1,这样能最大程度减少hash碰撞。

链表数据插入方法

l HashMap 1.7使用的是头插法,扩容后位置与原链表位置相反。防止尾部遍历,不然每次插入的时候都要定位到尾部节点,如果多线程情况下容易形成环;

l HashMap 1.8使用的是尾插法,因为其中会使用到红黑树,构建红黑树有个过程,扩容后位置与原链表相同;

扩展:因为HashMap 1.7是用单链表进行的纵向延伸,当采用头插法时会容易出现逆序且环形链表死循环问题。但是在HashMap 1.8之后是因为加入了红黑树使用尾插法,能够避免出现逆序且链表死循环的问题。

因为新数组table下标并不是根据循环逐步递增的,而是通过(table.length-1)& hash计算得到,因此扩容后,存放的位置就可能发生变化,那么到底发生怎样的变化呢,就是由下面的算法得到.

   * 通过e.hash & oldCap来判断节点位置通过再次hash算法后,是否会发生改变,如
* 果为0表示不会发生改变,如果为1表示会发生改变。到底怎么理解呢,举个例子:
* e.hash = 13 二进制:0000 1101
* oldCap = 32 二进制:0001 0000
* &运算: 0 二进制:0000 0000
* 结论:元素位置在扩容后不会发生改变
*/
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
/**
* e.hash = 18 二进制:0001 0010
* oldCap = 32 二进制:0001 0000
* &运算: 32 二进制:0001 0000
* 结论:元素位置在扩容后会发生改变,那么如何改变呢?
* newCap = 64 二进制:0010 0000
* 通过(newCap-1)&hash
* 即0001 1111 & 0001 0010 得0001 0010,32+2 = 34
*/
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
/**
* 若(e.hash & oldCap) == 0,下标不变,将原表某个下标的元素放到扩容表同样
* 下标的位置上
*/
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
/**
* 若(e.hash & oldCap) != 0,将原表某个下标的元素放到扩容表中
* [下标+增加的扩容量]的位置上
*/
newTab[j + oldCap] = hiHead;
}
}
}

# hashMap和hashTable的区别 #

hashmap线程不安全,hashtable线程安全。
hashmap键值可以为null。
hashtable键值不可以为null。

hashMap和hashTable的实现原理区别

继承的父类不同

对外提供的接口不同

对Null key 和Null value的支持不同

线程安全性不同

遍历方式的内部实现上不同

初始容量大小和每次扩充容量大小的不同

计算hash值的方法不同

ConcurrentHashMap

1、悲观锁与乐观锁:
悲观锁是指如果一个线程占用了一个锁,而导致其他所有需要这个锁的线程进入等待,一直到该锁被释放,换句话说就是这个锁被独占,比如说典型的就是synchronized;乐观锁是指操作并不加锁,而是抱着尝试的态度去执行某项操作,如果操作失败或者操作冲突,那么就进入重试,一直到执行成功为止。

2、原子性,指令有序性和线程可见性:
这三个性质在多线程编程中是核心的问题。原子性和事务的原子性一样,对于一个操作或者多个操作,要么都执行,要么都不执行。指令有序性是指,在我们编写的代码中,上下两个互不关联的语句不会被指令重排序。指令重排序是指处理器为了性能优化,在无关联的代码的执行是可能会和代码顺序不一致。比如说int i = 1;int j = 2;那么这两条语句的执行顺序可能会先执行int j = 2;线程可见性是指一个线程修改了某个变量,其他线程能马上知道。

3、无锁算法(nonblocking algorithms):
使用低层原子化的机器指令, 保证并发情况下数据的完整性。典型的如CAS算法。

4、内存屏障:
在《深入理解JVM》中解释是:它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;它会强制将对缓存的修改操作立即写入主存;如果是写操作,它会导致其他CPU中对应的缓存行无效。在使用volatile修饰的变量会产生内存屏障。

volatile关键字

在基本清除了java内存模型之后,我们开始详细说明一下volatile关键字,在concurrentHashMap之中,有很多的成员变量都是用volatile修饰的。被volatile修饰的变量有如下特性:

①使得变量更新变得具有可见性,只要被volatile修饰的变量的赋值一旦变化就会通知到其他线程,如果其他线程的工作内存中存在这个同一个变量拷贝副本,那么其他线程会放弃这个副本中变量的值,重新去主内存中获取

②产生了内存屏障,防止指令进行了重排序,关于这点的解释,请看下面一段代码:

 public class VolatileTest {

 int a = 0; //
int b = 1; //
volatile int c = 2; //
int d = 3; //
int e = 4; // }

在如上的代码中,因为c变量是用volatile进行修饰,那么就会对该段代码产生一个内存屏障,用以保证在执行语句3的时候语句1和语句2是绝对执行完毕的,而且在执行语句3的时候,语句4和语句5肯定没有执行。同时说明一下,在上述代码中虽然保证了语句3的执行顺序不可变换,但是语句1和语句2,语句4和语句5可能发生指令重排序哦。

hashhMap的更多相关文章

随机推荐

  1. leetcode-119-杨辉三角②

    题目描述: 第一次提交: class Solution: def getRow(self, rowIndex: int) -> List[int]: k = rowIndex pre = [1] ...

  2. Python3基础笔记_字典

    # Python3 字典 dict = {'} # 1.访问字典里的值 ,字典中没有key会报错 # 2.修改字典 print("修改之前:", dict['Beth']) dic ...

  3. Python基础笔记_Number类型

    import random import math import operator # 数字 # # 1. Python math 模块.cmath 模块 ''' Python math 模块.cma ...

  4. thinkphp 模板赋值

    如果要在模板中输出变量,必须在在控制器中把变量传递给模板,系统提供了assign方法对模板变量赋值,无论何种变量类型都统一使用assign赋值. 大理石平台检定规程 $this->assign( ...

  5. opencv-阈值分割

    关于自适应阈值,可参考:Wellner 自适应阈值二值化算法 一.大津法OTSU(最大类间方差法) 参考:非黑即白——图像分割入门篇之Otsu阈值 自适应阈值分割—大津法(OTSU算法)C++实现 灰 ...

  6. [原创]Java调用PageOffice给Word中的Table赋值

    Word中的table操作需要借助数据区域(DataRegion)实现的,要求数据区域完整的包含了整个Table的内容,这样才可以通过数据区域控制和操作table.因此,要想使用table,则必须在w ...

  7. linux操作mysql命令快速手记 — 让手指跟上思考的速度(二)

    这一篇是<mysql内建命令快速手记>的姐妹篇,废话不再赘述,直接上干货,跟老铁慢慢品 1.mysql -hlocalhost -uroot -proot,-h,-u,-p分别代表ip,u ...

  8. 16_k近邻算法总结

    1.k近邻算法属于分类算法 2.你的“邻居”来推断出你的类别 3.标准定义:如果一个样本在特征空间中的k个最相似(即特征空间中最邻近)的样本中的大多数属于某一个类别,则该样本也属于这个类别. 4.计算 ...

  9. JS基础语法之DOM02(事件)

    1.常用事件 1.onclick 单击 应用场景:为按钮绑定 2.ondbclick 双击 3.onfocus   获得焦点 4.onblur 失去焦点 应用场景:用于表单验证,用户离开某个输入框时, ...

  10. 11_数据降维PCA

    1.sklearn降维API:sklearn. decomposition 2.PCA是什么:主成分分析 本质:PCA是一种分析.简化数据集的技术. 目的:是数据维数压缩,尽可能降低原数据的维数(复杂 ...