Java高频面试题(集合)

1、Java集合体系

结构图
在这里插入图片描述
这里比较常见的就是List,Set,Map接口以及他们的实现类。

2. ArrayList和LinkedList区别?

  1. ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
  2. 对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。
  3. 对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。

ArrayList和Vector的区别

两个类都实现了List接口(List接口继承了 Collection 接口),他们都是有序集合,即存储在这两个集合中的元素的位置都是有顺序的,相当于一种动态的数组,我们以后可以按位置索引号取出某个元素,并且其中的数据是允许重复的,这是与 HashSet 之类的集合的最大不同处, HashSet 之类的集合不可以按索引号去检索其中的元素,也不允许有重复的元素。
ArrayList与Vector的区别主要包括两个方面:
同步性:
Vector 是线程安全的,也就是说是它的方法之间是线程同步的,而 ArrayList 是线程序不安全的,它的方法之间是线程不同步的。如果只有一个线程会访问到集合,那最好是使用 ArrayList ,因为它不考虑线程安全,效率会高些;如果有多个线程会访问到集合,那最好是使用 Vector ,因为不需要我们自己再去考虑和编写线程安全的代码。
数据增长:
ArrayList 与 Vector 都有一个初始的容量大小,当存储进它们里面的元素的个数超过了容量时,就需要增加 ArrayList 与 Vector 的存储空间,每次要增加存储空间时,不是只增加一个存储单元,而是增加多个存储单元,每次增加的存储单元的个数在内存空间利用与程序效率之间要取得一定的平衡。
Vector 默认增长为原来两倍,而 ArrayList 的增长策略在文档中没有明确规定(从源代码看到的是增长为原来的1.5倍)。 ArrayList 与 Vector 都可以设置初始的空间大小, Vector 还可以设置增长的空间大小,而 ArrayList 没有提供设置增长空间的方法。
总结:即Vector增长原来的一倍, ArrayList 增加原来的0.5倍。

3、HashMap和Hashtable的区别

HashMap 是 Hashtable 的轻量级实现(非线程安全的实现),他们都完成了Map接口,
主要区别在于 HashMap 允许空(null)键值(key),由于非线程安全,在只有一个线程访问的情况下,效率要高于 Hashtable 。
HashMap 允许将null作为一个entry的key或者value,而 Hashtable 不允许。
HashMap 把 Hashtable 的 contains 方法去掉了,改成 containsvalue 和 containsKey 。因为contains方法容易让人引起误解。
Hashtable 继承自 Dictionary 类,而 HashMap 是Java1.2引进的Map interface的一个实现。
最大的不同是, Hashtable 的方法是 Synchronize 的,而 HashMap 不是,在多个线程访问 Hashtable时,不需要自己为它的方法实现同步,而 HashMap 就必须为之提供同步。

4、Java中的同步集合与并发集合有什么区别?

同步集合与并发集合都为多线程和并发提供了合适的线程安全的集合,不过并发集合的可扩展性更高。
在Java1.5之前程序员们只有同步集合来用且在多线程并发的时候会导致争用,阻碍了系统的扩展性。
Java5介绍了并发集合 ConcurrentHashMap ,不仅提供线程安全还用锁分离和内部分区等现代技术提高了可扩展性。
同步 HashMap , Hashtable , HashSet , Vector , ArrayList 相比他们并发的实现
( ConcurrentHashMap , CopyOnWriteArrayList , CopyOnWriteHashSet )会慢得多。造成如此慢的
主要原因是锁, 同步集合会把整个Map或List锁起来,而并发集合不会。并发集合实现线程安全是通过使用先进的和成熟的技术像锁剥离。
比如 ConcurrentHashMap 会把整个Map 划分成几个片段,只对相关的几个片段上锁,同时允许多线程访问其他未上锁的片段。
同样的, CopyOnWriteArrayList 允许多个线程以非同步的方式读,当有线程写的时候它会将整个List复制一个副本给它。
如果在读多写少这种对并发集合有利的条件下使用并发集合,这会比使用同步集合更具有可伸缩性。

5、poll()方法和remove()方法区别?

poll() 和 remove() 都是从队列中取出一个元素,但是 poll() 在获取元素失败的时候会返回空,但是remove() 失败的时候会抛出异常。

6、 Comparator和Comparable的区别?

相同点
都是用于比较两个对象“顺序”的接口
都可以使用Collections.sort()方法来对对象集合进行排序
不同点
Comparable位于java.lang包下,而Comparator则位于java.util包下
Comparable 是在集合内部定义的方法实现的排序,Comparator 是在集合外部实现的排序
总结
使用Comparable接口来实现对象之间的比较时,可以使这个类型(设为A)实现Comparable接口,并可以使用Collections.sort()方法来对A类型的List进行排序,之后可以通过a1.comparaTo(a2)来比较两个对象;

7、TreeMap是实现原理

TreeMap是一个通过红黑树实现有序的key-value集合。
TreeMap继承AbstractMap,也即实现了Map,它是一个Map集合
TreeMap实现了NavigableMap接口,它支持一系列的导航方法,
TreeMap实现了Cloneable接口,它可以被克隆
TreeMap本质是Red-Black Tree,它包含几个重要的成员变量:root、size、comparator。其中root是红黑树的根节点。它是Entry类型,Entry是红黑树的节点,它包含了红黑树的6个基本组成:key、value、left、right、parent和color。Entry节点根据根据Key排序,包含的内容是value。Entry中key比
较大小是根据比较器comparator来进行判断的。size是红黑树的节点个数。

HashMap和TreeMap的区别

1、HashMap是通过hash值进行快速查找的;HashMap中的元素是没有顺序的;TreeMap中所有的元素都是有某一固定顺序的,如果需要得到一个有序的结果,就应该使用TreeMap;

2、HashMap和TreeMap都是线程不安全的;

3、HashMap继承AbstractMap类;覆盖了hashcode() 和equals() 方法,以确保两个相等的映射返回相同的哈希值;

TreeMap继承SortedMap类;他保持键的有序顺序;

4、HashMap:基于hash表实现的;使用HashMap要求添加的键类明确定义了hashcode() 和equals() (可以重写该方法);为了优化HashMap的空间使用,可以调优初始容量和负载因子;

TreeMap:基于红黑树实现的;TreeMap就没有调优选项,因为红黑树总是处于平衡的状态;

5、HashMap:适用于Map插入,删除,定位元素;

TreeMap:适用于按自然顺序或自定义顺序遍历键(key)

8、HashMap实现原理

在jdk1.7之前HashMap是基于数组和链表实现的,而且采用头插法。
而jdk1.8 之后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。采用尾插法。

HashMap默认的初始化大小为 16。当HashMap中的元素个数之和大于负载因子*当前容量的时候就要进行扩充,容量变为原来的 2 倍。(这里注意不是数组中的个数,而且数组中和链/树中的所有元素个数之和!)

HashMap扩容的时候为什么是2的n次幂

数组下标的计算方法是(n-1)& hash,取余(%)操作中如果除数是2的幂次则等价于与其除数减一的与(&)操作(也就是说 hash%length==hash&(length-1)的前提是 length 是2的 n 次方;)。 并且采用二进制位操作 &,相对于%能够提高运算效率,这就解释了 HashMap 的长度为什么是2的幂次方。

HashMap的put方法

1.根据key通过哈希算法与与运算得出数组下标

2.如果数组下标元素为空,则将key和value封装为Entry对象(JDK1.7是Entry对象,JDK1.8是Node对象)并放入该位置。

3.如果数组下标位置元素不为空,则要分情况

(i)如果是在JDK1.7,则首先会判断是否需要扩容,如果要扩容就进行扩容,如果不需要扩容就生成Entry对象,并使用头插法添加到当前链表中。

(ii)如果是在JDK1.8中,则会先判断当前位置上的TreeNode类型,看是红黑树还是链表Node

​ (a)如果是红黑树TreeNode,则将key和value封装为一个红黑树节点并添加到红黑树中去,在这个过程中会判断红黑树中是否存在当前key,如果存在则更新value。

​ (b)如果此位置上的Node对象是链表节点,则将key和value封装为一个Node并通过尾插法插入到链表的最后位置去,因为是尾插法,所以需要遍历链表,在遍历过程中会判断是否存在当前key,如果存在则更新其value,当遍历完链表后,将新的Node插入到链表中,插入到链表后,会看当前链表的节点个数,如果大于8(并且数组长度大于64),则会将链表转为红黑树

​ ©将key和value封装为Node插入到链表或红黑树后,在判断是否需要扩容,如果不需要扩容,就结束put方法。

9、ConcurrentHashMap的底层实现

在jdk1.7是 分段的数组+链表 ,jdk1.8的时候跟HashMap1.8的时候一样都是基于数组+链表/红黑树。

ConcurrentHashMap是线程安全的

(1)在jdk1.7的时候是使用分段所segment,每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。

(2)在jdk1.8的时候摒弃了 Segment的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。synchronized只锁定当前链表或红黑二叉树的首节点。

10、HashSet的实现原理

首先看一眼jdk1.8源码

public HashSet() {
        map = new HashMap<>();
    }

这是它的无参构造,当我们 Set set=new HashSet(); 的时候就会执行此方法,可见它又实例化了一个HashMap;由此可见,它是基于HashMap实现的。
看一下他的存值原理
还是源码中 add()方法


// HashSet中的定义的一个成员静态常量
   private static final Object PRESENT = new Object();


	public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

可见在我们存入一个值的时候就会将要存入的值放到了HashMap的key的位置,然后HashMap的value的位置就会放一个 静态的 Object类型的常量对象。 接下来具体的步骤,请看HashMap的put方法的实现,即可总结。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值