文章目录
- 常用的集合类有哪些?
- Collection和Collections的区别,介绍一下Collectionn的结构?
- ArrayList和LinkedList的区别是什么?
- 什么是双向链表?双向链表和单链表的区别?
- ArrayList和LinkedList哪个需要扩容?扩容机制是怎么样的?
- List和Set的区别?
- 说一下 HashSet 的实现原理?
- 如何遍历list并且删除元素?
- 多线程场景下如何使用 ArrayList?
- HashMap底层的数据结构?
- 说一下HashMap的put方法?
- HashMap的扩容机制原理?
- HashMap和HashTable的区别?
- HashMap是有序的吗?如何实现有序?
- CopyOnWriteArrayList的底层原理是怎样的
- BlockingQueue是什么?
- 如何实现 Array 和 List 之间的转换?
- comparable 和 comparator的区别?
- TreeMap 和 TreeSet 在排序时如何比较元素?Collections 工具类中的 sort()方法如何比较元素?
常用的集合类有哪些?
Map接口和Collection接口是所有集合框架的父接口
- Collection接口的子接口包括:Set接口和List接口
- Map接口的实现类主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及Properties等
- Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等
- List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector
Collection和Collections的区别,介绍一下Collectionn的结构?
Java.util.Collection是一个集合接口,Collection接口在Java类库中有非常多的实现。
Java.util.Collections是针对集合类提供的一个帮助类,它提供了一系列的静态方法实现
对集合的搜索、排序、线程安全化的操作。
ArrayList和LinkedList的区别是什么?
数据结构:ArrayList底层是动态数组,而LinkedList是双向链表的数据结构
随机访问:由于ArrayList底层是数组,通过下标获取数据所以效率较高
增加删除:在非首尾的增加和删除操作,LinkedList要比ArrayList效率高
因为ArrayList增加删除要影响其它元素的下标
什么是双向链表?双向链表和单链表的区别?
链表是指内存的底层元素和元素不是连续存储的,只是靠着引用指向来形成先后关系
ArrayList和LinkedList哪个需要扩容?扩容机制是怎么样的?
ArrayList才需要扩容
ArrayList() 创建对象时,不会定义底层数组的长度,当第一次调用add(E e) 方法时,初始化定义底层数组的长度为10,之后调用add(E e)时,如果需要扩容,则调用grow(int minCapacity) 进行扩容,长度为原来的1.5倍。
List和Set的区别?
Set 集合要求元素必须唯一 对顺序没有要求 SortedSet 要求唯一且有序
List 集合有序而且不唯一 有序:默认按照添加的顺序 可以指定排序
Collections.sort(list 集合,比较器)
说一下 HashSet 的实现原理?
HashSet 是基于 HashMap 实现的,HashSet的值存放于HashMap的key上,HashMap的value统一为PRESENT,因此 HashSet 的实现比较简单,相关 HashSet 的操作,基本上都是直接调用底层HashMap 的相关方法来完成,HashSet 不允许重复的值。
如何遍历list并且删除元素?
可以使用迭代器的 remove()方法
否则在遍历list的过程中直接对list集合进行删除操作会触发CME并发修改异常
为了防止并发错误的出现 util包当中所有的集合都会有一套比较完整的判断机制
这套判断机制具体指集合本身就总共操作次数
当我们得到迭代器的时候 迭代器会同步记录这个操作次数
然而当我们直接操作集合进行添加或者删除后,迭代器本身更新这个操作
次数从而在下次调用next()方法的时候导致校验并不成功(迭代器会认为有其它线程跟它一起操作数据)可能导致并发错误。
多线程场景下如何使用 ArrayList?
ArrayList 不是线程安全的,如果遇到多线程场景,可以通过 Collections 的 synchronizedList 方法将其转换成线程安全的容器后再使用
List<String> synchronizedList = Collections.synchronizedList(list);
synchronizedList.add("aaa");
synchronizedList.add("bbb");
for (int i = 0; i < synchronizedList.size(); i++) {
System.out.println(synchronizedList.get(i));
}
HashMap底层的数据结构?
在JDK 1.7的时候HashMap底层是一个数组+链表的数据结构,JDK 1.8中底层改为了数组+链表+红黑树,加红黑树的目的是为了提高整体的查询效率。
1.7中链表插入采用的是头插法,1.8中链表使用的是尾插法,因为1.8中插入key和value的时候需要判断元素个数,所以需要遍历链表统计元素个数。
1.7中hash算法比较复杂,存在各种右移与异或运算,1.8中进行了简化,因为复杂的哈希
算法的目的就是提高散列性,来提供HashMap的整体效率,而1.8中新增了红黑树,所以可以适当简化哈希算法,节省CPU资源。
说一下HashMap的put方法?
1.根据Key通过hash运算得出数组下标
2.如果数组下标位置元素为空,则将key和value封装为一个Entry对象(JDK 1.7中是一个Entry JDK 1.8中是一个Node对象)并放入该位置
3.如果数组下标元素不为空
(1)如果是JDK 1.7,则先判断是否需要扩容,如果需要扩容就进行扩容,如果不需要扩容就生成Entry对象,并且使用头插法添加到当前位置的链表中
(2)如果是 JDK 1.8,则会先判断当前位置的Node的类型,看是红黑树Node,还是链表Node
1)如果是红黑树Node,则将key和value封装为一个红黑树节点并添加到红黑树中,在这个过程中会先判断红黑树中是否存在key,如果存在则更新value
2) 如果是链表,则将key和value封装为一个Node并通过尾插法插入到链表的最后,因为是尾插法,所以需要遍历链表,在遍历链表的过程中会判断是否存在当前key,如果存在则
更新,当遍历完链表后,将新链表Node插入链表中,插入到链表后,会看当前链表的节点个数,如果大于等于8,那么将会将链表转化为红黑树。
3)将key和value封装为Node插入到链表或红黑树后,再判断是否需要扩容,如果需要就扩容,如果不需要就结束Put方法。
HashMap的扩容机制原理?
JDK 1.7
1.先生成新数组
2.遍历老数组中每个位置上的链表上的每个元素
3.取出每个元素的key,并基于新数组长度,计算出每个元素在新数组中的下标
4.将元素添加到新数组中
5.所有元素转移完成之后,将新数组赋值给HashMap对象的table属性
JDK 1.8
1.先生成新数组
2.遍历老数组中的每个位置上的链表或红黑树
3.如果是链表,则直接将链表中每个元素重新计算下标,并添加到新数组中
4.如果是红黑树,则先遍历红黑树,先计算出红黑树中每个元素对应在新数组
的下标位置。
(1)统计每个下标位置的元素个数
(2)如果该位置下的元素超过了8 ,则生成一个新的红黑树,并将根节点添加到
新数组的对应位置。
(3)如果该位置下的元素个数没有超过8,那么则生产厂一个链表,并将链表的头节点
添加到新数组的对应位置。
5.所有元素转移完成之后,将新数组赋值给HashMap对象的table属性
HashMap和HashTable的区别?
- HashTable 底层大量的方法使用了Synchronized()所以线程安全
- HashMap 无论主键对象 还是值对象 都允许添加null Hashtable 无论主键还是值 都不允许出现null
- HashMap 默认分为16个小组 分组组数可以随意指定 但是最终结果一定是2的n次方数 HashMap : hash & (分组组数-1)Hashtable 默认分为11个小组 分组组数可以随意指定 Hashtable : hash % 分组组数
- HashMap since JDK1.2 Hashtable JDK1.0
HashMap是有序的吗?如何实现有序?
HashMap是无序的
使用TreeMap
CopyOnWriteArrayList的底层原理是怎样的
- ⾸先CopyOnWriteArrayList内部也是⽤过数组来实现的,在向CopyOnWriteArrayList添加元素
时,会复制⼀个新的数组,写操作在新数组上进⾏,读操作在原数组上进⾏ - 并且,写操作会加锁,防⽌出现并发写⼊丢失数据的问题
- 写操作结束之后会把原数组指向新数组
- CopyOnWriteArrayList允许在写操作时来读取数据,⼤⼤提⾼了读的性能,因此适合读多写少的应
⽤场景,但是CopyOnWriteArrayList会⽐较占内存,同时可能读到的数据不是实时最新的数据,所以不适合实时性要求很⾼的场景
BlockingQueue是什么?
Java.util.concurrent.BlockingQueue是一个队列,在进行检索或移除一个元素的时候,它会等待队列变为非空;当在添加一个元素时,它会等待队列中的可用空间。BlockingQueue接口是Java集合框架的一部分,主要用于实现生产者-消费者模式。我们不需要担心等待生产者有可用的空间,或消费者有可用的对象,因为它都在BlockingQueue的实现类中被处理了。Java提供了集中BlockingQueue的实现,比如ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue,、SynchronousQueue等。
如何实现 Array 和 List 之间的转换?
Array 转 List: Arrays. asList(array) ;
List 转 Array:List 的 toArray() 方法。
comparable 和 comparator的区别?
comparable接口实际上是出自java.lang包,它有一个 compareTo(Object obj)方法用来排序
comparator接口实际上是出自 java.util 包,它有一个compare(Object obj1, Object obj2)方法用
来排序
一般我们需要对一个集合使用自定义排序时,我们就要重写compareTo方法或compare方法,当我们
需要对某一个集合实现两种排序方式,比如一个song对象中的歌名和歌手名分别采用一种排序方法的
话,我们可以重写compareTo方法和使用自制的Comparator方法或者以两个Comparator来实现
TreeMap 和 TreeSet 在排序时如何比较元素?Collections 工具类中的 sort()方法如何比较元素?
TreeSet 要求存放的对象所属的类必须实现 Comparable 接口,该接口提供了比较元素的 compareTo()
方法,当插入元素时会回调该方法比较元素的大小。TreeMap 要求存放的键值对映射的键必须实现
Comparable 接口从而根据键对元素进 行排 序。
Collections 工具类的 sort 方法有两种重载的形式,
第一种要求传入的待排序容器中存放的对象比较实现 Comparable 接口以实现元素的比较;
第二种不强制性的要求容器中的元素必须可比较,但是要求传入第二个参数,参数是Comparator 接口
的子类型(需要重写 compare 方法实现元素的比较),相当于一个临时定义的排序规则,其实就是通
过接口注入比较元素大小的算法,也是对回调模式的应用(Java 中对函数式编程的支持)。