# 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. HTML - head标签相关

    <html> <!-- head标签中主要配置浏览器的配置信息 --> <head> <!-- 网页标题标签, 用来指定网页的标题 --> <ti ...

  2. pandas的使用方法

    一.基本使用方法 # pandas引入约定 import pandas as pd from pandas import Series,DataFrame import numpy as np imp ...

  3. EF(Entity Framwork)结构

    初次接触EF,看了一些资料,将自己对EF结构的理解记录如下: EF的核心是EDM----实体数据模型(.edmx).它由三部分组成:概念模型(.csdl文件).存储模型(.ssdl文件).映射规范(. ...

  4. 北京服务业占GDP比重达81.7%

    北京服务业占GDP比重达81.7% 2017-05-17 19:46:00 来源: 中国新闻网(北京)举报   0 易信 微信 QQ空间 微博 更多 (原标题:北京服务业占GDP比重达81.7%)   ...

  5. iOS之String动态书写

    /** String动画书写出来 @param string 要写的字 @param view 父视图 @param ui_font 字体大小 @param color 字体颜色 */ - (void ...

  6. 版本控制git之二 分支 切换分支 创建分支 合并 删除

      版本控制git之二 分支   有人把 Git 的分支模型称为它的`‘必杀技特性’',也正因为这一特性,使得 Git 从众多版本控制系统中脱颖而出. 为何 Git 的分支模型如此出众呢? Git 处 ...

  7. 01-从这里开始js

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  8. 在ALV点击Key值调用TCode,跳过初始屏幕

    在开发ALV报表时,通常业务部门会要求在ALV中点击单据号,屏幕跳转到具体业务凭证中查看业务明细,效果如下图: 点击销售销售订单号或者交货单号可传入单据号直接打开销售订单或交货单,实现方式如下: 一. ...

  9. Leetcode 125.验证回文字符串(Python3)

    题目: 给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写. 说明:本题中,我们将空字符串定义为有效的回文串. 示例 1: 输入: "A man, a plan, ...

  10. 关于Canvas的坐标系

    注意Canvas的坐标系应该是这样子的: 看下面的例子: 最后的显示效果是: