Java后端面试试题答案

list和set的区别

  1. List和Set都是继承自collection接口List特点:元素有放入顺序,元素可重复。
  2. Set特点:元素无放入顺序,元素不可重复,重复元素会覆盖掉(元素虽然无放入顺序,但是元素在set中的位置是由该元素的HashCode来决定的,其位置是固定的,加入Set的Object必须定义equals()方法,另外list支持for循环,也就是通过下标来遍历,也可以用迭代器遍历,但是set只能用迭代,因为它无序,无法用下标来取得想要的值。)set和list对比Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。
  3. List:和数组类似,List可以动态增长,查找元素高,插入删除元素效率低,因为会引起其他元素位置改变

HashSet是如何保证不重复的

向hashSet中add元素时,判断元素是否存在的依据,不仅需要比较hash值,同时还要结合equals方法比较。HashSet中的add()方法会使用hashMap的add的方法。

HashMap是线程安全的吗,为什么不是线程安全的。

在多线程中,如果有两个线程都进行写操作,刚好这两个对象经过哈希计算得到了相同的哈希码,而且该位置还没有其他数据。所以两个线程都进入了同一个代码块中。假设有这么一种情况,线程A通过if判断该位置没有hash冲突,进入了if语句,还没有完成数据的插入,这时候CPU就把资源让给了线程B,那么现在的线程A停在了if语句里面,线程B判断该位置没有哈希冲突,也访问了if语句,线程B执行完成之后,轮到线程A执行,现在线程A直接在该位置插入而if语句在之前已经判断过了,所以线程A将线程B的数据给覆盖了。发生了线程不安全的情况。本来在HashMap中,发生哈希冲突是可以用链表法或者红黑树来解决的,但是多线程中,可能就直接给覆盖了。

hashMap的扩容机制

当向容器添加元素的时候,会判断当前容器的元素个数,如果大于等于阈值–即当前数组的长度乘以加载因子的值的时候,就要自动扩容。
扩容:就是重新计算容量,向hashMap对象里不停的添加元素,而hashMap对象内部的数组无法装载更多的元素时,对象就需要扩大数组的长度,一边能装入更多的元素。当然Java里的数组是无法自动扩容的,方法是多使用一个新的数组代替以有容量小的数组,就像我们用一个桶装水,如果想要装更多的水,就只得换大水桶。

hashMap1.7与1.8的区别,说明1.8做了哪些优化,如何优化的?

JDK1.7之前的版本中,HashMap又叫散列表:基于一个数组以及多个链表的实现,hash值冲突的时候就将对应节点以链表的形式存储。
JDK1.8中,当同一个hash值的链表节点数不小于8时,将不再以单链表的形式存储了,会被调整成一颗红黑树。
JDK1.7中
使用一个Entry数组来存储数据,用key的hashcode取模来决定key会被放到数组里的位置;如果hashcode相同,或者hashcode去模的结果相同,那么这些key会被定位到Entry数组的同一个格子里,这些key会形成一个链表。
在hashcode特别差的情况下,比方说所有的key的hashcode都相同,这个链表可能会比较长,那么put/get操作都可能需要遍历这个链表,也就是说时间复杂度最差的情况下会退化到O(n)
JDK1.8中
使用一个node数组来存储数据,但这个node可能时链表结构,也可能是红黑树结构
如果插入的key的hashcode相同,那么这些key也会被定位到node数组的同一个格子里。
如果同一个格子里的key不超过8个,使用链表结构存储。
如果超过8个,那么会调用treeIfBin函数,将链表转换为红黑树。
那么即使hashcode完全相同,由于红黑树的特点,查找某个特定元素,也只需要O(log n)的开销
也就是说put/get的操作的时间复杂度最差只有O(log n)
听起来很不错,但是真正想要利用JDK1.8的好处有一个限制:
key的对象,必须正确的实现了Compare接口
如果没有实现Compare接口,或者实现得不正确(比方说所有的Compare方法都返回0)
那么JDK1.8的HashMap其实还是慢于JDK1.7的

二叉搜索树

  1. 左子树上的所有节点的值小于根节点的值
  2. 右子树上的所有节点的值大于根节点的值
  3. 中序遍历值递增。

红黑树特点

红黑树是一种近似平衡的二叉查找树,它能够确保任何一个节点的左右子树的高度差不会超过二者中较低那个的一倍。具体来说,红黑树需要满足如下条件的二叉查找树
每个节点要么是红的,要么是黑的
根节点必须是黑色的
红节点不能连续(即红色节点的孩子和父亲都不能是红色的)
对于每个节点,从该节点至null(树尾端)的任何路径,都含有相同个数的黑色节点

final finally finalize

final可以修饰类、变量、方法,修饰类表示该类不能被继承,修饰变量表示该变量是一个常量不能被重新赋值,修饰方法标识该方法不能被重写

finally一般作用于try/catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块中,不管是否出现异常都执行该代码块,一般用来存放一些关闭资源的代码。

finalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,该方法一般由垃圾回收器来调用,当我们调用System.gc()方法的时候,有垃圾回收器调用finalize(),回收垃圾,一个对象是否可回收的最后判断

对象的四种引用

强引用 只要存在强引用,垃圾回收器就永远不会回收
Object obj = new Object();
User user=new User();
可直接通过obj取得对应的对象,如obj.equals(new Object());而这样obj对象对后面new object的一个强引用,只有当obj这个引用被释放之后,对象才会被释放,这也是我们经常所用到的编码形式。
软引用非必须引用,内存溢出之前进行回收,可以通过一下代码实现
Object obj=new Object();
SoftReference sf=new SoftReference<>(obj);
obj = null;
sf.get();//有时会返回null
这时候sf对obj的一个软引用,通过sf.get()方法可以取到这个对象,当然,这个对象被标记为需要回收的对象时,则返回null;软引用主要用户实现类似缓存的功能,在内存足够的情况下直接通过软引用取值,无需从繁忙的真是来源查询数据,提升速度;当内存不足时,自动删除这部分缓存数据,从真正的来源查询这些数据。
弱引用第二次垃圾回收时,可以通过如下代码实现;
Object obj=new Object();
WeakReference wf = new WeakReference(obj);
obj = null;
wf.get();//有时候会返回null
wf.isEnQueued();//返回是否被垃圾回收器标记为即将回收的垃圾
弱引用是在第二次垃圾回收时回收,短时间内通过弱引用取对应的数据,可以取到,当执行第二次垃圾回收时,将返回null。弱引用主要用于监控对象是否已经被垃圾回收器标记为即将回收的垃圾,可以通过弱引用的isEnQuequed方法返回对象是否被垃圾回收器标记。
比如用到弱引用的类有ThreadLocal。
虚引用 垃圾回收时回收,无法通过引用取到对象值,可以通过如下代码实现
Object obj=new Object();
PhantomReference pf=new PhantomReference<>(obj);
obj=null;
pf.get();//永远返回null
pf.isEnQuequed();//返回是否从内存中已经删除。

Java获取反射的三种方法

  1. 通过new对象实现反射机制
  2. 通过路径实现反射机制
  3. 通过类名实现反射机制

Java反射机制

Java反射机制是在运行状态中,对于任意一个类,都能够获得这个类的所有属性和方法,对于任意一个对象都能够调用它的任意一个属性和方法。这种在运行时动态的获取信息以及动态调用对象的方法的功能称为Java的反射机制。
class类与java.lang.reflect类库一起对反射的概念进行了支持,该类库包含了field,method,constructor类(每个类都实现了member接口)。这些类型的对象由JVM在运行时创建的,用以标识未知类里对应的成员。
这样你就可以使用constructor创建新的对象,用get/set方法读取和修改与field对象关联的字段,用invoke()方法调用method对象关联的方法。另外,还可以调用getFields()getMethods()和getConstructors()等很便利的方法,以返回表示字段,方法,以及构造器的对象的数组。这样匿名对象的信息就能在运行时被完全确定下来,而在编译时不需要知道任何事情。

Arrays.sort和Collections.sort实现原理和区别

collection和collections区别
java.util.collection是一个集合接口。它提供了对集合对象进行基本操作的通用接口方法。
java.util.collections时针对集合类的一个帮助类,它提供一些列静态方法实现对各种集合的搜索、排序、线程安全等操作。然后还有混排、反转、替换所有的元素(fill)、拷贝(copy)、返回Conllections中的最小元素min、最大元素max、返回指定源列表中最后一次出现指定目标列表的起始位置、返回指定源列表中第一次出现指定目标列表的起始位置、根据指定的距离循环移动指定列表中的元素;
事实上Collections.sort方法底层就是调用array.sort

数组在内存中如何分配

对于Java数组的初始化,有以下两种方式。
静态初始化:初始化时由程序员显示指定每个数组元素的初始值,由系统决定数组长度。
动态初始化:初始化时由程序员显示的指定数组的长度,由系统为数据每个元素分配初始值,Java数组时引用类型的变量。

发布了24 篇原创文章 · 获赞 1 · 访问量 552

猜你喜欢

转载自blog.csdn.net/qq_45366515/article/details/104044332