复习Java基础知识(二)

220103

1. Java中有哪些容器(集合类)?

答:集合类主要由Collection(有Set、Queue、Tree三个子接口)和Map接口派生。有超多的实现类。

Set:无序、元素不可重复

List:有序、元素可以重复

Queue:(FIFO)队列

Map:有映射关系的集合

2. 容器线程安全和线程不安全的分别有哪些?

答:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZGSzQhDr-1641219420921)(D:\Java\图\线程安全与否.png)]

如需使用线程安全的集合类,可以用Collections工具类提供的synchronizedXxx()方法,将其包装为线程安全的集合类。

3. Map接口的实现类

答:HashMap、TreeMap、LinkedHashMap、ConcurrentHashMap。

HashMap基于hash表,继承自AbstractMap,实现接口Map。它是线程不安全的,但他是性能最好的Map实现(相比HashTable,它在put采取加锁机制,而非同步)。它允许null key与null value,元素无序。对于多线程环境应采用ConcurrentHashMap。HashMap使用链表实现entry的存储,叫做bucket/bin。默认空间为16,权值为2。

4. 描述Map put的过程

答:以HashMap为例。先判断数组是否为空,若为空进行第一次扩容;依据hash算法计算键值对索引;若当前位置为空,则直接插入;若不为空,且key存在,直接覆盖value,若key不存在,将数据链到尾端;若长度到8,则将其转为红黑树,将数据插入数中;若元素个数超过threshold,则再次进行扩容。

5. 如何得到一个线程安全的Map

答:使用ConcurrentHashMap;用工具类包装非线程安全的map

6. HashMap的特点

答:非线程安全、允许null key null value的存在

7. JDK7和JDK8中的HashMap有什么区别?

答:以前的HashMap采用数组+链表存储,一旦链表过长,效率会很低O(N)。而JDK8加入了红黑树来优化存储,它的时间复杂度降低为O(logN)

8. HashMap底层实现原理

答:基于Hash算法,通过put、get存储获取对象。

存储:调用k的hashCode得到bucket位置,进行存储

获取:调用k的hashCode得到bucket位置,使用equals确定

发生碰撞时,将使用链表/红黑树组织数据。

9. 介绍HashMapd的扩容机制

答:数组初始容量为16,是否扩充是由负载因子判断,默认0.75。

10. HashMap中的循环链表如何产生

答:循环链表如何产生可以看这篇。

11. HashMap为什么用红黑树而不用B树?

答:B/B+树多用于外存,而在数据量不高的情况下,数据都会挤到一个节点里,这时候遍历效率就退化为链表。

12. HashMap为什么线程不安全?

答:在并发执行put时,可能形成循环链表,从而引起死循环。

13. HashMap如何实现线程安全?

答:直接使用ConcurrentHashMap;使用HashTable类;用方法包装HashMap。

14. HashMap是如何解决哈希冲突的?

答:数组元素为单向链表类型。当链表长度达到一个阈值时,链表会被转为红黑树存储提高性能,反之缩小到一个阈值,红黑树会被转为单向链表。

15. 说一说HashMap和HashTable的区别

答:HashMap线程不安全,允许null key、value

HashTable线程安全,不允许null作为key和value

220104

16. HashMap与ConcurrentHashMap有什么区别?

答:HashMap线程不安全,在多线程对Map进行修改时会产生数据不一致问题,在并发插入数据时会导致链表成环,查找时会发生死循环。

ConcurrentHashMap是线程安全的。采用了减少锁粒度的方法,尽量减少因为竞争锁而导致的阻塞与冲突,而且ConcurrentHashMap的检索操作是不需要锁的。

17. 介绍一下ConcurrentHashMap是怎么实现的?

答:JDK1.7时ConcurrentHashMap结构图。它采取了分段锁技术。其中Segment继承于ReentranLock,HashEntry用于存储键值对数据。Segment结构与HashMap相似,数组+链表。

put方法:通过Key定位到Segment,随后在Segment中具体Put。先尝试获取锁,若失败则表明有其他线程存在竞争,利用scanAndLockForPut()自旋获取锁。将当前Segment中的table通过key的hashCode定位到HashEntry。遍历当前Entry,若不为空且当前key相等,则覆盖旧value;若为空则需要新建HashEntry加如Segment,同时判断是否需要扩容。解除Segment的锁。

get方法:将key经过Hash定位到具体Segment,再Hash定位到具体元素,整体过程无需加锁。

ConcurrentHashMap Segment HashEntry

JDK1.8时,由于查询遍历链表效率太低,抛弃Segment分段锁,采用CAS+synchronized保证并发安全,HashEntry改为Node,也加入了红黑树提高查询效率。

18. ConcurrentHashMap是怎么分段分组的?

答:17中put、get方法详解。

19. 说一说你对LinkedHashMap的理解

答:HashMap+双向链表以维护插入(默认)/读取顺序一致。很多方法直接继承自HashMap。

20. 请介绍LinkedHashMap的底层原理

答:在HashMap任意两个节点加了两条连线,(before指针、after指针)形成了一个以head为头结点的双向链表,故每次put进来的Entry会将它插入到双向链表的尾部。

21. 请介绍TreeMap的底层原理

答:TreeMap基于红黑树实现,时间复杂度为log(N)。映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,

22. Map和Set有什么区别?

答:Map:有映射关系的集合,key无序且不能重复。

Set:无序、元素不可重复。

23. List和Set有什么区别?

答:List:有序,可重复

Set:无序、不可重复

24. ArrayList和LinkedList有什么区别?

答:ArrayList实现基于数组,LinkedList基于双向链表。

25. 有哪些线程安全的List?

答:Vector、SynchronizedList、CopyOnWriteArrayList

26. 介绍一下ArrayList的数据结构?

答:基于数组实现。默认数组长度为10,超出限制时会增加到50%的容量。

27. 谈谈CopyOnWriteArrayList的原理

答:在对一块内存进行修改时,不直接对内存块进行写操作,而是拷贝一份内存,在新内存中进行写操作,之后,将原本只想的内存指针指到新的内存,原内存可以被回收。读取操作无需同步及锁,但写操作需要加锁。适用于多读少写。

28. 说一说TreeSet和HashSet的区别

答:元素均不能重复,线程不安全。但HashSet中元素可以为null,TreeSet内元素不能为null。HashSet无序,TreeSet支持自然排序、定制排序。HashSet基于Hash表实现,TreeSet基于红黑树实现。

29. 说一说HashSet的底层结构

答:基于HashMap实现,默认Constructor时一个初始容量为16,负载因子为0.75的HashMap。而放入HashSet的元素由HashMap的key保存,value时PRESENT,是一个静态Object对象。

30. BlockingQueue中有哪些方法,为什么这样设计?

答:阻塞队列,基于ReentranQueue。它是一个接口。

抛异常特定值阻塞超时
插入add(e)offer(e)put(e)offer(e,time,unit)
移除remove()poll()take()poll(time,unit)
检查element()peek()
  • 抛异常:如果操作无法立即执行,则抛一个异常;
  • 特定值:如果操作无法立即执行,则返回一个特定的值(一般是 true / false)。
  • 阻塞:如果操作无法立即执行,则该方法调用将会发生阻塞,直到能够执行;
  • 超时:如果操作无法立即执行,则该方法调用将会发生阻塞,直到能够执行。但等待时间不会超过给定值,并返回一个特定值以告知该操作是否成功(典型的是true / false)。
31. BlockingQueue是怎么实现的?

答:它是一个接口,实现类有ArrayBlockingQueue、DelayQueue、 LinkedBlockingQueue、PriorityBlockingQueue、SynchronousQueue等。区别主要在存储结构和对元素操作。

32. Stream(不是IOStream)有哪些方法?

答:它是Java对集合操作的优化,相比于Iterator。Stream的速度非常快。

获取数据源
进行逻辑转换操作
归约操作形成新的流
Stream<String> stream = Stream.of("a","c","v");
String[] strArray = new String[]{"a","c","v"};
stream = Arrays.stream(strArray);
List<String> list = Array.asList(strArray);
Stream = list.stream();

操作分中间操作与末端(规约)操作。

中间操作:对容器的处理过程,包括排序,筛选,映射。

  • filter(Predicate predicate):过滤Stream中所有不符合predicate的元素。
  • mapToXxx(ToXxxFunction mapper):使用ToXxxFunction对流中的元素执行一对一的转换,该方法返回的新流中包含了ToXxxFunction转换生成的所有元素。
  • peek(Consumer action):依次对每个元素执行一些操作,该方法返回的流与原有流包含相同的元素。该方法主要用于调试。
  • distinct():该方法用于排序流中所有重复的元素(判断元素重复的标准是使用equals()比较返回true)。这是一个有状态的方法。
  • sorted():该方法用于保证流中的元素在后续的访问中处于有序状态。这是一个有状态的方法。
  • limit(long maxSize):该方法用于保证对该流的后续访问中最大允许访问的元素个数。这是一个有状态的、短路方法。

末端操作:将流中数据整合输出为一个结果

  • forEach(Consumer action):遍历流中所有元素,对每个元素执行action。
  • toArray():将流中所有元素转换为一个数组。
  • reduce():该方法有三个重载的版本,都用于通过某种操作来合并流中的元素。
  • min():返回流中所有元素的最小值。
  • max():返回流中所有元素的最大值。
  • count():返回流中所有元素的数量。
  • anyMatch(Predicate predicate):判断流中是否至少包含一个元素符合Predicate条件。
  • noneMatch(Predicate predicate):判断流中是否所有元素都不符合Predicate条件。
  • findFirst():返回流中的第一个元素。
  • findAny():返回流中的任意一个元素。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值