饿了么Java面试经验分享:从基础知识到项目实战的全方位指南

今天分享的是来自华中师范大学的一位同学的饿了么Java一面面经,主要考察Java基础知识以及项目经验(例如黑马点评),内容相对简单。

图片

1. Java基础的核心模块概述

以下是我认为Java基础知识比较核心的模块总结:

  • 异常处理:定义了Java运行中可能出现的异常,并提供了处理机制。
  • 泛型:通过使用泛型参数,提高代码的可读性和稳定性。
  • 反射机制:允许在运行时对类进行分析,并执行类中的方法,可以获取任意类的属性和方法,并调用它们。
  • 注解:可以视作一种特殊的注释,主要用于修饰类、方法或变量,提供信息供编译或运行时使用。
  • 集合框架:Java集合,也称为容器,用于保存数据,主分为List、Set、Queue和Map四大类,每类有不同的用途。
  • 输入输出流(I/O):用于处理输入和输出操作,例如文件读写。
  • 多线程:除了基础的Thread类和Runnable接口外,最重要的就是Java并发工具包(JUC),其中包含了并发编程中常用的工具类,如线程池、异步I/O、各种锁等。

2. Java异常的顶层结构及接口实现

Java异常类层次结构概览

![图片](data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='1px' height='1px' viewBox='0 0 1 1' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Ctitle%3E%3C/title%3E%3Cg stroke='none' stroke-width='1' fill='none' fill-rule='evenodd' fill-opacity='0'%3E%3Cg transform='translate(-249.000000, -126.000000)' fill='%23FFFFFF'%3E%3Crect x='249' y='126' width='1' height='1'%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)

在Java中,所有的异常类都继承自java.lang包中的Throwable类。Throwable类有两个重要的子类:

  • Exception:程序可以处理的异常,通过catch捕获。可以分为检查异常(Checked Exception)和非检查异常(Unchecked Exception)。
  • Error:这些是程序无法处理的错误,通常不建议通过catch捕获,例如Java虚拟机运行错误和内存不足错误。

3. Java集合的顶层结构及接口实现

Java集合框架架构图如下

![图片](data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='1px' height='1px' viewBox='0 0 1 1' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Ctitle%3E%3C/title%3E%3Cg stroke='none' stroke-width='1' fill='none' fill-rule='evenodd' fill-opacity='0'%3E%3Cg transform='translate(-249.000000, -126.000000)' fill='%23FFFFFF'%3E%3Crect x='249' y='126' width='1' height='1'%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)

具体的集合接口实现类如下:

List接口实现类

  • ArrayList:基于动态数组实现。
  • LinkedList:基于双向链表实现。
  • Vector:与ArrayList类似,但提供线程安全性。
  • Stack:继承自Vector,实现栈的功能。

Set接口实现类

  • HashSet:基于哈希表实现,元素无序。
  • LinkedHashSet:结合哈希表和链表,维护插入顺序。
  • TreeSet:基于红黑树实现,元素有序。

Map接口实现类

  • HashMap:存储键值对,早期基于数组和链表实现。
  • LinkedHashMap:维护插入顺序的HashMap,效率更高。
  • TreeMap:提供基于键的排序功能。
  • Hashtable:与HashMap相似,但线程安全。

Queue接口实现类

  • LinkedList:同时实现了ListQueue接口。
  • PriorityQueue:元素出队顺序依据优先级。
  • ArrayDeque:基于动态数组和双指针实现,允许在两端进行操作。

4. HashMap的结构分析

JDK1.8之前

在JDK1.8之前,HashMap底层结构结合了数组和链表,也称为链表散列。通过keyhashcode计算哈希值,并通过(n - 1) & hash确定存放位置。如果位置已有元素,通过键值比较解决冲突。

扰动函数是HashMap的hash方法,用于降低碰撞几率。

JDK 1.8的hash方法源码如下

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

JDK 1.7的hash方法源码如下

static int hash(int h) {
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}

5. IO流的基本概念

在Java中,I/O代表输入和输出。输入是将数据传入计算机的过程,而输出则是将数据从计算机输出到外部存储(如文件、数据库等)。I/O流被分为输入流和输出流,依据处理方式又分为字节流和字符流。

Java中的I/O流类大多源自以下四个抽象类:

  • InputStream/Reader: 输入流基类。
  • OutputStream/Writer: 输出流基类。

6. 字节流与字符流的区别

尽管信息的存储最小单元为字节,但I/O操作分为字节流和字符流主要有两点原因:

  • 字符流是将字节转换为字符的过程,效率较低。
  • 使用字节流时对编码方式不明时,可能会出现乱码。

7. 使用Redis的原因

在面试时,您可以从“高性能”和“高并发”这两个方面来回答。

高性能:对于高频且不常改变的数据,可以将其缓存以提高再次访问的速度。

高并发:使用Redis可以显著提高QPS(每秒查询次数),例如Redis可达到10w+的QPS,远高于传统数据库。

8. Redis与MySQL的流量承受能力

如上所述,Redis在流量承受能力上显著高于MySQL。

9. Redis的数据结构及底层实现

Redis常见的数据类型包括:

  • 基础数据类型:String、List、Set、Hash、Zset。
  • 特殊数据类型:HyperLogLog、Bitmap、Geospatial。

这些基础数据类型的底层数据结构实现主要依赖:简单动态字符串、双向链表、哈希表、跳跃表等。

10. 使用MQ的理由

使用消息队列可以带来以下优势:

  1. 提高系统性能,减少响应时间。
  2. 实现削峰与限流。
  3. 降低系统耦合性。

11. 消息队列的解耦示例

通过消息队列,生产者和消费者之间的耦合性降低,系统的可扩展性增强。

![图片](data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='1px' height='1px' viewBox='0 0 1 1' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Ctitle%3E%3C/title%3E%3Cg stroke='none' stroke-width='1' fill='none' fill-rule='evenodd' fill-opacity='0'%3E%3Cg transform='translate(-249.000000, -126.000000)' fill='%23FFFFFF'%3E%3Crect x='249' y='126' width='1' height='1'%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)

12. 如何创建线程?

在Java中,创建线程的方式有多种,例如继承Thread类、实现Runnable接口等,但最终仍依赖于new Thread().start()

13. 线程池的参数解析

ThreadPoolExecutor的重要参数包括:

  • corePoolSize:最大同时运行的线程数量。
  • maximumPoolSize:任务队列满时的最大线程数量。
  • workQueue:任务被存放的队列。

![图片](data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='1px' height='1px' viewBox='0 0 1 1' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Ctitle%3E%3C/title%3E%3Cg stroke='none' stroke-width='1' fill='none' fill-rule='evenodd' fill-opacity='0'%3E%3Cg transform='translate(-249.000000, -126.000000)' fill='%23FFFFFF'%3E%3Crect x='249' y='126' width='1' height='1'%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)

在任务队列未满时,最多运行的线程数量为核心线程数;当任务队列满时,最多运行的线程数量为最大线程数。