《互联网大厂 Java 求职者面试:从核心知识到分布式组件》

41 阅读6分钟

以下是面试过程:

第一轮: 面试官:请你说说 Java 的基本数据类型有哪些? 王铁牛:有 byte、short、int、long、float、double、char、boolean 这几种。 面试官:不错,那这些数据类型在内存中的存储方式是怎样的呢? 王铁牛:嗯……这个不太清楚。 面试官:那你再说说 Java 中的面向对象三大特性是什么? 王铁牛:封装、继承、多态。 面试官:很好,那你能举例说明一下封装的作用吗? 王铁牛:(思考片刻)比如在一个类中,将一些属性和方法封装起来,对外只提供必要的接口,这样可以提高代码的安全性和可维护性。

第二轮: 面试官:讲讲 JUC 中的常用类有哪些? 王铁牛:有 CountDownLatch、CyclicBarrier、Semaphore 等。 面试官:那 CountDownLatch 主要用于什么场景呢? 王铁牛:(挠挠头)好像是用于线程同步的,具体不太清楚。 面试官:再说说 JVM 的内存结构分为哪几部分? 王铁牛:有堆、栈、方法区、本地方法栈和程序计数器。 面试官:那堆和栈的区别是什么呢? 王铁牛:(一脸茫然)不太明白。

第三轮: 面试官:谈谈多线程的创建方式有哪些? 王铁牛:有继承 Thread 类和实现 Runnable 接口这两种。 面试官:那它们有什么区别呢? 王铁牛:(思考了一会)继承 Thread 类比较简单,直接重写 run 方法就行;实现 Runnable 接口则需要通过线程池来创建线程。 面试官:不错,那线程池的作用是什么呢? 王铁牛:(犹豫了一下)好像是可以提高线程的复用性,减少创建和销毁线程的开销。 面试官:非常好,那你说说 ArrayList 和 LinkedList 的区别吧。 王铁牛:(思考了一会)ArrayList 是基于数组实现的,查询快但插入和删除慢;LinkedList 是基于链表实现的,插入和删除快但查询慢。

面试官:今天的面试就到这里,你可以回家等通知了。

答案:

  • Java 的基本数据类型:
    • byte:占 1 个字节,范围是 -128 到 127,主要用于存储较小的整数。
    • short:占 2 个字节,范围是 -32768 到 32767,用于存储中等大小的整数。
    • int:占 4 个字节,范围是 -2147483648 到 2147483647,是最常用的整数类型。
    • long:占 8 个字节,范围是很大的整数,用于表示非常大的数值。
    • float:占 4 个字节,用于表示单精度浮点数。
    • double:占 8 个字节,用于表示双精度浮点数。
    • char:占 2 个字节,用于存储字符,在 Java 中字符是以 Unicode 编码表示的。
    • boolean:占 1 个字节,只有 true 和 false 两个值,用于表示逻辑判断。
  • JVM 的内存结构:
    • 堆:是线程共享的区域,用于存储对象实例和数组等。堆分为新生代和老年代,新生代又分为 Eden 区、From Survivor 区和 To Survivor 区。对象首先在 Eden 区分配内存,经过一定次数的垃圾回收后,如果仍然存活,则会被移动到老年代。
    • 栈:每个线程都有自己的栈,用于存储方法调用的局部变量、参数和返回值等。栈的大小是固定的,并且随着方法的调用和返回而动态变化。
    • 方法区:存储类的信息、常量、静态变量和编译后的代码等。方法区是线程共享的区域,在 JDK8 之前是永久代,在 JDK8 及之后是元空间。
    • 本地方法栈:与栈类似,用于存储本地方法的调用信息。本地方法是用其他语言(如 C、C++)实现的,通过 JNI(Java Native Interface)调用。
    • 程序计数器:每个线程都有自己的程序计数器,用于记录当前线程执行的字节码的行号。程序计数器是线程私有的,不会发生内存溢出。
  • 多线程的创建方式:
    • 继承 Thread 类:通过继承 Thread 类并重写 run 方法来创建线程。子类的实例就是一个线程对象,可以直接调用 start 方法启动线程。这种方式简单直观,但存在继承的局限性,不能多继承。
    • 实现 Runnable 接口:定义一个实现 Runnable 接口的类,并重写 run 方法。然后创建该类的实例,并将其作为参数传递给 Thread 类的构造函数,创建线程对象。最后调用 start 方法启动线程。这种方式可以避免继承的局限性,适合多个线程共享资源的情况。
  • 线程池的作用:
    • 提高线程的复用性:线程池中的线程可以重复利用,避免了频繁创建和销毁线程的开销。当有任务需要执行时,直接从线程池中获取一个空闲线程来执行任务,任务执行完毕后,线程不会被销毁,而是回到线程池中等待下一个任务。
    • 管理线程的生命周期:线程池可以对线程的数量进行管理,根据系统的负载情况自动调整线程的数量。当系统负载较高时,增加线程的数量;当系统负载较低时,减少线程的数量,以提高系统的性能和资源利用率。
    • 提供线程的排队机制:线程池可以对提交的任务进行排队,当线程池中的线程都处于忙碌状态时,新提交的任务会被放入队列中等待执行。这样可以避免任务的丢失,并且可以根据队列的类型(如先进先出、优先队列等)来控制任务的执行顺序。
  • ArrayList 和 LinkedList 的区别:
    • 数据结构:ArrayList 是基于数组实现的,数组的长度是固定的,在创建 ArrayList 时需要指定初始容量。LinkedList 是基于链表实现的,链表中的节点通过指针连接起来,长度可以动态变化。
    • 随机访问:ArrayList 实现了 RandomAccess 接口,支持随机访问,即可以通过索引快速访问元素。而 LinkedList 不支持随机访问,需要通过遍历链表来访问元素,因此随机访问的效率较低。
    • 插入和删除:ArrayList 插入和删除元素时,需要移动数组中的元素,因此效率较低。而 LinkedList 插入和删除元素时,只需要修改链表中的指针,因此效率较高。特别是在插入和删除元素较多的情况下,LinkedList 的优势更加明显。
    • 内存占用:ArrayList 占用连续的内存空间,因此在内存中分配和释放空间的效率较高。而 LinkedList 占用不连续的内存空间,需要额外的指针来维护链表的结构,因此在内存中分配和释放空间的效率较低。