2025.06.07

1. Java 中操作字符串有哪些类型?它们之间有什么区别?

Java 主要有三种可操作“字符序列”的类型:

  • String

    • 特点:不可变(immutable)、底层基于字符数组 char[]

    • 适用:读多写少的场景,它一旦创建,内容就不可变,保证了线程安全

  • StringBuilder

    • 特点:可变(mutable)、非线程安全,底层同样是 char[],扩容时按 oldCapacity * 2 + 2

    • 适用:单线程下大量拼接字符串的场景,效率比 String + 高,比 StringBuffer

  • StringBuffer

    • 特点:可变、线程安全(方法上用 synchronized),同样基于 char[]

    • 适用:多线程需要频繁修改字符串的场景,但性能低于 StringBuilder

  • CharSequence

    • 说明StringStringBuilderStringBuffer 等都实现了它,表示一段可读的字符序列


2. String 类的常用方法及其作用

  • 长度与内容

    • int length():返回字符串长度(字符个数)。

    • boolean isEmpty():判断字符串是否为空(length() == 0)。

    • char charAt(int index):返回指定下标处的字符,0-based。

    • char[] toCharArray():将字符串转换为 char[]

    • byte[] getBytes():按平台默认编码将字符串转换成字节数组。

  • 比较与查找

    • boolean equals(Object):区分大小写比较内容是否相同。

    • boolean equalsIgnoreCase(String):忽略大小写比较内容是否相同。

    • int compareTo(String another):按字典序比较,返回负/零/正。

    • int indexOf(String/char):查找某子串或字符第一次出现的位置,找不到返回 -1

    • int lastIndexOf(String/char):查找子串或字符最后一次出现的位置,找不到返回 -1

    • boolean contains(CharSequence):判断是否包含指定子串。

    • boolean startsWith(String):判断是否以指定前缀开始。

    • boolean endsWith(String):判断是否以指定后缀结束。

  • 截取与分割

    • String substring(int beginIndex):截取从 beginIndex 到末尾的子串。

    • String substring(int beginIndex, int endIndex):截取从 beginIndex(含)到 endIndex(不含)的子串。

    • String[] split(String regex):按正则表达式拆分,返回字符串数组。

    • String trim():去除两端空白字符(space、tab、换行等),不影响中间空格。

  • 替换

    • String replace(char oldChar, char newChar):全局替换字符。

    • String replace(CharSequence target, CharSequence replacement):全局替换字符串(不支持正则)。

    • String replaceAll(String regex, String replacement):按正则替换,所有匹配均被替换。

    • String replaceFirst(String regex, String replacement):按正则替换,仅替换第一个匹配。

  • 大小写 & 格式化

    • String toLowerCase():转为全小写。

    • String toUpperCase():转为全大写。

    • String format(String fmt, Object… args):按格式化字符串生成新字符串(类似 printf)。

  • 连接与常量池

    • static String join(CharSequence delimiter, CharSequence… elements):将多个序列用 delimiter 连接成一个字符串。

    • String intern():将字符串加入常量池并返回池中引用;若已存在则返回已有引用,便于节省内存和快速比较。

  • 其它

    • boolean matches(String regex):判断整个字符串是否匹配给定正则。

    • 拼接建议:大量拼接时推荐使用 StringBuilderStringBuffer,最后调用 toString() 得到新 String


3. List、Set、Map 之间的区别是什么?

  • List

    • 特点:有序、允许重复

    • 常用实现ArrayListLinkedList

    • 适用:需要按下标访问、允许重复元素的场景

  • Set

    • 特点:无序(或排序)、不允许重复

    • 常用实现HashSetLinkedHashSetTreeSet

    • 适用:需要去重的场景

  • Map

    • 特点:键值对结构,key 不可重复,value 可重复

    • 常用实现HashMapLinkedHashMapTreeMap

    • 适用:需要快速根据 key 查找 value 的场景


4. HashMap 的实现原理

  • 底层结构:数组 + 链表/红黑树(当单个桶中元素过多时,链表转为红黑树以优化性能)

  • 哈希定位hash(key) → 扰动后与 (capacity - 1) 做位与,定位到数组下标

  • 插入

    • 若目标桶为空,直接插入

    • 否则遍历链表/树,若 key 相同则覆盖 value,否则追加到末尾/树中

  • 扩容:当元素数超过 loadFactor(默认 0.75)时,容量翻倍,并重新 rehash 所有元素

  • 查询:同哈希定位过程,遍历桶内链表/树,通过 equals 比较 key,找到则返回对应 value


5. ArrayList 和 LinkedList 的区别

  • ArrayList

    • 底层结构:动态数组 Object[]

    • 随机访问:O(1)

    • 任意位置插入/删除:O(n)(需要移动元素)

    • 尾部插入/删除:均摊 O(1)

    • 适用:读多写少、需要频繁随机访问的场景

  • LinkedList

    • 底层结构:双向链表

    • 随机访问:O(n)

    • 任意位置插入/删除:O(1)(只改动指针)

    • 尾部操作:O(1)

    • 适用:插入/删除操作频繁的场景


6. 创建线程池的几种方式

  • Executors 工厂方法

    • Executors.newFixedThreadPool(int n)

    • Executors.newCachedThreadPool()

    • Executors.newSingleThreadExecutor()

    • Executors.newScheduledThreadPool(int corePoolSize)

  • ThreadPoolExecutor 构造

    new ThreadPoolExecutor(
        corePoolSize,
        maximumPoolSize,
        keepAliveTime, TimeUnit.SECONDS,
        new LinkedBlockingQueue<>(),
        threadFactory,
        rejectedExecutionHandler
    );
    

  • ForkJoinPool

    • 适合分治任务,内部使用工作窃取算法

  • Spring 注解方式

    • @EnableAsync + 自定义 TaskExecutor

    • @Scheduled 定时任务线程池


7. 什么是死锁?怎么防止死锁?

  • 死锁:两个或多个线程各自持有对方需要的资源并相互等待,导致都无法继续执行的状态

  • 防止策略

    • 固定顺序加锁:所有线程按同一顺序请求锁,避免循环等待

    • 尝试加锁(tryLock):使用带超时的 tryLock(timeout),超时释放已持有锁再重试

    • 锁分离/细化:缩短持锁时间,减少锁竞争

    • 死锁检测与资源剥夺(复杂,少用)


  • Cookie

    • 存储位置:客户端(浏览器)

    • 容量:≈4KB

    • 生命周期:可设置过期时间

    • 安全性:易被篡改

  • Session

    • 存储位置:服务器

    • 容量:受服务器内存/存储限制

    • 生命周期:通常浏览器关闭或超时后失效

    • 安全性:只在客户端保存 sessionId,相对更安全

  • Session 工作原理

    1. 客户端首次请求时,服务器创建 HttpSession 并生成唯一 sessionId

    2. 服务器通过 Set-Cookie: JSESSIONID=… 下发给客户端

    3. 后续请求浏览器携带该 Cookie,服务器根据 sessionId 找到对应 HttpSession

    4. HttpSession 中存取用户信息、购物车等会话数据


9. 什么是 Spring Boot?Spring Boot 的优点

  • Spring Boot:基于 Spring 框架的快速开发平台,通过“约定大于配置”和自动化配置简化 Spring 应用搭建

  • 优点

    • 开箱即用:内嵌 Tomcat/Jetty,无需外部容器

    • 自动配置:根据类路径依赖自动装配常用组件

    • Starter POMs:一组 Starter 简化依赖管理

    • 生产级监控:Actuator 提供健康检查、指标等

    • 极简配置:application.propertiesyml 一处配置即可


10. Spring Cloud 的核心组件有哪些?

  • Spring Cloud Config(配置中心)

  • Eureka / Consul / Zookeeper(服务注册与发现)

  • Ribbon(客户端负载均衡)

  • Feign(声明式 HTTP 客户端,集成 Ribbon)

  • Hystrix / Resilience4j(断路器)

  • Zuul / Spring Cloud Gateway(API 网关)

  • Bus(事件总线,用于广播配置变更)

  • Sleuth + Zipkin(分布式链路跟踪)

  • Stream(消息驱动微服务)


11. MyBatis 的一级缓存和二级缓存

  • 一级缓存

    • 作用域:SqlSession 级别,同会话中相同语句与参数直接从缓存取

    • 默认开启;commit/rollback/clearCache 会清空

  • 二级缓存

    • 作用域:Mapper(namespace)级别,跨 SqlSession 共享

    • 默认关闭,需要在全局与单个 mapper 中配置 <setting name="cacheEnabled" value="true"/><cache/>,且返回对象须可序列化


12. 自增表中删除后重启再插入,ID 会是多少?

  • InnoDB(默认):删除 ID 6、7 后剩 1~5,重启时自增值 = MAX(id)+1 = 6,插入的新记录 ID 为 6

    • 重启后:InnoDB 会根据表中现有的最大 id(此例为 5)来重新计算下一个自增值,变成 5 + 1 = 6

    • 未重启:自增计数器仍在内存里,继续累加,所以看到的是 8。

  • MyISAM:自增计数器保存在磁盘,重启不变,新记录 ID 为 8


13. ACID 是什么?

  • A (Atomicity,原子性):事务要么全部成功,要么全部失败(回滚)

  • C (Consistency,一致性):事务执行前后保持数据完整性约束

  • I (Isolation,隔离性):并发事务互不干扰,可设不同隔离级别

  • D (Durability,持久性):事务一旦提交,对数据的修改永久保存


14. MySQL 索引是怎么实现的?

  • B+ 树索引:最常用,非叶子节点存 key 和子节点指针;叶子节点存完整 key + 指向数据行的指针(聚簇索引直接存数据行)

  • Hash 索引(MEMORY 引擎):通过哈希表实现,等值查询快,范围查询和排序不支持

  • 全文索引:基于倒排索引,支持 MATCH ... AGAINST

  • R-Tree 索引(MyISAM 空间索引):用于地理空间数据


15. JVM 运行时数据区

  • 程序计数器 (PC Register):存放当前线程所执行字节码的行号指示器

  • Java 栈 (Stack):每个方法调用时创建的栈帧,存储局部变量、操作数栈、动态链接、方法出口等

  • 本地方法栈 (Native Stack):为执行本地方法服务

  • 堆 (Heap):存放所有对象实例和数组,是 GC 管理的重点区域

  • 方法区 (Method Area):存放类信息、常量、静态变量、即时编译器编译后的代码等(HotSpot 中的 Metaspace)

  • 运行时常量池 (Runtime Constant Pool):方法区的一部分,存放编译期产生的各种字面量和符号引用

  • 直接内存 (Direct Memory):不属于 JVM 内存一部分,通过 NIO 等方式分配,用于高效 I/O

16.服务器被入侵了怎么办?

  • 立即断网隔离

  • 保存证据和日志

  • 修改所有密码(包括数据库、应用等)

  • 评估影响范围

  • 备份核心数据

  • 完全重置系统

  • 重装后及时更新系统补丁

  • 从可信备份恢复数据

  • 加强安全配置和监控

  • 编写事件报告