当前位置:   article > 正文

学习笔记之极客时间《Java 核心技术面试精讲》

java 核心技术面试精讲

地址

Java 核心技术面试精讲

第1讲 谈谈你对Java平台的理解

1. Java是解释执行还是编译执行?

我们开发的 Java 的源代码,首先通过 Javac 编译成为字节码(bytecode),然后,在运行时,通过 Java 虚拟机(JVM)内嵌的解释器将字节码转换成为最终的机器码,这种情况属于编译执行。但是常见的 JVM,比如我们大多数情况使用的 Oracle JDK 提供的 Hotspot JVM,都提供了 JIT(Just-In-Time)编译器,也就是通常所说的动态编译器,JIT 能够在运行时将热点代码编译成机器码,这种情况下部分热点代码就属于编译执行,而不是解释执行了

第2讲 Exception和Error有什么区别?

  1. Exception 和 Error 都是继承了Throwable 类,在 Java 中只有 Throwable 类型的实例才可以被抛出(throw)或者捕获(catch),它是异常处理机制的基本组成类型
  2. Exception 是程序正常运行中,可以预料的意外情况,可能并且应该被捕获,进行相应处理。
  3. Error 是指在正常情况下,不大可能出现的情况,绝大部分的 Error 都会导致程序(比如 JVM 自身)处于非正常的、不可恢复状态。既然是非正常情况,所以不便于也不需要捕获,常见的比如 OutOfMemoryError 之类,都是 Error 的子类。
  4. Exception 又分为可检查(checked)异常和不检查(unchecked)异常,可检查异常在源代码里必须显式地进行捕获处理,这是编译期检查的一部分不检查异常就是所谓的运行时异常,类似 NullPointerException、ArrayIndexOutOfBoundsException 之类,通常是可以编码避免的逻辑错误
  5. NoClassDefFoundError 和 ClassNotFoundException 的区别:前者实现项目编译时没有找到class, 后者是在代码运行的时候没有找到class,比如调用class.forName(“xxx”)。
  6. try-with-resource: JDK7之后Java新增语法,是一个声明一个或多个资源对象的 try语句,其中每个对象所在类需要实现AutoCloseable接口,在语句执行完毕后,每个资源都被自动关闭。
public class MyAutoClosable implements AutoCloseable {
   
    public void doIt() {
   
        System.out.println("MyAutoClosable doing it!");
    }

    @Override
    public void close() throws Exception {
   
        System.out.println("MyAutoClosable closed!");
    }

    public static void main(String[] args) {
   
        try(MyAutoClosable myAutoClosable = new MyAutoClosable()){
   
            myAutoClosable.doIt();
        } catch (Exception e) {
   
            e.printStackTrace();
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  1. 尽量不要捕获类似 Exception 这样的通用异常,而是应该捕获特定异常。

第3讲 谈谈final、finally、 finalize有什么不同?

  1. final 可以用来修饰类、方法、变量,分别有不同的意义,final 修饰的 class 代表不可以继承扩展,final 的变量是不可以修改的,而 final 的方法也是不可以重写的(override)。
  2. finally 则是 Java 保证重点代码一定要被执行的一种机制。我们可以使用 try-finally 或者 try-catch-finally 来进行类似关闭 JDBC 连接、保证 unlock 锁等动作。
  3. finalize 是基础类 java.lang.Object 的一个方法,它的设计目的是保证对象在被垃圾收集前完成特定资源的回收。finalize 机制现在已经不推荐使用,因为我们无法保证 finalize 什么时候执行,执行的是否符合预期。使用不当会影响性能,导致程序死锁、挂起等,并且在 JDK 9 开始被标记为 deprecated。
  4. System.exit,退出虚拟机,无论状态码为多少,都是不会执行finally的。
import java.util.Objects;

public class MyAutoClosable implements AutoCloseable {
   
    @Override
    public void close() throws Exception {
   
        System.out.println("MyAutoClosable closed!"); // 不会打印
    }

    public static void main(String[] args) {
   
        Objects.requireNonNull(args);
        try(MyAutoClosable myAutoClosable = new MyAutoClosable()){
   
//            return ;
            System.exit(0);
        } catch (Exception e) {
   
            e.printStackTrace();
        } finally {
   
            System.out.println("finally"); // 不会打印
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  1. final只能约束对象的引用不被改变。

第4讲 强引用、软引用、弱引用、幻象引用有什么区别?

  1. 强引用:就是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能表明对象还“活着”,垃圾收集器不会碰这种对象。
  2. 软引用:相对强引用弱化一些的引用,可以让对象豁免一些垃圾收集,只有当 JVM 认为内存不足时,才会去试图回收软引用指向的对象。JVM 会确保在抛出 OutOfMemoryError 之前,清理软引用指向的对象。软引用通常用来实现内存敏感的缓存,如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,不会耗尽内存。
  3. 弱引用:并不能使对象豁免垃圾收集,仅仅是提供一种访问在弱引用状态下对象的途径。这就可以用来构建一种没有特定约束的关系,比如,维护一种非强制性的映射关系,如果试图获取时对象还在,就使用它,否则重现实例化。它同样是很多缓存实现的选择。
  4. 虚引用:也叫幻象引用,不能通过它访问对象。幻象引用仅仅是提供了一种确保对象被 finalize 以后,做某些事情的机制。

第5讲 String、StringBuffer、StringBuilder有什么区别?

  1. String: 典型的 Immutable 类,被声明成为 final class,所有属性也都是 final 的。也由于它的不可变性,类似拼接、裁剪字符串等动作,都会产生新的 String 对象。
  2. StringBuffer: 为解决上面提到拼接产生太多中间对象的问题而提供的一个类,把字符串添加到已有序列的末尾或者指定位置。StringBuffer 本质是一个线程安全的可修改字符序列,它保证了线程安全,也随之带来了额外的性能开销。
  3. StringBuilder: Java 1.5 中新增的,在能力上和 StringBuffer 没有本质区别,但是它去掉了线程安全的部分,有效减小了开销,是绝大部分情况下进行字符串拼接的首选。
  4. String 在 Java 6 以后提供了 intern() 方法,目的是提示 JVM 把相应字符串缓存起来,以备重复使用。但在JDK6中,并不推荐大量使用 intern,因为被缓存的字符串是存储在永久代中,只会被FULLGC收集在后续版本中,这个缓存被放置在堆中,这样就极大避免了永久代占满的问题。
  5. JDK9之前String是基于char[]实现,每一个char占用两个字节;JDK9之后,String变为一个 byte 数组加上一个标识编码的所谓 coder实现。

第7讲 int和Integer有什么区别?

  1. int是基本数据类型,Integer是引用数据类型。在JDK5中,Interger新增了静态工厂方法 valueOf,在调用它的时候如果传入的值在-128到127之间,会利用一个缓存机制返回对应的Interger对象。

第8讲 对比Vector、ArrayList、LinkedList有何区别?

  1. Vector: Java 早期提供的线程安全的动态数组,内部是使用对象数组来保存数据,可以根据需要自动的增加容量,当数组已满时,会创建新的数组,并拷贝原有数组数据。
  2. ArrayList: 应用更加广泛的动态数组实现,它本身不是线程安全的,也是可以根据需要调整容量,不过与Vector的调整逻辑有所区别,Vector 在扩容时会提高 1 倍,而 ArrayList 则是增加 50%。
  3. LinkedList: Java 提供的双向链表,所以它不需要像上面两种那样调整容量,它也不是线程安全的。

第9讲 对比HashTable、HashMap、TreeMap有什么不同?

  1. HashTable: 早期 Java 类库提供的一个哈希表实现,本身是同步的,不支持 null 键和值,由于同步导致的性能开销,所以已经很少被推荐使用。
  2. HashMap: HashMap 是应用更加广泛的哈希表实现,行为上大致上与 HashTable 一致,主要区别在于 HashMap 不是同步的,支持 null 键和值等。通常情况下,HashMap 进行 put 或者 get 操作,可以达到常数时间的性能。
  3. TreeMap: 基于红黑树的一种提供顺序访问的 Map,和 HashMap 不同,它的 get、put、remove 之类操作都是 O(log(n))的时间复杂度,具体顺序可以由指定的 Comparator 来决定,或者根据键的自然顺序来判断。
  4. LinkedHashMap: 通常提供的是遍历顺序符合插入顺序,它的实现是通过为条目(键值对)维护一个双向链表。注意,通过特定构造函数,我们可以创建反映访问顺序的实例,所谓的 put、get、compute 等,都算作“访问”。
  5. HashMap 内部的结构:它可以看作是数组(Node[] table)和链表结合组成的复合结构,数组被分为一个个桶(bucket),通过哈希值决定了键值对在这个数组的寻址;哈希值相同的键值对,则以链表形式存储。
  6. 为什么 HashMap 要树化呢?本质上这是个安全问题。因为在元素放置过程中,如果一个对象哈希冲突,都被放置到同一个桶里,则会形成一个链表,而链表查询是线性的,会严重影响存取的性能。而在现实世界,构造哈希冲突的数据并不是非常复杂的事情,恶意代码就可以利用这些数据大量与服务器端交互,导致服务器端 CPU 大量占用(查询缓慢),这就构成了哈希碰撞拒绝服务攻击。

第10讲 如何保证集合是线程安全的? ConcurrentHashMap如何实现高效地线程安全?

  1. 我们可以调用 Collections 工具类提供的包装方法,来获取一个同步的包装容器(如 Collections.synchronizedMap),但是它们都是利用非常粗粒度的同步方式,在高并发情况下,性能比较低下。
  2. 更加普遍的选择是利用并发包提供的线程安全容器类:
    a. 各种并发容器,比如 ConcurrentHashMapCopyOnWriteArrayList
    b. 各种线程安全队列(Queue/Deque),如 ArrayBlockingQueueSynchronousQueue
    c. 各种有序容器的线程安全版本等。
  3. 在 Java 8 和之后的版本中,ConcurrentHashMap 发生了哪些变化呢?
    a. 总体结构上,它的内部存储变得和HashMap 结构非常相似,同样是大的桶(bucket)数组,然后内部也是一个个所谓的链表结构(bin),同步的粒度要更细致一些。
    b. 其内部仍然有 Segment 定义,但仅仅是为了保证序列化时的兼容性而已,不再有任何结构上的用处。因为不再使用 Segment,初始化操作大大简化,修改为 lazy-load 形式,这样可以有效避免初始开销。
    c. 数据存储利用 volatile 来保证可见性。
    d. 使用 CAS 等操作,在特定场景进行无锁并发操作。
    e. 使用 Unsafe、LongAdder 之类底层手段,进行极端情况的优化。
  4. JDK8中,ConcurrentHashMap使用syncronized而不是ReentrantLock进行同步,为什么?因为现代 JDK 中,synchronized 已经被不断优化,可以不再过分担心性能差异,另外,相比于 ReentrantLock,它可以减少内存消耗。

第11讲 Java提供了哪些IO方式? NIO如何实现多路复用?

  1. BIO: 传统的 java.io 包,它基于流模型实现,提供了我们最熟知的一些 IO 功能,比如 File 抽象、输入输出流等。交互方式是同步、阻塞的方式,也就是说,在读取输入流或者写入输出流时,在读、写动作完成之前,线程会一直阻塞在那里,它们之间的调用是可靠的线性顺序。
  2. NIO: 在 Java 1.4 中引入了 NIO 框架(java.nio 包),提供了 ==Channel、Selector、Buffer ==等新的抽象,可以构建多路复用的、同步非阻塞 IO 程序,同时提供了更接近操作系统底层的高性能数据操作方式。
  3. == NIO2(AIO): 在 Java 7 中,NIO 有了进一步的改进,也就是 NIO 2,引入了异步非阻塞 IO 方式,也有很多人叫它 AIO(Asynchronous IO)。异步 IO 操作基于事件和回调机制,可以简单理解为,应用操作直接返回,而不会阻塞在那里,当后台处理完成,操作系统会通知相应线程进行后续工作。==
  4. 同步/异步与阻塞/非阻塞的区别:同步/异步说明的对象是线程之间的协作关系,线程B必须等待线程A完成才开始执行,则线程A和B是同步的关系;阻塞/非阻塞说明的是某一线程内部的关系,比如线程A此时正在执行读取文件操作,线程A无法再做其他事情,只能等待读取结束,此时就称线程A处于阻塞状态。
  5. Reader/Writer 是用于操作字符,增加了字符编解码等功能,适用于类似从文件中读取或者写入文本信息。本质上计算机操作的都是字节,不管是网络通信还是文件读取,Reader/Writer 相当于构建了应用逻辑和原始数据之间的桥梁。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/397656?site
推荐阅读
相关标签
  

闽ICP备14008679号