编程语言基础 - 面试题库
一、基础题 ⭐
Q1. C# 中 async/await 的工作原理是什么?
答案:async/await 是编译器支持的异步编程语法。编译器会将带有 async 标记的方法转换为一个状态机(IAsyncStateMachine),其中每个 await 表达式对应一个状态点。当遇到 await 时,如果任务尚未完成,方法会返回一个 Task 给调用者,并在任务完成后通过回调机制恢复执行。整个过程不会阻塞线程,而是利用线程池来执行异步操作。await 并不创建新线程,它只是等待一个 Task 完成。 关联知识点:异步编程、状态机、Task、线程池
Q2. Java 中 HashMap 的底层结构是什么?
答案:Java 8 之后 HashMap 采用数组 + 链表 + 红黑树的结构。数组是主体,通过 hash 值计算索引位置。当发生 hash 冲突时,使用链地址法解决,同一位置的元素以链表形式连接。当链表长度超过阈值(默认 8)且数组长度大于等于 64 时,链表会转换为红黑树,将查找时间复杂度从 O(n) 优化到 O(log n)。HashMap 是非线程安全的,允许 null 键和 null 值。 关联知识点:数据结构、哈希表、红黑树、线程安全
Q3. Python 中列表(list)和元组(tuple)的区别是什么?
答案:列表是可变序列,创建后可以增删改元素;元组是不可变序列,一旦创建就不能修改。由于元组的不可变性,它可以作为字典的键,而列表不行。元组在内存占用上更轻量,创建和访问速度略快。语义上,列表用于存储同类型、可变化的数据集合,元组用于存储异构的、结构化的数据(类似记录)。元组的不可变性也使其在某些场景下更安全,避免意外修改。 关联知识点:数据结构、不可变性、内存管理
Q4. C# 中的值类型和引用类型有什么区别?
答案:值类型(如 int、struct、enum)直接存储数据,通常分配在栈上(但作为类成员时随类分配在堆上)。引用类型(如 class、interface、delegate)存储的是对堆上对象的引用。值类型赋值时进行值拷贝,引用类型赋值时拷贝引用。值类型不能为 null(除非使用 Nullable
Q5. Java 中 == 和 equals() 的区别是什么?
答案:== 是运算符,对于基本类型比较值是否相等,对于引用类型比较的是内存地址是否相同。equals() 是 Object 类的方法,默认实现也是用 == 比较地址,但很多类(如 String、Integer)重写了 equals() 来比较内容是否相等。使用 equals() 时应注意:需要重写 hashCode() 保证相等对象有相同哈希码;需要满足自反性、对称性、传递性和一致性。对于字符串比较,应使用 equals() 而非 ==。 关联知识点:对象比较、hashCode 契约、字符串处理
Q6. Python 中的 GIL(全局解释器锁)是什么?对多线程有什么影响?
答案:GIL 是 CPython 解释器中的一个互斥锁,保证同一时刻只有一个线程执行 Python 字节码。这意味着即使在多核 CPU 上,Python 的多线程也无法真正实现并行计算。GIL 的存在简化了 CPython 的内存管理(特别是引用计数)。对于 CPU 密集型任务,多线程无法提升性能,应使用多进程(multiprocessing);对于 I/O 密集型任务,多线程仍然有效,因为 I/O 操作会释放 GIL。其他实现如 Jython、PyPy 没有 GIL。 关联知识点:并发编程、解释器设计、多进程
二、进阶题 ⭐⭐
Q7. C# 中 delegate、Action、Func 和 Predicate 有什么区别?
答案:delegate 是 C# 中定义方法签名的类型,可以指向具有匹配签名的方法。Action 是 delegate 的封装,表示无返回值的方法,支持 0-16 个参数。Func 也表示 delegate,但必须有返回值,最后一个泛型参数是返回类型。Predicate 是 Func<T, bool> 的别名,专门用于返回布尔值的判断方法。Action 和 Func 是框架预定义的泛型委托,使用更方便,避免了为每个方法签名单独声明 delegate 类型。它们都支持多播委托(+= 组合多个方法)。 关联知识点:委托、泛型、函数式编程
Q8. Java 中 synchronized 和 ReentrantLock 的区别是什么?
答案:synchronized 是 JVM 内置关键字,自动获取和释放锁,使用简单但不灵活。ReentrantLock 是 JUC 包中的 API 类,需要手动 lock() 和 unlock(),通常放在 finally 块中。ReentrantLock 提供更多功能:支持公平锁和非公平锁选择、可中断锁获取(lockInterruptibly)、超时尝试(tryLock)、可绑定多个 Condition。性能方面,Java 6 之后 synchronized 经过锁升级优化(偏向锁→轻量级锁→重量级锁),两者性能差距不大。推荐优先使用 synchronized,需要高级功能时选 ReentrantLock。 关联知识点:并发控制、锁机制、JUC
Q9. Python 中装饰器(decorator)的工作原理是什么?请举例说明。
答案:装饰器本质是接受函数作为参数并返回新函数的高阶函数,用于在不修改原函数代码的情况下增强其功能。使用 @ 语法糖在函数定义时应用。装饰器在函数定义时执行(而非调用时),返回的包装函数替换原函数。典型应用包括日志记录、性能测试、权限校验、缓存等。带参数的装饰器需要三层嵌套:外层接收参数,中层接收函数,内层是实际包装函数。functools.wraps 用于保留原函数的元信息(如 name、doc)。 关联知识点:高阶函数、闭包、元编程
Q10. C# 中 LINQ 的延迟执行(Deferred Execution)是什么?有什么影响?
答案:LINQ 查询在定义时不会立即执行,而是在遍历结果(如 foreach、ToList()、ToArray())时才真正执行,这称为延迟执行。延迟执行的好处是查询可以组合而不产生中间结果,提高效率;数据源变化时查询结果也会变化。但这也可能导致问题:多次遍历会多次执行查询,如果数据源在此期间改变,结果可能不一致。使用 ToList()、ToArray() 等方法可以强制立即执行,将结果物化到内存中。Where、Select 等是延迟的,Count、First 等会立即执行。 关联知识点:查询表达式、迭代器、性能优化
Q11. Java 中反射(Reflection)的用途和性能影响是什么?
答案:反射允许程序在运行时获取类的信息并动态调用方法、访问字段。主要用途包括:框架开发(如 Spring 的依赖注入)、ORM 映射、序列化/反序列化、动态代理。反射的性能开销较大:方法调用比直接调用慢数倍,因为需要安全检查、参数验证和动态解析。优化方式包括:缓存 Method/Field 对象、使用 setAccessible(true) 跳过访问检查、在热点代码中避免反射。Java 9 之后模块系统对反射有更多限制。MethodHandle 和 VarHandle 是更现代的替代方案。 关联知识点:元编程、框架设计、性能优化
Q12. Python 中生成器(generator)和迭代器(iterator)的关系是什么?
答案:迭代器是实现了 iter() 和 next() 方法的对象,用于逐个产生值。生成器是一种特殊的迭代器,使用 yield 关键字定义,函数执行到 yield 时暂停并返回值,下次调用时从暂停处恢复。生成器自动实现了迭代器协议,无需手动编写类。生成器表达式(如 (x*2 for x in range(10)))是更简洁的写法。生成器的优势是惰性求值,内存占用恒定,适合处理大数据流或无限序列。生成器只能遍历一次,耗尽后需重新创建。 关联知识点:迭代协议、惰性求值、内存优化
Q13. C# 中的垃圾回收(GC)机制是如何工作的?
答案:.NET GC 采用分代回收策略,将对象分为 0、1、2 三代和大型对象堆(LOH)。新对象分配在 0 代,经过一次回收仍存活的晋升到 1 代,以此类推。0 代回收最频繁,2 代最少。GC 使用标记-压缩算法,先标记可达对象,然后压缩内存消除碎片。大对象(>85000 字节)直接分配在 LOH,默认不压缩。.NET 4.5.1 之后支持 LOH 压缩。GC 触发条件包括 0 代空间不足、显式调用 GC.Collect()、系统内存不足。现代 .NET 提供工作站 GC 和服务器 GC 两种模式。 关联知识点:内存管理、分代回收、性能调优
Q14. Java 中类加载器的双亲委派模型是什么?有什么作用?
答案:双亲委派模型规定:类加载器收到加载请求时,先委托父加载器加载,只有父加载器无法完成时才自己加载。加载器层次从顶到下是:启动类加载器(Bootstrap)→ 扩展类加载器(Extension)→ 应用程序类加载器(Application)→ 自定义加载器。作用:保证核心类库只被加载一次,避免重复加载导致类型不一致;防止用户自定义类冒充核心类(如 java.lang.String)。破坏双亲委派的场景包括:SPI 机制(如 JDBC)、热部署、代码隔离(如 Tomcat)。Java 9 引入模块系统后有所变化。 关联知识点:类加载、JVM、模块化
三、高级题 ⭐⭐⭐
Q15. C# 中 Span 和 Memory 的作用是什么?解决了什么问题?
答案:Span
Q16. Java 中 CompletableFuture 相比 Future 有什么优势?如何使用?
答案:Future 是 Java 5 引入的异步结果占位符,功能有限:只能 get() 阻塞获取结果或取消,无法组合多个异步操作、无法设置回调。CompletableFuture 是 Java 8 引入的增强版,实现了 CompletionStage 接口,支持:链式调用(thenApply、thenAccept)、组合多个 Future(thenCombine、allOf)、异常处理(exceptionally、handle)、手动完成(complete)。支持自定义 Executor,避免使用公共 ForkJoinPool。推荐用 supplyAsync 启动异步任务,用 thenXxx 组合,用 handle 统一处理异常,避免阻塞调用。 关联知识点:异步编程、函数式组合、响应式编程
Q17. Python 中 asyncio 的事件循环(Event Loop)是如何工作的?
答案:asyncio 基于单线程事件循环模型,通过 I/O 多路复用(epoll/kqueue)实现并发。事件循环维护一个就绪队列和 I/O 注册表,不断循环:检查是否有已完成的 Future/Task,执行其回调;等待 I/O 事件(带超时);处理定时任务。async/await 语法在底层转换为协程对象,通过 ensure_future 包装为 Task 注册到事件循环。阻塞操作会阻塞整个事件循环,应使用 run_in_executor 将 CPU 密集型任务放入线程池。asyncio 适合 I/O 密集型场景,不适合 CPU 密集型。 关联知识点:事件驱动、I/O 多路复用、协程
Q18. C# 中 Source Generator 的工作原理和应用场景是什么?
答案:Source Generator 是 .NET 5 引入的编译器扩展,允许在编译时分析用户代码并生成额外的 C# 源文件。它在语法树分析阶段运行,可以读取程序集中的类型、属性、特性等信息,然后输出新的 .cs 文件参与编译。与反射相比,Source Generator 在编译时完成工作,零运行时开销。典型应用:序列化器生成(如 System.Text.Json 源生成)、依赖注入容器、属性变更通知(INotifyPropertyChanged)、路由注册、DTO 映射。它改变了 .NET 生态中许多框架的实现方式,从运行时反射转向编译时代码生成。 关联知识点:编译时编程、代码生成、性能优化
Q19. Java 中虚拟线程(Virtual Threads)与传统线程的区别是什么?
答案:虚拟线程是 Java 21 正式推出的轻量级线程(Project Loom),由 JVM 管理而非操作系统。传统线程是 OS 线程的 1:1 映射,栈内存固定(通常 1MB),创建成本高,数量受限。虚拟线程是 M:N 模型,大量虚拟线程复用少量 OS 线程(载体线程),栈内存按需增长,初始仅几百字节。虚拟线程适合高并发 I/O 场景,可以轻松创建百万级线程。阻塞操作(如 I/O、sleep)会自动卸载载体线程。虚拟线程不适合 CPU 密集型或长时间持有锁的场景。使用 Thread.startVirtualThread() 创建。 关联知识点:并发模型、线程调度、高并发
Q20. Python 中元类(metaclass)的作用是什么?与普通类有什么区别?
答案:元类是创建类的类,type 是默认的元类。普通类创建实例,元类创建类。通过定义 metaclass 可以控制类的创建过程:修改类属性、自动注册类、强制实现某些方法、添加描述符等。元类的 new 方法在类定义结束时调用,接收类名、基类列表和属性字典。典型应用:ORM 框架中自动将类属性映射为数据库字段、API 框架中自动注册路由、ABC 抽象基类强制子类实现方法。元类功能强大但复杂,Python 3 的类装饰器可以替代部分场景。 关联知识点:元编程、类创建机制、框架设计
四、跨语言对比题 🔄
Q21. C#、Java、Python 中的异常处理机制有什么异同?
答案:三者都使用 try-catch-finally 结构。C# 和 Java 是编译型语言,异常是类型安全的,所有异常继承自基类(Exception)。Java 有受检异常(checked exception),编译器强制处理;C# 只有非受检异常。Python 是动态类型语言,异常也是对象,但没有受检异常概念。C# 支持 using 语句自动释放资源;Java 有 try-with-resources;Python 有 with 语句和上下文管理器。Python 的 else 子句在无异常时执行,finally 总是执行。三者都支持自定义异常和异常链。 关联知识点:异常处理、资源管理、语言设计
Q22. 三种语言的垃圾回收机制有什么主要差异?
答案:Java 使用分代 GC,有多种收集器(G1、ZGC、Shenandoah),可针对吞吐量和延迟优化。Java GC 暂停时间从毫秒到秒级不等,ZGC 可实现亚毫秒暂停。C# GC 也是分代回收,但更强调低延迟,有工作站和服务器两种模式,.NET Core 之后引入后台 GC 减少暂停。Python 主要使用引用计数,辅以标记-清除处理循环引用,还有分代 GC 优化。Python 的 GIL 简化了 GC 实现。Java 和 C# 的 GC 更成熟,适合大规模应用;Python 的 GC 简单但可预测性差。 关联知识点:内存管理、GC 算法、性能调优
Q23. C# 的 LINQ、Java 的 Stream API、Python 的生成器表达式在数据处理上有什么异同?
答案:三者都提供函数式数据处理能力。LINQ 语法最丰富,支持查询语法和方法语法,延迟执行,可查询多种数据源(对象、数据库、XML)。Java Stream API 是方法链式调用,支持串行和并行流,一次性使用(消费后不能重用)。Python 生成器表达式和内置函数(map、filter)最轻量,惰性求值,与语言深度集成。性能方面:LINQ 经过多年优化最好;Stream API 并行流有额外开销;Python 生成器最轻量但受 GIL 限制。三者都支持 map、filter、reduce 等操作。 关联知识点:函数式编程、数据处理、惰性求值
Q24. 三种语言的并发编程模型有什么本质区别?
答案:Java 传统模型基于 OS 线程和共享内存,使用 synchronized、Lock、volatile 等机制,需要处理竞态条件和死锁,模型复杂但功能强大。C# 模型与 Java 类似,但 async/await 语言级支持更完善,Task 抽象更统一。Python 受 GIL 限制,多线程不适合 CPU 并行,推荐多进程(multiprocessing)或 asyncio 协程模型。asyncio 是单线程事件循环,适合 I/O 并发。Java 21 的虚拟线程和 C# 的 Task 都向轻量级并发发展,Python 也在探索无 GIL 的实现。 关联知识点:并发模型、GIL、异步编程
Q25. 三种语言在序列化和反序列化方面的方案有什么差异?
答案:Java 有内置序列化(Serializable),但性能差、安全性问题多,现代项目多用 Jackson(JSON)、Protobuf、Kryo。C# 有 BinaryFormatter(已弃用,不安全),推荐 System.Text.Json、XmlSerializer、Protobuf。Python 有 pickle(灵活但不安全,不能跨语言),json 模块用于 JSON 序列化。跨语言场景推荐 Protobuf、JSON、MessagePack。性能方面:二进制格式(Protobuf、Kryo)远快于文本格式。安全性方面:反序列化不可信数据都有风险,需要白名单校验。现代趋势是代码生成方案(如 Protobuf 编译生成类)替代反射方案。 关联知识点:序列化、数据交换、安全性