目录
6.ArrayList和Vector的区别(带一下CopyOnWriterArrayList)
7.Array怎么转换为ArrayList,ArrayList怎么转换为Array
9.HashMap和Hashtable的区别(带一下ConcurrentHashMap)
11.HashMap的底层原理(数据结构+put()流程+resize()扩容策略)
13.什么是哈希碰撞/哈希冲突,怎么解决哈希冲突,HashMap采用的是什么策略
14.HashMap为什么不直接使用key的HashCode()函数返回的哈希码作为落槽时的索引号,HsahMap是怎么解决的呢
16.为什么重写了equals()方法,必须也要重写hashcode()方法
锁和死锁
锁是Java中用来保证线程操作原子性的一种机制
锁是数据库中用来保证数据库操作原子性的一种机制
Java中的锁有synchronized和Lock锁
synchronized是关键字,可以锁代码块,也可以锁方法
lock是类(官方推荐),只能锁代码块
我们把数据类型分为线程安全类型和线程不安全类型
如果一个数据类型需要我们自己手动加锁来保证其操作的原子性,那么他就是线程不安全的数据类型
如果一个数据类型能够自己在方法中加锁来保证其操作的原子性,那么他就是线程安全的
线程不安全 | 线程安全 |
ArrayList | 1.Vector 2.CopyOnWriteArrayList |
HashMap | 1.Hashtable 2.ConcurrentHashMap |
String,StringBuilder | StringBuffer |
int,Integer | AtomicInteger |
其他...................................................... |
产生死锁的原因
两个线程手上各自持有一个对象的锁,在没有释放的情况下,去请求对方手上的锁
产生死锁的条件是什么(死锁产生的原因)
1.互斥条件:锁要具有排他性,在同一时刻,锁只能被一个线程持有
2.请求与保持条件:一个线程因为请求其他资源被阻塞时,对以获取的资源保持不变
3.不剥夺条件:一个线程没有主动开锁释放资源之前,是不能被其他线程强行剥夺
4。循环等待条件:A线程持有资源a的锁,B线程持有资源b的锁,在互相不释放自己持有的情况下,去请求对方持有的锁,这时候会形成双方循环等待,造成永久阻塞
解决死锁
破坏任意一个条件即可
1.破坏互斥条件:用共享锁,在同一时刻,锁可以被多个线程持有
2.破坏请求与保持条件:一个线程因为请求其他资源被阻塞时,主动释放已获取的资源
3.破坏不剥夺条件:一个线程因为请求其他资源被阻塞时,主动释放取的资源
4.破坏循环等待条件:所有的线程按照同样的顺序请求资源
集合容器整理
1.Coollection和Map的区别
1.Collection和Map是官方提供的集合容器的两大体系的顶层接口
2.Collection代表单元素集合体系
3.Map代表kv键值对集合体系
4.Cpllection体系继承了Iterable迭代器接口,所有的子类都提供了迭代器的实现,Map体系没有
2.List,Set,Queue的区别
1.List,Set,Queue都是Collection体系下的子接口,分别代表三个体系
2.List体系的特点是有序,不唯一
3.Set体系的特点是无序,唯一
4.Queue体系的特点是先入先出
3.队列(Queue)和栈(Stack)的区别
1.队列是一种FIFO(First In First Out)先入先出的结构
2.栈是一种FILO(First In Last Out)先入后出的结构
Java集合体系中的LinkedList类可以实现队列和栈结构
在链表头部插入,尾部取出或者尾部插入,头部取出,就是队列(插入和取出在不同方向上进行)
在链表头部插入,头部取出或者尾部插入,尾部取出,就是栈(插入和取出在相同方向上进行)
4.Array和ArrayList的区别
Array是数组,ArrayList是类
Array是定长的(需要手动扩容),ArrayList长度可变(使用过程中自动扩容)
ArrayList的底层是Array
5.ArrayList和LinkedList的区别
1.底层数据结构实现︰ArrayList底层数据结构是动态数组,而 LinkedList的底层数据结构是双向链表
2.随机访问(即读)效率∶ArrayList比LinkedList在随机访问的时候效率要高,因为ArrayList底层是数组,可以通过索引号快速访问,LinkedList是通过二分查找法逼历链表节点进行查找的
3.增加和删除效率∶在非首尾的增加和删除操作,LinkedList 要比 ArrayList 效率要高,因为ArrayList增删操作需要大量的前移或后移,这个过程中涉及到大量的赋值操作比较耗时间,LinkedList只需要修改节点对象的左右指针即可。
4.内存空间占用:LinkedList 比 ArrayList更占内存,因为 LinkedList的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。
5.综合来说,在需要频繁读取集合中的元素时,更推荐使用ArrayList,而在插入和删除操作较多时,更推荐使用 LinkedList。
6.ArrayList和Vector的区别(带一下CopyOnWriterArrayList)
ArrayList是线程不安全的,Vector是线程安全的,ArrayList中所有的方法都没有加同步锁,Vector中所有的方法都加了synchronized同步锁。官方在JDK1.5版本中又推出了一个CopyOnWriterArrayList,使用了Lock锁实现线程安全,然后弃用了Vector,因为Lock锁的性能比synchronized锁的性能更好。
在并发编程中,如果多个线程共享一个ArrayList,那么必须考虑线程安全的问题,可以自己在代码中对ArrayList操作代码加锁,或者直接用线程安全的CopyOnWriterArrayList类
在不考虑线程安全的环境下,用ArrayList性能更好,因为加锁开锁是很耗性能的
7.Array怎么转换为ArrayList,ArrayList怎么转换为Array
1.官方提供的数组工具类Arrays中提供了一个静态方法asList()可以把数组转换为List,参数是数组,返回值是List
2.ArrayList类中提供了toArray方法,可以把ArrarList转换为Array后进行返回
8.HashSet和TreeSet的区别
HashSet和TreeSet都是Set接口下面的子类
HashSet的底层是HashMap,他将数据存储在HashMap的key中
HashSet是无序的,唯一的,因为HashMap的key是无序的,唯一的
TreeSet的底层是TreeMap,他将数据存储在TreeMap的key中
TreeSet是有序的,唯一的,因为TreeMap的key是有序的,唯一的
9.HashMap和Hashtable的区别(带一下ConcurrentHashMap)
HashMap是线程不安全的,Hashtable是线程安全的,HashMapt中所有的方法都没有加同步锁,Hashtable中所有的方法都加了synchronized同步锁。官方在JDK1.5版本中又推出了一个ConcurrentHashMap,使用了Lock锁实现线程安全,然后弃用了Hashtable,因为Lock锁的性能比synchronized锁的性能更好。
在并发编程中,如果多个线程共享一个HashMap,那么必须考虑线程安全的问题,可以自己在代码中对HashMap操作代码加锁,或者直接用线程安全的ConcurrentHashMap类
在不考虑线程安全的环境下,用HashMap性能更好,因为加锁开锁是很耗性能的
对Null key和Null value支持:HashMap支持key为null,但只能有一个,Hashtable不支持key为null,会直接抛NPE,Hashtable和HashMap支持value为空,不限制个数
ConcurrentHashMap的key和value都不支持null
HashMap在1.8以后,设置了阈值=8,当链表长度超过阈值的时候,会转换为红黑树以减少检索时间,Hashtable被弃用了,没有更新
初始容量大小和扩容容量大小的区别:
HashMap初始容量是16,扩容策略是原来的2倍
Hashtable默认初始容量是11,扩容策略是原来的2n+1
HashMap如果手动指定了初始容量,不是n的2次方,他也会找到最近的一个2的n次方作为初始容量
Hashtable如果手动指定了初始容量,会直接使用给定的大小
Hashtable采用的锁全表的机制,ConcurrentHashMap采用了分段锁的设计,锁粒度更细,性能更好
10.HashMap和TreeMap的区别
HashMap底层是数组+链表/红黑树, key是无序的, 唯一的
TreeMap底层是红黑树, key是有序的, 唯一的
HashMap的性能比TreeMap更好, 但如果需要一个有序的key的集合, 需要使用TreeMap
对于在Map中插入、删除和定位元素这类操作,HashMap是最好的选择。然而,假如你需要对一个有序的key集合进行遍历,TreeMap是更好的选择。基于你的collection的大小,也许向HashMap中添加元素会更快,将map换为TreeMap进行有序key的遍历
11.HashMap的底层原理(数据结构+put()流程+resize()扩容策略)
HashMap在JDK1.8之前是数组+链表, JDK1.8之后是数组+链表/红黑树
1. 根据key的hashCode计算出数组index
2. 落槽时
1. 如果数组中节点为null, 创建新的节点对象, 把k,v存储在节点对象中, 把节点对象存储在数组中
2. 如果数组的节点不为null, 判断节点的key与插入元素的key是否相等
1. 相等, 直接用新的k,v覆盖原节点中的k,v
2. 不相等, 判断此时节点是否为红黑树
1. 是红黑树, 创建红黑树节点对象存储k,v, 插入到红黑树中
2. 不是红黑树, 创建链表节点对象存储k,v, 插入到链表中, 判断链表长度是否大于阈值8
1. 大于阈值8, 链表转换为红黑树
3. 判断++size 是否大于阈值, 是就扩容
HashMap默认初始容量是16
resize()方法是在size大于阈值时或者初始化时,就调用resize方法进行扩容
每次扩容的时候始终是原数组长度的2倍, 即长度永远是2的n次方
扩容后节点对象的位置要么在原位置,要么偏移到两倍的位置
12.HashMap的长度为什么是2的幂次方
为了能让 HashMap 存取高效,尽量较少碰撞,也就是要尽量把数据分配均匀,每个链表/红黑树长度大致相同。这个实现就是把数据存到哪个链表/红黑树中的算法。
13.什么是哈希碰撞/哈希冲突,怎么解决哈希冲突,HashMap采用的是什么策略
如果有两个不同字符串通过同样的哈希算法计算出来的哈希码是一样的,则称他们发生了哈希碰撞,哈希冲突,解决方法:
- 开放地址法
- 再哈希法
- 拉链法(链地址法)HashMap默认使用的就是这种
- 建立公共溢出区
14.HashMap为什么不直接使用key的HashCode()函数返回的哈希码作为落槽时的索引号,HsahMap是怎么解决的呢
这题也可以这样问“HashMap的底层是如何计算key落槽时的索引的“
答:hashCode()
方法返回的是int整数类型,其范围为-(2 ^ 31)~(2 ^ 31 - 1),约有40亿个映射空间,而HashMap的容量范围是在16(初始化默认值)~2 ^ 30,HashMap通常情况下是取不到最大值的,并且设备上也难以提供这么多的存储空间,从而导致通过hashCode()
计算出的哈希值可能不在数组大小范围内,进而无法匹配存储位置;
那怎么解决呢?
- HashMap自己实现了自己的
hash()
方法,通过两次扰动使得它自己的哈希值高低位自行进行异或运算,降低哈希碰撞概率也使得数据分布更平均; - 在保证数组长度为2的幂次方的时候,使用
hash()
运算之后的值与运算(&)(数组长度 - 1)来获取数组下标的方式进行存储,这样一来是比取余操作更加有效率,二来也是因为只有当数组长度为2的幂次方时,h&(length-1)才等价于h%length,三来解决了“哈希值与数组大小范围不匹配”的问题;
15.== 和 equals()方法的区别
==和equals()都可以用于比较,语法是a ==b 或者a.equals(b)
== 比较的是内存地址
equals()方法是Object类中方法,可以被任意类继承或重写,通过看官方Object类源码知道equals()方法默认也是用==比较内存地址
如果想要修改equals()方法的比较规则,可以重写equals()方法
String类就重写equals()方法的比较规则,由默认的比较两个字符串对象的内存地址,修改为比较字符串中每个字符是否相等
因为堆区中可能会出现两个一模一样的字符串对象,但内存地址不一样,所以字符串的比较必须用equals()方法,否则可能会出现两个内容一模一样的字符串,因为地址不一样比较后出现不相等的情况。
16.为什么重写了equals()方法,必须也要重写hashcode()方法
HashMap的底层采用了key的hashcode()来计算数组的索引Index
如果数组[index]为null说明key不存在,直接落槽插入
如果数组[index]不为null说明该位置有key存在,但不能一定说明已存在的key于要插入的key重复,因为可能会发生哈希碰撞,此时应该进一步用equals方法比较已存在的key于要插入的key是否相等。如果相等就说明一定是重复的,应该覆盖,如果不相等,说明发生了哈希碰撞,那么应该插入在链表中
重写equals方法的目的是为了不去比较两个对象的内存地址,改为比较对象的内容,如果一个类重写了equals,没有重写hashcode,就可能出现两个地址不同的对象equals比较相等,但是hashcode比较不相等,这样会违反HashMap的唯一性,因此,重写了equals方法必须也要重写hashcode方法,且必须满足两个对象equals相等,hashcode也必须相等