《互联网大厂 Java 求职者面试三轮提问及答案》

75 阅读7分钟

以下是互联网大厂 Java 求职者面试三轮提问及答案:

第一轮: 面试官:请你谈谈 Java 的基本数据类型有哪些? 王铁牛:有 byte、short、int、long、float、double、char、boolean 这八种。 面试官:那说说它们的存储范围和默认值分别是多少? 王铁牛:byte 存储范围是 -128 到 127,默认值是 0;short 存储范围是 -32768 到 32767,默认值是 0;int 存储范围是 -2147483648 到 2147483647,默认值是 0;long 存储范围很大,默认值是 0L;float 存储范围和精度有限,默认值是 0.0f;double 存储范围和精度更高,默认值是 0.0d;char 存储一个字符,默认值是 '\u0000';boolean 只有 true 和 false 两个值,默认值是 false。

第二轮: 面试官:在 Java 中,如何实现多线程? 王铁牛:可以通过继承 Thread 类或实现 Runnable 接口来实现多线程。 面试官:那这两种方式有什么区别呢? 王铁牛:继承 Thread 类时,子类直接继承了 Thread 类的方法和属性,代码相对简单,但 Java 不支持多重继承,所以如果已经继承了其他类就不能再继承 Thread 类。实现 Runnable 接口则是将线程任务和线程本身分离,更适合多个线程共享同一个资源的情况,并且可以避免单继承的限制。 面试官:说说线程的生命周期吧。 王铁牛:线程的生命周期包括新建、就绪、运行、阻塞和死亡五个状态。新建状态是创建线程对象但还未启动;就绪状态是线程对象创建后调用 start()方法,等待 CPU 调度;运行状态是线程获得 CPU 时间片开始执行;阻塞状态是线程因等待某个条件而暂停执行,如等待锁、I/O 操作等;死亡状态是线程执行完毕或出现异常终止。

第三轮: 面试官:谈谈你对 HashMap 的理解,它的底层原理是什么? 王铁牛:HashMap 是用于存储键值对的集合类,它基于哈希表实现。底层通过数组和链表或红黑树来存储元素,根据键的哈希值来确定元素在数组中的位置,以实现快速的插入、删除和查找操作。 面试官:当哈希冲突发生时,HashMap 是如何解决的? 王铁牛:当哈希冲突发生时,HashMap 会使用链表法或红黑树法来解决。如果链表长度小于等于 8 且数组长度小于 64 时,使用链表法;如果链表长度大于 8 或数组长度大于等于 64 时,将链表转换为红黑树,以提高查询效率。 面试官:HashMap 的容量大小和负载因子对性能有什么影响? 王铁牛:容量大小决定了哈希表的初始大小,负载因子决定了哈希表在何时进行扩容。如果容量太小,会导致哈希冲突频繁,影响性能;如果容量太大,会浪费内存空间。负载因子一般设置为 0.75f,在这个值下,哈希表的性能比较平衡。

面试官总结:今天的面试就到这里,你表现得还不错,回去等我们的通知吧。感谢你参加面试。

答案详细讲述技术点:

  • Java 基本数据类型:基本数据类型是编程语言中最基本的数据单元,它们在内存中占据固定大小的空间,并且具有特定的取值范围和默认值。这些类型用于存储简单的数据,如整数、浮点数、字符和布尔值。
  • 多线程实现方式
    • 继承 Thread 类:通过创建一个继承自 Thread 类的子类,并重写 run()方法来定义线程的执行逻辑。在子类的构造函数中可以传递线程名称等参数。然后通过调用 start()方法来启动线程,线程会自动调用 run()方法执行任务。
    • 实现 Runnable 接口:创建一个实现了 Runnable 接口的类,并重写 run()方法。然后创建 Thread 对象,并将实现了 Runnable 接口的对象作为参数传递给 Thread 的构造函数。最后调用 Thread 的 start()方法启动线程。这种方式可以避免单继承的限制,多个线程可以共享同一个实现了 Runnable 接口的对象。
  • 线程的生命周期
    • 新建状态:通过调用 Thread 类的构造函数创建一个线程对象,但还没有调用 start()方法,此时线程处于新建状态。
    • 就绪状态:当调用线程的 start()方法后,线程进入就绪状态,等待 CPU 调度执行。处于就绪状态的线程并没有实际执行,只是等待被分配 CPU 时间片。
    • 运行状态:当线程获得 CPU 时间片后,就进入运行状态,开始执行线程的 run()方法中的代码。线程可以一直处于运行状态,直到执行完毕或被阻塞。
    • 阻塞状态:线程在运行过程中可能会因为等待某个条件而暂停执行,例如等待锁、等待 I/O 操作完成等,此时线程进入阻塞状态。当条件满足时,线程会从阻塞状态转换为就绪状态,等待 CPU 调度。
    • 死亡状态:线程执行完毕或者出现异常导致线程终止,此时线程进入死亡状态。线程死亡后不能再被启动。
  • HashMap 底层原理
    • 哈希表结构:HashMap 基于哈希表实现,哈希表是一种数据结构,它通过哈希函数将键映射到数组的索引上,从而实现快速的插入、删除和查找操作。在 HashMap 中,数组是哈希表的基础,每个数组元素称为一个桶(bucket)。
    • 哈希函数:哈希函数是用于计算键的哈希值的函数,它将键映射到数组的索引上。哈希函数的设计应该尽可能地均匀分布,以减少哈希冲突的发生。在 Java 中,HashMap 使用键的 hashCode()方法计算哈希值,并通过位运算将哈希值映射到数组的索引上。
    • 链表和红黑树:当多个键通过哈希函数计算得到相同的哈希值时,就会发生哈希冲突。在 HashMap 中,对于发生哈希冲突的键值对,通过链表或红黑树来存储。如果链表长度小于等于 8 且数组长度小于 64 时,使用链表法;如果链表长度大于 8 或数组长度大于等于 64 时,将链表转换为红黑树,以提高查询效率。
  • HashMap 容量大小和负载因子对性能的影响
    • 容量大小:容量大小决定了哈希表的初始大小,默认初始容量为 16。如果初始容量过小,会导致哈希冲突频繁,需要频繁进行扩容操作,影响性能;如果初始容量过大,会浪费内存空间。在创建 HashMap 时可以通过构造函数指定初始容量,但最好是选择 2 的幂次方,这样可以提高哈希函数的性能。
    • 负载因子:负载因子是衡量哈希表满程度的指标,默认负载因子为 0.75f。当哈希表中的元素个数超过容量大小乘以负载因子时,哈希表会进行扩容操作,将容量大小扩大为原来的 2 倍。负载因子越大,哈希表越容易满,需要更频繁地进行扩容操作;负载因子越小,哈希表的利用率越低,但可以减少扩容操作的次数。在实际应用中,需要根据具体情况选择合适的负载因子,以平衡性能和内存占用。