Java基础面试
一、集合
数组
ArrayList 和 LinkedList 的区别?
ArrayList 底层是数组结构,可以通过下标直接访问元素,因此查询效率较高;但在中间插入或删除元素时,可能需要移动后续元素,效率相对较低。
LinkedList 底层是双向链表结构,查询时需要从头节点或尾节点遍历,效率较低;但插入或删除元素时只需修改前后节点指针,效率较高。
因此在查询较多的场景下通常使用 ArrayList,而在频繁插入删除的场景下可以考虑使用 LinkedList。
详细可参阅:
ArrayList和LinkedList的区别?
为什么实际开发中我们几乎都用 ArrayList?
实际开发中更多使用 ArrayList,主要有三个原因:
第一,大多数业务场景是查询多、插入删除少;
第二,LinkedList 每个节点都需要额外存储前后指针,占用更多内存空间;
第三,ArrayList 基于连续内存存储,对 CPU 缓存更加友好,整体性能更优。
需要注意的是,LinkedList 虽然插入删除理论上快,但前提是已经定位到节点,而定位过程仍然需要遍历。
HashMap
HashMap 是一个用于存储键值对(key-value)的集合,线程不安全。
底层架构是什么?
在 JDK1.8 中,HashMap 的底层结构是:
数组 + 链表 + 红黑树
工作流程:
通过 key 的 hash 计算数组下标
如果没有冲突,直接存储
如果发生冲突,使用链表解决
当链表长度超过 8 时,会转换为红黑树,提高查询效率
红黑树是一种自平衡的二叉查找树,能够保证查询效率。
为什么 HashMap 的容量一定是 2 的幂?
HashMap 的容量设计为 2 的幂,是为了让:
hash % length |
可以通过:
hash & (length - 1) |
代替,从而提高计算数组下标的效率。
二、多线程
进程和线程的区别
进程是操作系统分配资源的基本单位,每个进程都有独立的内存空间;
线程是 CPU 调度的基本单位,一个进程可以包含多个线程;
同一进程内的线程共享内存和资源,因此线程创建和切换开销较小,但也更容易出现线程安全问题。
为什么线程切换比进程切换快?
进程切换需要保存和恢复整个内存空间,而线程切换只需要切换执行上下文,因此开销更小。
线程之间为什么会有线程安全问题?
因为多个线程共享同一块内存,当多个线程同时修改同一变量时,可能会导致数据不一致的问题。
线程安全问题的本质是:多个线程同时访问共享资源。
线程的生命周期和状态?
五种周期:新建、就绪、运行、阻塞、销毁。
6 种状态(NEW、RUNNABLE、BLOCKED、WAITING、TIME_WAITING、TERMINATED)
并发和并行的区别
并发是指多个任务在同一时间段内交替执行,看起来像同时发生;
并行是指多个任务在同一时刻真正同时执行,通常需要多核 CPU 支持。
Java 是并发还是并行?
Java 支持并发;在多核 CPU 环境下可以实现并行。
synchronized 的作用
synchronized 是 Java 提供的关键字,用于保证多线程环境下的线程安全。
它可以对方法或代码块加锁,保证同一时间只有一个线程可以执行,从而避免数据不一致问题。
补充说明:
属于悲观锁
是可重入锁
基于对象锁实现
什么是死锁?
死锁是指多个线程(或进程)互相持有对方需要的资源,彼此等待,导致程序无法继续执行的情况。
死锁产生的四个必要条件
互斥
请求与保持
不可剥夺
循环等待
如果记不住四个条件,记住“资源互相等待”即可。
如何避免死锁?
可以通过统一加锁顺序来避免死锁,例如规定所有线程必须按照相同顺序获取锁。
String、StringBuilder、StringBuffer
String 是不可变字符串,线程安全。
StringBuilder 是可变字符串,线程不安全,但性能较高。
StringBuffer 是可变字符串,线程安全,但性能略低。
在实际开发中,一般使用 StringBuilder。
三、MySQL
何为索引?有什么作用?
索引是数据库中用于提高查询效率的一种数据结构,本质上是一种有序的数据结构,用来加快数据的查找速度。
索引的主要作用是:
- 提高查询效率
- 加快排序和分组操作
- 减少磁盘 IO 次数
缺点是占用额外存储空间,增删改会变慢(因为要维护索引)
MySQL 索引底层是什么?
InnoDB 存储引擎使用 B+ 树作为索引结构。
为什么用 B+树,不用红黑树?
因为 B+树适合磁盘存储,能减少磁盘 IO 次数,适合范围查询。
主键索引和普通索引区别?
主键索引是唯一索引,不允许重复;普通索引允许重复。
更专业的回答是:InnoDB 的主键索引是聚簇索引^1,数据和索引存储在一起;普通索引是二级索引。
四、Redis
Redis 为什么这么快?
Redis 快的原因主要有三个:
第一,基于内存存储,避免磁盘 IO;
第二,采用单线程模型,避免线程切换开销;
第三,使用 IO 多路复用机制,提高网络处理效率。
为什么 Redis 用单线程还那么快?
因为 Redis 是基于内存操作,避免了磁盘 IO,并且单线程避免了线程切换的开销,同时通过 IO 多路复用处理高并发请求。
Redis 常用数据类型
String |
说一说hash类型的底层数据结构
Redis的哈希对象的底层存储可以使用ziplist(压缩列表)和hashtable(字典)。
当哈希类型元素个数小于hash-max-ziplist-entries 配置(默认512个),同时所有值都小于hash-max-ziplist-value配置(默认64 字节)时,Redis会使用ziplist作为哈希的内部实现。ziplist使用更加紧凑的结构实现多个元素的连续存储,所以在节省内存方面比hashtable更加优秀。当哈希类型无法满足ziplist的条件时,Redis会使用hashtable作为哈希的内部实现,因为此时ziplist的读写效率会下降,而 hashtable的读写时间复杂度为O(1)。
MySQL 和 Redis 的区别
Redis 是基于内存的 Key-Value 数据库,读写速度非常快,通常用于缓存、会话存储、排行榜等高频访问场景;
MySQL 是关系型数据库,主要用于持久化存储结构化数据,支持事务和复杂 SQL 查询。
为什么要用 Redis 做缓存?
使用 Redis 做缓存主要是为了提高系统响应速度,并减轻 MySQL 的访问压力。
由于 Redis 基于内存存储,读写性能非常高,可以将高频访问的数据放入缓存,从而减少数据库查询次数,提高系统整体性能。
常见的HTTP协议请求头有哪些?
