一、HashMap面试
https://blog.csdn.net/u012512634/article/details/72735183
HashMap的特性:HashMap存储键值对,实现快速存取数据;允许null键/值;非同步;不保证有序(比如插入的顺序)。
HashMap底层使用哈希表(数组 + 链表)实现。里边最重要的两个方法put、get,使用put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。 在Java 8中,如果一个bucket中碰撞冲突的元素超过某个限制(默认是8),则使用红黑树来替换链表,从而提高速度。
put 方法过程
注:数组里可以存储元素的位置被称为“桶(bucket)”,每个 bucket 都有其指定索引,系统可以根据其索引快速访问该 bucket 里存储的元素。
1.对key的hashCode做hash操作,然后再计算在bucket中的index(1.5 HashMap的哈希函数);
2.如果没碰撞直接放到bucket里;
3.如果碰撞了,以链表的形式存在buckets后;
4.如果节点已经存在就替换old value(保证key的唯一性)
5.如果bucket满了(超过阈值,阈值=loadfactor*current capacity,load factor默认0.75),就要resize。
get()方法的工作原理?
通过对key的hashCode()进行hashing,并计算下标( n-1 & hash),从而获得buckets的位置。如果产生碰撞,则利用key.equals()方法去链表或树(java1.8)中去查找对应的节点中查找对应的节点。
HashMap 怎样解决冲突?
HashMap中处理冲突的方法实际就是链地址法,内部数据结构是数组+单链表。
能否让HashMap同步?
HashMap可以通过下面的语句进行同步:Map m = Collections.synchronizeMap(hashMap);
二、ConcurrentHashMap
https://blog.csdn.net/itachi85/article/details/51816668
1、线程不安全的HashMap
HashMap在并发执行put操作是会引起死循环,是因为多线程会导致HashMap的Entry链表形成环形数据结构,一旦形成唤醒数据结构,Entry的next节点永远不为空,就会产生死循环。
https://blog.csdn.net/zhuqiuhui/article/details/51849692
2、效率低下的HashTable
HashTable使用synchronized来保证线程的安全,但是在线程竞争激烈的情况下HashTable的效率非常低下。当一个线程访问HashTable的同步方法,其他方法访问HashTable的同步方法时,会进入阻塞或者轮询状态。
3、ConcurrentHashMap的锁分段技术
HashTable容器在竞争激烈的并发环境效率低下的原因是所有访问HashTable的线程都必须竞争同一把锁,假如容器有多把锁,每一把锁用于锁住容器中一部分数据,那么多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效提高并发访问率,这就是ConcurrentHashMap的锁分段技术。将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一段数据的时候,其他段的数据也能被其他线程访问。
4、ConcurrentHashMap使用什么技术来保证线程安全?
jdk1.7:Segment+HashEntry来进行实现的(锁加数据);
jdk1.8:放弃了Segment臃肿的设计,采用Node+CAS+Synchronized来保证线程安全;
https://zhuanlan.zhihu.com/p/81937382
(1)jdk1.7
ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment是一种可重入锁ReentrantLock,在ConcurrentHashMap里扮演锁的角色,HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构, 一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素, 每个Segment守护者一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。 插入和获取时通过散列算法定位到segment。
(2)jdk1.8
从Java1.8 版本开始 ConcurrentHashMap 不再采用 Segment 实现,而是改用 Node,Node 是一个链表的结构,每个节点可以引用到下一个节点(next)。
- Node类
Node是最核心的内部类,包装了key-value键值对,所有插入ConcurrentHashMap的数据都包装在这里面。 它与HashMap中的定义很相似,但是有一些差别它对value和next属性设置了volatile同步锁,它不允许调用setValue方法直接改变Node的value域,它增加了find方法辅助map.get()方法。
- TreeNode类
树节点类,另外一个核心的数据结构。 当链表长度过长的时候,会转换为TreeNode。 但是与HashMap不相同的是,它并不是直接转换为红黑树,而是把这些结点包装成TreeNode放在TreeBin对象中,由TreeBin完成对红黑树的包装。 而且TreeNode在ConcurrentHashMap继承自Node类,而并非HashMap中的集成自LinkedHashMap.Entry
- TreeBin
这个类并不负责包装用户的key、value信息,而是包装的很多TreeNode节点。它代替了TreeNode的根节点,也就是说在实际的ConcurrentHashMap“数组”中,存放的是TreeBin对象,而不是TreeNode对象,这是与HashMap的区别。
- ForwardingNode
一个用于连接两个table的节点类。它包含一个nextTable指针,用于指向下一张表。而且这个节点的key value next指针全部为null,它的hash值为-1。这里面定义的find的方法是从nextTable里进行查询节点,而不是以自身为头节点进行查找。
put函数
put方法依然沿用HashMap的put方法的思想,根据hash值计算这个新插入的点在table中的位置i,如果i位置是空的,直接放进去,否则进行判断,如果i位置是树节点,按照树的方式插入新的节点,否则把i插入到链表的末尾。ConcurrentHashMap中依然沿用这个思想,有一个最重要的不同点就是ConcurrentHashMap不允许key或value为null值。另外由于涉及到多线程,put方法就要复杂一点。在多线程中可能有以下两个情况:
如果一个或多个线程正在对ConcurrentHashMap进行扩容操作,当前线程也要进入扩容的操作中。这个扩容的操作之所以能被检测到,是因为transfer方法中在空结点上插入forward节点,如果检测到需要插入的位置被forward节点占有,就帮助进行扩容。
如果检测到要插入的节点是非空且不是forward节点,就对这个节点加锁,这样就保证了线程安全。尽管这个有一些影响效率,但是还是会比hashTable的synchronized要好得多。
整体流程就是
1、首先定义不允许key或value为null的情况放入 对于每一个放入的值,首先利用spread方法对key的hashcode进行一次hash计算,由此来确定这个值在table中的位置。如果这个位置是空的,那么直接放入,而且不需要加锁操作。
2、如果这个位置存在结点,说明发生了hash碰撞,首先判断这个节点的类型。如果是链表节点(fh>0),则得到的结点就是hash值相同的节点组成的链表的头节点。需要依次向后遍历确定这个新加入的值所在位置。如果遇到hash值与key值都与新加入节点是一致的情况,则只需要更新value值即可。否则依次向后遍历,直到链表尾插入这个结点。 如果加入这个节点以后链表长度大于8,就把这个链表转换成红黑树。如果这个节点的类型已经是树节点的话,直接调用树节点的插入方法进行插入新的值。
get方法
给定一个key来确定value的时候,必须满足两个条件 key相同 hash值相同,对于节点可能在链表或树上的情况,需要分别去查找。
三、接口和抽象类的区别
https://www.zhihu.com/question/20149818
接口和抽象类都是继承树的上层,他们的共同点如下:
1) 都是上层的抽象层。
2) 都不能被实例化
3) 都能包含抽象的方法,这些抽象的方法用于描述类具备的功能,但是不比提供具体的实现。
他们的区别如下:
1) 在抽象类中可以写非抽象的方法,从而避免在子类中重复书写他们,这样可以提高代码的复用性,这是抽象类的优势;接口中只能有抽象的方法。
2) 一个类只能继承一个直接父类,这个父类可以是具体的类也可是抽象类;但是一个类可以实现多个接口。
1、Java语言中类的继承是单继承原因是:当子类重写父类方法的时候,或者隐藏父类的成员变量以及静态方法的时候,JVM使用不同的绑定规则。如果一个类有多个直接的父类,那么会使绑定规则变得更复杂。为了简化软件的体系结构和绑定机制,java语言禁止多继承。
2、接口可以多继承,是因为接口中只有抽象方法,没有静态方法和非常量的属性,只有接口的实现类才会重写接口中方法。因此一个类有多个接口也不会增加JVM的绑定机制和复杂度。
3、对于已经存在的继承树,可以方便的从类中抽象出新的接口,但是从类中抽象出新的抽象类就不那么容易了,因此接口更有利于软件系统的维护和重构。
链接:https://www.zhihu.com/question/20149818/answer/142270191
来源:知乎
四、HashMap,LinkedHashMap,TreeMap的有序性
https://www.cnblogs.com/Andrew520/p/8587771.html
https://blog.csdn.net/hackersuye/article/details/84350756
Map,它保存了键-值对的一对一的关系形式,并用哈希值来作为存贮的索引依据,在查找、插入以及删除时的时间复杂度都为O(1),是一种在程序中用的最多的几种数据结构。Java在java.util工具包中实现了Map接口,来作为各大Map实现类的规范,其中主要的Map实现类有三个,分别是:HashMap、TreeMap以及LinkedHashMap类,
1、HashMap 实际上是一个链表的数组。HashMap 的一个功能缺点是它的无序性,被存入到 HashMap 中的元素,在遍历 HashMap 时,其输出是无序的。如果希望元素保持输入的顺序,可以使用 LinkedHashMap 替代。
2、LinkedHashMap继承自HashMap,具有高效性,同时在HashMap的基础上,又在内部增加了一个链表,用以存放元素的顺序。LinkedHashMap内部多了一个双向循环链表的维护,该链表是有序的,可以按元素插入顺序或元素最近访问顺序(LRU)排列,简单地说:LinkedHashMap=散列表+循环双向链表。
3、TreeMap类,如其名,是一个以树结构来实现Map的一个类。 TreeMap是以红黑树来做存贮结构的,因为TreeMap是有序的,所以它存贮的对象都是必须实现Comparable或者Comparator接口的,以便于排序。 默认是不用外部的比较器的,因为传进来的的对象是实现了Comparable或者Comparator接口。
4、LinkedHashMap 是根据元素增加或者访问的先后顺序进行排序,即按照put进的顺序排列,TreeMap 则是基于元素的固有顺序 (由 Comparator 或者 Comparable 确定)。而 TreeMap 则根据元素的 Key 进行排序。
HashMap怎么进行存贮键值对,而让其查找的时间复杂度为O(1)呢?
是利用每个键的哈希值,在进行存贮的时候,先计算键的哈希值,通过哈希值来确定索引值,这样就可以完成时间复杂度为O(1)的操作。
Mysql 的锁机制
https://zhuanlan.zhihu.com/p/29150809
引擎
https://blog.csdn.net/zgrgfr/article/details/74455547
索引
https://my.oschina.net/liughDevelop/blog/1788148