Java_后端面试题

2021-03-22 11:38:51 浏览数 (1)

1、HashSet 是如何保证不重复的?

向 HashSet 中 add ()元素时,判断元素是否存在的依据,不仅要比较hash值,同时还要结合 equles 方法比较。 HashSet 中的 add ()方法会使用 HashMap 的 add ()方法。以下是 HashSet 部分源码:

代码语言:javascript复制
private static final Object PRESENT = new Object(); 
private transient HashMap<E,Object> map; 
public HashSet() { 
    map = new HashMap<>(); 
}
public boolean add(E e) {
    return map.put(e, PRESENT)==null; 
}

HashMap 的 key 是唯一的,由上面的代码可以看出 HashSet 添加进去的值就是作为 HashMap 的key。所以不会重复(HashMap 首先key是否相等是先比较 hashcode 再比较 equals)。

2、HashMap 是线程安全的吗,为什么不是线程安全的?

不是线程安全的。 如果有两个线程A和B,都进行插入数据,刚好这两条不同的数据经过哈希计算后得到的哈希码是一样的,且该位置还没有其他的数据。 假设一种情况,线程A通过if判断,该位置没有哈希冲突,进入了if语句,还没有进行数据插入,这时候 CPU 就把资源让给了线程B,线程A停在了if语句里面,线程B判断该位置没有哈希冲突(线程A的数据还没插入),也进入了if语句,线程B执行完后,轮到线程A执行,现在线程A直接在该位置插入而不用再判断。这时候,你会发现线程A把线程B插入的数据给覆盖了。发生了线程不安全情况。

本来在 HashMap 中,发生哈希冲突是可以用链表法或者红黑树来解决的,但是在多线程中,可能就直接给覆盖了。

3、final finally finalize
  • final可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表示该变量是一个常量不能被重新赋值。
  • finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。
  • finalize是一个方法,属于Object类的一个方法,该方法一般由垃圾回收器来调用,当我们调用 System.gc() 方法的时候,由垃圾回收器调用finalize(),回收垃圾,一个对象是否可回收的最后判断。
4、对象的四种引用
  • 强引用 只要引用存在,垃圾回收器永远不会回收
  • 软引用 非必须引用,内存溢出之前进行回收
代码语言:javascript复制
Object obj = new Object(); 
SoftReference<Object> sf = new SoftReference<Object>(obj); 
obj = null; 
sf.get();//有时候会返回null

软引用主要用户实现类似缓存的功能,在内存足够的情况下直接通过软引用取值,无需从繁忙的真实来源查询数据,提升速度;当内存不足时,自动删除这部分缓存数据,从真正的来源查询这些数据。

  • 弱引用 第二次垃圾回收时回收 弱引用是在第二次垃圾回收时回收,短时间内通过弱引用取对应的数据,可以取到,当执行过第二次垃圾回收时,将返回null。弱引用主要用于监控对象是否已经被垃圾回收器标记为即将回收的垃圾,可以通过弱引用的isEnQueued 方法返回对象是否被垃圾回收器标记。
  • 虚引用 垃圾回收时回收,无法通过引用取到对象值 虚引用是每次垃圾回收的时候都会被回收,通过虚引用的get方法永远获取到的数据为null,因此也被成为幽灵引用。虚引用主要用于检测对象是否已经从内存中删除。
5、Java获取反射的三种方法

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

代码语言:javascript复制
public class Student{
    private int id;
    String name;
    protected boolean sex;
    public float score;
}
代码语言:javascript复制
public class ReflectDemo {
    public static void main(String[] args) throws ClassNotFoundException {
        // 1、通过对象
        Student student = new Student();
        Class class1 = student.getClass();
        System.out.println(class1.getName());

        // 2、通过路径
        Class class2 = Class.forName("c02.Student");
        System.out.println(class2.getName());

        // 3、通过类名
        Class class3 = Student.class;
        System.out.println(class3.getName());
    }
}
6、wait 和 sleep 的区别

1、sleep 来自 Thread 类, wait 来自 Object 类。 2、sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。 3、wait,notify和 notifyAll 只能在同步控制方法或者同步控制块里面使用,而 sleep 可以在任何地方使用(使用范围) 。 4、 sleep 必须捕获异常,而 wait , notify 和 notifyAll 不需要捕获异常。

7、数组在内存中如何分配
  • 静态初始化:初始化时由程序员显式指定每个数组元素的初始值,由系统决定数组长度。
  • 动态初始化:初始化时由程序员显示的指定数组的长度,由系统为数据每个元素分配初始值。

静态初始化方式,程序员虽然没有指定数组长度,但是系统已经自动帮我们给分配了,而动态初始化方式,程序员虽然没有显示的指定初始化值, 但是因为 Java 数组是引用类型的变量,所以系统也为每个元素分配了初始化值 null ,当然不同类型的初始化值也是不一样的,假设是基本类型int类型, 那么为系统分配的初始化值也是对应的默认值0。

本文由来源 jackaroo2020,由 javajgs_com 整理编辑,其版权均为 jackaroo2020 所有,文章内容系作者个人观点,不代表 Java架构师必看 对观点赞同或支持。如需转载,请注明文章来源。

0 人点赞