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