1、Java集合概述
Java集合,也叫作容器。由两大接口派生而来:Collection接口,用于存放单一元素;Map接口,主要用于存放键值对。对于Collection接口,下面又有三个主要的子接口:List、Set、Queue
2、说说List,Set,Queue,Map的区别:
3、如何选用集合?
根据集合的特点来选择合适的集合:
4、为什么要使用集合?
当我们需要存储一组类型相同的数据时,数组是最常用且最基本的容器之一,但是,使用数组存储对象存在一些不足,因为在实际开发中,存储的数据类型多种多样且数量不确定时,这时,Java集合就派上用场了。与数组相比,Java集合提供了更灵活、更有效的方法来存储多个数据对象。Java集合框架中的各种集合类和接口可以存储不同类型和数量的对象,同时还具有多样化的操作方式。相较于数组,Java集合的优势在于它们的大小可变、支持泛型、具有内建算法等。总的来说,Java集合提高了数据的存储和处理灵活性,可以更好地适应现代软件开发中多样化的数据需求,并支持高质量的代码编写。
5、List
ArrayList和Array(数组)的区别?
ArrayList可以添加null值吗
ArrayList与LinkedList区别?
是否保证线程安全:ArrayList和LinkedList都是不同步的,也就是不保证线程安全
底层数据结构:ArrayList底层使用的是Object数组;LinkedList底层使用的是双向链表数据结构。
插入和删除是否受元素位置的影响
是否支持快速随机访问:LinkedList不支持高效的随机元素访问,而ArrayList(因为实现了RandomAccess接口)支持。快速随机访问就是通过元素的序号快速获取元素对象。
内存空间占用:ArrayList的空间浪费主要体现在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)
我们在项目中一般是不会使用到LinkedList的,需要用到LinkedList的场景几乎都可以使用ArrayList来代替,并且,性能通常会更好!
6、Set
无序性和不可重复性的含义是什么
比较HashSet、LinkedHashSet和TreeSet三者的异同
7、Queue
Queue与Deque的区别
ArrayDeque与LinkedList的区别
二者都实现了Deque接口,两者都具有队列的功能,区别如下:
从性能的角度上,选用ArrayDeque来实现队列要比LinkedList更好。此外,ArrayDeque也可以用于实现栈
说一说PriorityQueue
PriorityQueue是在JDK1.5中被引入,其与Queue的区别在于元素出队顺序是与优先级相关的,即总是优先级最高的元素先出队
什么是BlockingQueue
BlockingQueue(阻塞队列)是一个接口,继承自Queue。BlockingQueue阻塞的原因是其支持当队列没有元素时一直阻塞,直到有元素;还支持如果队列已满,一直等到队列可以放入新元素时再放入。
8、Map
HashMap和Hashtable的区别
HashMap和TreeMap区别
TreeMap和HashMap都继承自AbstractMap,但是,TreeMap还实现了NavigableMap接口和SortedMap接口。实现NavigableMap接口让TreeMap有了对集合内元素的搜索的能力;实现SortedMap接口TreeMap有了对集合中的元素根据键排序的能力。默认是按Key的升序排序,不过我们也可以指定排序的比较器。
HashMap的底层实现
HashMap多线程操作导致死循环问题
JDK1.7及之前的HashMap在多线程环境下扩容操作可能存在死循环问题,这是由于当一个桶位中有多个元素需要进行扩容时,多个线程同时对链表进行操作,头插法可能会导致链表中的节点指向错误的位置,从而形成一个环形链表,进而使得查询元素的操作陷入死循环无法结束。为了解决这个问题,JDK1.8 版本的 HashMap 采用了尾插法而不是头插法来避免链表倒置,使得插入的节点永远都是放在链表的末尾,避免了链表中的环形结构。但是还是不建议在多线程下使用HashMap,因为多线程下使用HashMap还是会存在数据覆盖的问题。并发环境下,推荐使用ConcurrentHashMap
HashMap为什么线程不安全?
JDK1.7及之前版本,在多线程环境下,HashMap扩容时会造成死循环和数据丢失的问题。JDK1.8之后,在HashMap中,多个键值对可能会被分配到同一个桶(bucket),并以链表或红黑树的形式存储。多个线程对HashMap的Put操作会导致线程不安全,具体来说会有数据覆盖的风险。
HashMap常见的遍历方式
存在阻塞时parallelStream性能最高,非阻塞时parallelStream性能最低
HashMap 遍历从大的方向来说,可分为以下 4 类:
但每种类型下又有不同的实现方式,因此具体的遍历方式又可以分为以下 7 种:
ConcurrentHashMap线程安全的具体实现方式/底层具体实现
Java8中,ConcurrentHashMap取消了Segment分段锁,采用Node+CAS+synchronized来保证并发安全。数据结构跟HashMap1.8的结构类似,数组+链表/红黑二叉树。Java 8 在链表长度超过一定阈值(8)时将链表(寻址时间复杂度为 O(N))转换为红黑树(寻址时间复杂度为 O(log(N)))。
Java8中,锁粒度更细,synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,就不会影响其他Node的读写,效率大幅提升。
9、Java集合使用注意事项总结
集合判空
判断所有集合内部的元素是否为空,使用isEmpty()方法,而不是size()==0的方式
因为isEmpty()方法的可读性更好,并且时间复杂度为O(1)
集合转Map
在使用 java.util.stream.Collectors 类的 toMap() 方法转为 Map 集合时,一定要注意当 value 为 null 时会抛 NPE 异常。
集合遍历
不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁。
集合去重
可以利用 Set 元素唯一的特性,可以快速对一个集合进行去重操作,避免使用 List 的 contains() 进行遍历去重或者判断包含操作。
集合转数组
使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一致、长度为 0 的空数组。
数组转集合
使用工具类Array.aslist()把数组转换成集合时,不能使用其修改集合相关的方法,它的add/remove/clear方法会抛出UnsupportedOperationException异常