某行某费 线上笔试

一、选择题和简答题

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 

比较点HashMapHashtable
出现时间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 等)都依赖反射实现 “解耦” 和 “动态化”。框架无法在编译期知道你自定义的类(比如 UserService、OrderMapper),通过反射可以在运行时加载并实例化这些类,调用指定方法。   示例:Spring 的 IOC 容器会读取配置文件(或注解),通过反射创建对象并管理依赖;MyBatis 通过反射将数据库查询结果映射到实体类的私有属性,无需手动赋值。

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

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

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

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

12. Java中Stream API

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

  • 操作分类:

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

    • 终端操作:触发实际计算,返回结果或副作用,如 forEach、collect、reduce。

  • 串行流与并行流:

    • 串行流:单线程处理,通过 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 抛出,程序无法处理,如 OutOfMemoryError、StackOverflowError。

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

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

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

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编程工具,助力开发者即刻编程。

更多推荐