某行某费 线上笔试

一、选择题和简答题

1.  CompletableFuture是Java 8 引入的异步编程工具,用于异步任务的组合和链式调用。

通过静态工厂方法可以创建CompletableFuture实例:

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello");
 

支持链式调用,可以将多个异步任务串联起来:

CompletableFuture.supplyAsync(() -> "Hello")
    .thenApply(s -> s + " World")
    .thenAccept(System.out::println);
 

多个 CompletableFuture 可以组合成一个新的 CompletableFuture:

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");
future1.thenCombine(future2, (s1, s2) -> s1 + " " + s2)
    .thenAccept(System.out::println);
 

支持多种回调函数,如 thenApply、thenAccept、thenRun

CompletableFuture.supplyAsync(() -> "Hello")
    .thenApply(s -> s + " World")
    .thenAccept(System.out::println)
    .thenRun(() -> System.out.println("Done"));
 

2.  释放线程持有的锁——wait()

java中有关线程的操作:

  • run():  线程执行的入口方法。run()Runnable接口或Thread类中定义的核心方法,包含线程实际执行的代码逻辑。直接调用run()不会启动新线程,而是在当前线程中同步执行。 是否释放锁取决于其内部是否调用了wait()或同步代码块的执行结束。
  • sleep():  调用sleep()不会释放任何锁,线程即使休眠仍持有锁。其他线程无法进入该对象的同步代码块或方法。Thread.sleep(long millis)使当前线程暂停执行指定毫秒数,可能抛出InterruptedException,通常用于模拟耗时操作或控制执行节奏。sleep()属于Thread类。
  • wait():  wait()Object类的方法,用于线程间通信。调用前必须持有对象锁(在synchronized块内),会释放锁并让线程进入等待状态,直到其他线程调用notify()/notifyAll()唤醒它这是唯一明确释放锁的操作。
  • start():启动线程,调用后线程进入就绪状态,等待CPU调度。
  • join():等待线程执行完成,主线程会阻塞直到目标线程结束。
  • interrupt():中断线程,设置线程的中断标志位。
  • yield():让出CPU资源,当前线程从运行状态转为就绪状态。
  • notify() / notifyAll():唤醒在对象上等待的单个或所有线程。

线程状态管理——线程的生命周期中涉及多种状态转换,相关操作包括:

  • isAlive():检查线程是否存活。
  • getState():获取线程当前状态(NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED)。
  • setDaemon(boolean):设置线程为守护线程(后台线程)。

线程同步与锁——多线程并发操作时常用的同步机制:

  • synchronized:关键字,用于方法或代码块同步。
  • Lock接口:显式锁,提供更灵活的锁控制(如ReentrantLock)。
  • wait() / notify():配合synchronized使用,实现线程间通信。

线程池操作——Java并发包(java.util.concurrent)提供了线程池相关操作:

  • ExecutorService:线程池接口,常用实现类如ThreadPoolExecutor。
  • submit() / execute():提交任务到线程池。
  • shutdown() / shutdownNow():关闭线程池。

其他高级操作

  • ThreadLocal:线程局部变量,每个线程独立存储数据。
  • Future / Callable:获取线程执行结果。
  • ForkJoinPool:分治任务的线程池框架。

Java中创建线程

  • 继承Thread类
  • 实现Runnable接口

3. HashMap和HashTable 

比较点 HashMap Hashtable
出现时间 JDK 1.2 引入,属于新集合框架 JDK 1.0 就存在的遗留类 (Legacy)
线程安全 非线程安全 线程安全 (方法使用 synchronized 修饰)
性能 高(无锁竞争) 低(锁竞争严重)
允许 null 允许一个 null 键,多个 null 值 不允许 null 键和 null 值(会抛 NPE)
继承体系 继承 AbstractMap,实现 Map 接口 继承 Dictionary,实现 Map 接口
迭代器 Iterator (fail-fast) 除了 Iterator,还有 Enumeration (非 fail-fast)
初始容量 默认 16 默认 11
扩容机制 容量变为原来的 2 倍 容量变为原来的 2 倍 + 1
hash 计算 对 key 的 hashCode 进行扰动处理 直接使用 key 的 hashCode
是否支持 contains 方法 有 containsKey 和 containsValue 有 contains 方法(类似 containsValue

4. 设计模式

创建型模式:关注对象的创建过程,将对象的创建与使用分离。

  • 工厂方法:定义一个创建对象的接口,由子类决定实例化哪个类。
  • 抽象工厂:提供一组相关或依赖对象的创建接口,无需指定具体类。
  • 单例:确保一个类仅有一个实例,并提供全局访问点。
  • 建造者:将一个复杂对象的构建与其表示分离,支持分步骤构造。
  • 原型:通过复制现有对象来创建新对象,避免重复初始化。

结构型模式:关注类和对象的组合,通过继承或组合构建更大的结构。

  • 适配器:将一个类的接口转换成客户端期望的另一种接口。
  • 桥接:将抽象部分与实现部分分离,使两者可独立变化。
  • 组合:将对象组织成树形结构以表示“部分-整体”层次关系。
  • 装饰器:动态地为对象添加额外职责,比继承更灵活。
  • 外观:为复杂子系统提供统一的简化接口。
  • 享元:运用共享技术有效支持大量细粒度对象。
  • 代理:为其他对象提供一种代理以控制对这个对象的访问。

行为型模式:关注对象之间的交互与职责分配。

  • 责任链:将请求的发送者和接收者解耦,允许多个对象处理请求。
  • 命令:将请求封装为对象,支持参数化、队列或日志请求。
  • 解释器:定义语言的文法表示,并提供解释器处理该语言。
  • 迭代器:提供一种方法顺序访问聚合对象的元素而不暴露内部表示。
  • 中介者:用一个中介对象封装一系列对象交互,降低耦合度。
  • 备忘录:捕获对象内部状态并在不破坏封装情况下恢复该状态。
  • 观察者:定义对象间一对多依赖关系,使得状态变化时自动通知。
  • 状态:允许对象在内部状态改变时改变其行为。
  • 策略:定义一系列算法并使其可互换,独立于使用它的客户端。
  • 模板方法:在父类中定义算法框架,允许子类重写特定步骤。
  • 访问者:在不修改元素类的前提下为其添加新的操作。

5. TCP连接建立与释放

三次握手(建立连接)

客户端 (Client)                             服务器 (Server)
   CLOSED                                      LISTEN
     |                                            |
     |       SYN=1, seq=x (不能携带数据)          |
     | ----------------------------------------> |
     |                                            | 收到 SYN
     |                                            | 状态变为 SYN-RCVD
     |       SYN=1, ACK=1, seq=y, ack=x+1         |
     | <---------------------------------------- |
     |                                            |
  状态变为 ESTABLISHED                             |
     |                                            |
     |       ACK=1, seq=x+1, ack=y+1 (可携带数据)  |
     | ----------------------------------------> |
     |                                            | 状态变为 ESTABLISHED
     |                                            |

四次挥手(释放连接,客户端主动关闭)

主动关闭方 (Client)                        被动关闭方 (Server)
  ESTABLISHED                                 ESTABLISHED
       |                                            |
       |      FIN=1, seq=u (可能携带最后数据)         |
       | ---------------------------------------->  |
  状态变为 FIN-WAIT-1                                | 收到 FIN,回复 ACK
       |                                            | 状态变为 CLOSE-WAIT
       |      ACK=1, seq=v, ack=u+1                 |
       | <----------------------------------------  |
  状态变为 FIN-WAIT-2                                |
       |                                            | (服务器可能还有数据要发)
       |                                            | ... 数据传输 ...
       |                                            |
       |      FIN=1, ACK=1, seq=w, ack=u+1          |
       | <----------------------------------------  |
       |                                     状态变为 LAST-ACK
  状态变为 TIME-WAIT                                 |
       |                                            |
       |      ACK=1, seq=u+1, ack=w+1               |
       | ---------------------------------------->  |
       |                                     状态变为 CLOSED
  等待 2MSL 后关闭
  状态变为 CLOSED

6. volatile作用

  • 保证可见性:线程修改volatile变量后,会强制写回主内存,其他线程读取时从主内存刷新。

  • 禁止指令重排:通过内存屏障防止编译器和CPU重排序。

  • 不保证原子性:例如count++操作仍需要加锁。

7. equals()和==

  • ==:比较基本数据类型时比,比较引用类型时比内存地址

  • equals:基本数据类型没有equals,引用类型同==比内存地址;但很多类(如String、Integer)重写了equals,用于比较内容

8. 触发Full GC

Full GC 是指对整个堆(新生代、老年代、方法区/元空间)进行垃圾回收,通常伴随较长的停顿。触发场景包括:

  • 老年代空间不足

    • 对象晋升失败:Minor GC 后存活对象需要进入老年代,但老年代剩余空间不足。

    • 大对象直接分配:大对象超过阈值直接进入老年代,若空间不足则触发 Full GC。

    • CMS 并发模式失败:CMS 回收期间老年代被快速填满,退化为 Full GC。

  • 永久代/元空间不足

    • 加载的类、方法、常量池过多,导致方法区溢出。

  • 显式调用 System.gc()

    • 代码中调用 System.gc() 建议 JVM 执行 Full GC(可通过 -XX:+DisableExplicitGC 屏蔽)。

  • GC 算法相关

    • G1 回收失败:混合回收无法满足内存需求。

    • 空间分配担保失败:Minor GC 前检查老年代最大连续空间不足,且担保不允许冒险。

  • 内存泄漏:对象持续无法回收,堆逐渐占满。

9. 多态

多态是面向对象三大特性之一,指同一操作作用于不同对象,可以有不同的解释和执行结果。简单说:父类引用指向子类对象,调用方法时执行子类的实现。提高代码的扩展性和可维护性,减少耦合。

  • 核心:类型兼容(子类对象可以赋值给父类类型的变量)+动态绑定(程序运行时,JVM 会根据变量指向的实际对象类型,而非变量的声明类型,来决定调用哪个方法),编译看左边(声明类型),运行看右边(实际对象)

  • 表现形式:

    • 编译时多态:方法重载(静态绑定)。同一类中,方法名相同,参数列表(个数/类型/顺序)不同。编译时就确定调用哪个方法。

    • 运行时多态:方法重写Override(动态绑定)。有继承关系,子类重写父类的非private、非final、非static方法,父类引用指向子类对象。

      • 向上转型:子类→父类,自动完成,目的是 “统一类型”;

      • 向下转型:父类→子类,强制转换,目的是 “调用子类特有方法”;注意:向下转型前必须用 instanceof 判断,否则会抛出 ClassCastException(类型转换异常)。

面向对象三大特性:

  • 封装:隐藏内部细节,通过接口访问,核心是 “保护和隐藏”;
  • 继承:复用父类代码,扩展子类功能,核心是 “复用和扩展”;
  • 多态:父类引用指向子类对象,同一行为不同表现,核心是 “灵活和扩展”。

这三个特性相互配合:封装保证数据安全,继承实现代码复用,多态提升代码灵活性,共同构成

10. 线程池参数及作用

  • corePoolSize:核心线程数,即使空闲也会保留(除非设置 allowCoreThreadTimeOut)。

  • maximumPoolSize:最大线程数,线程池允许创建的最大线程数量。

  • keepAliveTime:非核心线程空闲存活时间。

  • unit:时间单位。

  • workQueue:任务队列,用于存放等待执行的任务。

  • threadFactory:线程工厂,用于创建新线程。

  • handler:拒绝策略,当任务无法执行时的处理方式。

corePoolSize和maximumPoolSize的大小相比:通常 maximumPoolSize >= corePoolSize

  • 当提交任务时,如果当前线程数 < corePoolSize,则创建新线程执行任务。

  • 如果当前线程数 >= corePoolSize,则将任务放入队列。

  • 如果队列已满,且当前线程数 < maximumPoolSize,则创建新线程(非核心)执行任务。

  • 如果当前线程数 >= maximumPoolSize 且队列已满,则执行拒绝策略。

11. 反射:允许程序在运行时获取类的信息(属性、方法、构造器、注解等)并操作类或对象的属性和方法。

只能用子系统类,不能用自定义类?错误

  • 反射可以操作任何类:包括 JDK 自带类、第三方库类以及自定义类。只要类在类路径中,就可以通过 Class.forName() 或 .class 获取 Class 对象。

  • 限制:某些情况下可能受限于模块化(Java 9+),如果自定义类不在当前模块或未开放包,则可能无法访问私有成员。但这是模块化权限控制,并非反射本身限制。

核心用途:框架 / 工具类场景优先用,业务代码尽量少用(避免性能和封装性问题)。

  • 框架开发:几乎所有主流 Java 框架(Spring、MyBatis、Hibernate、JUnit 等)都依赖反射实现 “解耦” 和 “动态化”。框架无法在编译期知道你自定义的类(比如 UserServiceOrderMapper),通过反射可以在运行时加载并实例化这些类,调用指定方法。   示例:Spring 的 IOC 容器会读取配置文件(或注解),通过反射创建对象并管理依赖;MyBatis 通过反射将数据库查询结果映射到实体类的私有属性,无需手动赋值。

  • 动态调用方法/修改私有属性:反射可以突破访问修饰符的限制(比如调用 private 方法、修改 private 属性)

  • 实现通用化工具类:反射可以编写 “通用型” 代码,适配不同类的操作,避免重复编写相似逻辑。例如BeanUtils.copyProperties,实现任意对象的属性拷贝。

  • 动态加载类(突破编译期依赖):反射可以在运行时根据条件加载不同的类,适用于 “插件化开发” 或 “动态扩展” 场景。

  • 处理注解(注解的核心配套机制):Java 注解本身不会生效,必须通过反射读取注解信息并执行逻辑(比如 Spring 的 @Controller@Autowired,JUnit 的 @Test)。

12. Java中Stream API

  • Stream 不是数据结构:它不存储数据,而是对数据源(集合、数组等)进行函数式操作的工具。

  • 操作分类

    • 中间操作:返回新的 Stream,可链式调用,如 filtermapsorted。惰性求值,只有遇到终端操作才执行。

    • 终端操作:触发实际计算,返回结果或副作用,如 forEachcollectreduce

  • 串行流与并行流

    • 串行流:单线程处理,通过 stream() 获取。

    • 并行流:多线程处理,通过 parallelStream() 或 parallel() 转换,利用 Fork/Join 框架。

  • 特点:支持函数式编程,提高代码可读性,但需注意线程安全。

13. 数据库连接池的优点

数据库连接池是一种管理数据库连接的技术。

主要优点:

  • 减少连接创建开销:预先创建并维护一定数量的连接,避免每次请求都建立和关闭连接(耗时资源)。解决 “频繁创建 / 销毁数据库连接” 性能损耗。

  • 资源复用:有效控制连接数量,防止过多连接耗尽数据库资源。

  • 统一管理:可配置连接超时、泄漏检测、统计信息等。

  • 提高响应速度:直接从池中获取连接,无需等待 TCP 握手等过程。

  • 提高系统可靠性:连接池能自动回收无效连接,避免内存泄漏。

常见连接池:HikariCP(目前最优,高性能、轻量级,Spring Boot 2.x 及以上默认内置的连接池)、Druid(阿里开源,功能最全,监控、安全)、.Tomcat JDBC Pool(轻量级,Tomcat 内置)、C3P0 (老牌,逐渐淘汰)等。

连接池参数和特性:

  • 连接池大小配置:
    • 最大连接数(根据数据库性能和业务并发量调整)、
    • 最小空闲连接数(保持一定的空闲连接,避免高峰期频繁创建连接,建议设为最大连接数的 1/4~1/2)、
    • 初始化连接数(项目启动时创建的连接数,建议等于最小空闲连接数,减少启动后首次请求的耗时)
  • 超时配置:
    • 连接超时(获取连接的最大等待时间,超时抛出异常(建议 3~5 秒),避免请求无限阻塞)、
    • 空闲超时(空闲连接的最大存活时间,超时自动回收(建议 10~30 分钟),避免连接长期闲置占用资源)、
    • 连接存活时间(连接的最大生命周期(建议 1~2 小时),避免数据库端主动断开连接导致的 “无效连接”)
  • 性能特性:
    • 优先选择无锁 / 轻量级锁的连接池(如 HikariCP),减少并发竞争;
    • 支持连接池预热(启动时创建所有最小空闲连接),避免首次请求慢。

14. 通过反射调用私有方法需先执行setAccessible(true) 来取消 Java 语言访问检查。

私有方法默认不可访问,setAccessible(true) 可以强制访问。(注意setAccessible 会破坏封装,需谨慎使用,且在模块化系统中可能受模块限制)

  • 步骤:

    1. 获取 Class 对象。

    2. 通过 getDeclaredMethod("methodName", parameterTypes) 获取 Method 对象(包括私有)。

    3. 调用 method.setAccessible(true)

    4. 调用 method.invoke(instance, args)

15. Exception分类

Java 中的异常体系以 Throwable 为根,分为两大类:

  • Error:系统级错误,通常由 JVM 抛出,程序无法处理,如 OutOfMemoryErrorStackOverflowError

  • Exception:程序可处理的异常,又分为:

    • 受检异常(Checked Exception):编译时强制处理(try-catch 或 throws),如 IOExceptionSQLException

    • 非受检异常(Unchecked Exception / RuntimeException):运行时异常,可不用显式处理,如 NullPointerExceptionIllegalArgumentException

16. 数据库的隔离机制,脏读,不可重复读,幻读,并结合具体事例说明这三种现象如何产生。

数据库事务的隔离级别定义了并发事务之间的可见性。四种隔离级别(从低到高):

(1)读未提交(Read Uncommitted):允许脏读。

(2)读已提交(Read Committed):避免脏读,但可能发生不可重复读。

(3)可重复读(Repeatable Read):避免脏读和不可重复读,但可能发生幻读(MySQL InnoDB 通过间隙锁解决)。

(4)串行化(Serializable):最高级别,避免所有问题,但并发性能低。

并发问题

  • 脏读:一个事务读取了另一个事务未提交的数据。

    • 例:事务A将余额从100改为200(未提交),事务B读取到200,随后事务A回滚,余额仍是100,但事务B基于200做了错误操作。

  • 不可重复读:一个事务内两次读取同一数据,得到不同结果(因为另一事务修改并提交了该数据)。

    • 例:事务A第一次读取余额为100,事务B修改为200并提交,事务A再次读取得到200,两次读取不一致。

  • 幻读:一个事务内两次查询同一范围的数据,得到不同行数(因为另一事务插入或删除了数据)。

    • 例:事务A查询所有余额>100的用户,得到3条;事务B插入一条余额150的用户并提交;事务A再次查询得到4条,出现幻行。

注意:不可重复读针对同一条记录内容变化,幻读针对记录数量变化。

二、编程题

17. 输入s,找到s中最长的回文子串(反序与原序相同)

思路:回文串的中心有两种情况:

  • 奇数长度:中心是一个字符,例如 "aba" 的中心是 'b'

  • 偶数长度:中心是两个字符之间,例如 "abba" 的中心是 "bb" 之间。

我们遍历字符串的每个位置,将其作为奇数中心,并向两边扩展;同时将每个位置作为偶数中心的左半部分(即与下一个字符一起作为中心),向两边扩展。记录下能扩展的最大长度,并更新最长回文的起始和结束位置。

步骤

(1)初始化 start = 0, end = 0,用于记录最长回文的起始和结束下标。

(2)遍历字符串的每个索引 i

  • 以 i 为中心向两边扩展(奇数情况),得到长度 len1
  • 以 i 和 i+1 为中心向两边扩展(偶数情况),得到长度 len2
  • 取 len = max(len1, len2)
  • 如果 len > end - start + 1,则更新 start 和 end
    • end = i + len / 2

    • start = i - (len - 1) / 2

​​​​​​​(3)返回 s.substring(start, end + 1)

public String longestPalindrome(String s) {
    if (s == null || s.length() < 1) return "";
    int start = 0, end = 0;
    for (int i = 0; i < s.length(); i++) {
        int len1 = expandAroundCenter(s, i, i);     // 奇数中心
        int len2 = expandAroundCenter(s, i, i + 1); // 偶数中心
        int len = Math.max(len1, len2);
        if (len > end - start) {
            start = i - (len - 1) / 2;
            end = i + len / 2;
        }
    }
    return s.substring(start, end + 1);
}

private int expandAroundCenter(String s, int left, int right) {
    while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
        left--;
        right++;
    }
    // 当循环结束时,left 和 right 指向不匹配或边界外,回文长度为 right - left - 1
    return right - left - 1;
}

18. 找出给定字符中出现次数最多的字符及其出现次数

解法:使用哈希表统计频率,然后遍历找最大值。

public char mostFrequentChar(String s) {
    if (s == null || s.isEmpty()) throw new IllegalArgumentException();
    int[] count = new int[256]; // 假设 ASCII
    for (char c : s.toCharArray()) {
        count[c]++;
    }
    char maxChar = s.charAt(0);
    int maxCount = 0;
    for (char c : s.toCharArray()) {
        if (count[c] > maxCount) {
            maxCount = count[c];
            maxChar = c;
        }
    }
    // 或者遍历 count 数组
    return maxChar;
}
// 返回次数可另外获取

19. 给定一个整数数组nums,找到一个具有最大连续和的连续数组

解法:经典动态规划(Kadane算法)。遍历数组,维护当前连续和 cur 和最大和 max,若 cur 为负则重置为当前元素。

public int maxSubArray(int[] nums) {
    int max = nums[0];
    int cur = nums[0];
    for (int i = 1; i < nums.length; i++) {
        cur = Math.max(nums[i], cur + nums[i]);
        max = Math.max(max, cur);
    }
    return max;
}
// 若需返回子数组,需记录起始和结束索引

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐