1. 集合类
1.1 HashMap原理
HashMap是基于哈希表实现的数据结构,每个元素由键值对组成,并实现了Serializable和Cloneable接口。它允许使用null键和null值,但不保证映射顺序。内部通过链地址法解决冲突,当容量超过负载因子时会自动扩容。与Hashtable相比,除了不同步和允许使用null之外,二者功能相似。HashMap不是线程安全的。
1.2 ConcurrentHashMap实现原理
ConcurrentHashMap通过分段锁机制实现高并发访问。数据被分成多个段(Segment),每个段都有自己的锁。线程可以同时访问不同的段,从而提高并发性能。对于需要跨段的操作如size()和containsValue(),则需要锁定所有段。Segment继承自ReentrantLock,确保其可重入性。
1.3 HashMap与Hashtable的区别
- 继承的父类不同:Hashtable继承自Dictionary类,而HashMap继承自AbstractMap类。
- 线程安全性:Hashtable的方法是同步的,而HashMap的方法默认是非同步的。
- 方法命名差异:Hashtable保留了contains方法,而HashMap将其拆分为containsKey和containsValue以避免混淆。
- null值支持:Hashtable不允许key或value为null,而HashMap允许。
- 遍历方式:两者都支持Iterator,但Hashtable还保留了较早版本的Enumeration。
- hash值计算:Hashtable直接使用对象的hashCode,而HashMap重新计算hash值。
- 数组初始化与扩容:Hashtable默认容量为11且不要求底层数组大小为2的幂次方;HashMap默认容量为16并要求必须为2的幂次方。扩容时,Hashtable将容量变为原来的两倍加一,而HashMap仅扩大一倍。
1.4 HashMap的扩容机制
随着元素增多,哈希碰撞概率增加,因此需要进行扩容以提升查询效率。当元素数量超过数组大小乘以加载因子时触发扩容,默认情况下加载因子为0.75。扩容后需重新计算元素位置,这是一项耗时操作。预估元素数量有助于减少不必要的扩容次数,例如new HashMap(2048)可以有效避免频繁调整大小。
2. 数据结构
2.1 链表与数组的区别
- 内存占用:链表的存储空间可以是连续或非连续的,而数组必须是连续的一块内存区域。相同数量的数据下,数组通常占用更少的内存。
- 长度灵活性:链表长度动态变化,数组长度固定定义。
- 访问效率:链表适合频繁插入删除操作,数组适合随机访问。
3. 线程管理
3.1 synchronized关键字的用法
- 修饰方法:使整个方法成为同步方法。
- 修饰代码块:只对特定代码段加锁。
- 修饰静态方法:针对类级别的同步。
- 修饰类:对整个类的所有实例加锁。
3.2 synchronized与Lock接口的比较
- 语法层面:synchronized是内置关键字,Lock是一个接口。
- 异常处理:synchronized在发生异常时自动释放锁,Lock需要手动调用unlock()。
- 中断响应:Lock允许等待线程响应中断,而synchronized无法做到。
- 锁获取状态:Lock可以检查是否成功获取锁,synchronized则不能。
- 读写优化:Lock更适合多线程读操作。
- 性能表现:在竞争激烈的情况下,Lock比synchronized性能更好。
3.3 volatile关键字的作用
volatile用于声明变量可能会被其他线程修改,确保每次读取到最新的值。它强制将修改立即写入主存,并使缓存失效。此外,volatile禁止指令重排,具备可见性和有序性,但不具备原子性。
3.4 线程等待机制
Object类提供了wait(), notify()和notifyAll()等方法来控制线程的等待和唤醒。wait()让当前线程进入等待状态并释放锁,notify()唤醒单个等待线程,notifyAll()唤醒所有等待线程。
3.5 ThreadPoolExecutor的工作流程
- 线程数小于核心线程数时创建新线程。
- 线程数等于或大于核心线程数且任务队列未满时,将任务放入队列。
- 线程数等于或大于核心线程数且任务队列已满:
- 若线程数小于最大线程数,则创建新线程。
- 若线程数等于最大线程数,则拒绝新任务。
3.6 ThreadPoolExecutor的默认参数
- corePoolSize=1
- queueCapacity=Integer.MAX_VALUE
- maxPoolSize=Integer.MAX_VALUE
- keepAliveTime=60秒
- allowCoreThreadTimeout=false
- rejectedExecutiOnHandler=AbortPolicy()
3.7 常见线程池类型
- newCachedThreadPool:适用于短期异步任务,核心线程数为0,最大线程数无限制,空闲线程存活时间为60秒。
- newFixedThreadPool:适用于长期运行的任务,固定数量的核心线程,任务队列为无界阻塞队列。
- newSingleThreadExecutor:适用于串行执行的任务,只有一个线程,任务队列为无界阻塞队列。
- newScheduledThreadPool:支持定时及周期性任务,核心线程数固定,最大线程数无限制,任务队列为按超时时间排序的延迟队列。
4. Java虚拟机(JVM)
4.1 JVM工作原理
JVM负责解释执行Java字节码文件。首先,前端编译器将.java源文件编译成.class字节码文件,然后JVM加载这些字节码文件到内存中,最后通过即时编译器(JIT)将字节码转换为本地机器码。
4.2 运行时数据区
- 程序计数器:记录当前线程执行的字节码行号。
- 栈:保存方法调用的局部变量、操作数栈等信息。
- 堆:存放几乎所有的对象实例,是垃圾回收的主要区域。
- 方法区:存储类信息、常量、静态变量等。
4.3 对象回收判断
- 引用计数法:通过计数器跟踪对象引用次数,但难以处理循环引用问题。
- 可达性分析:从GC Roots出发追踪对象引用链,未被引用的对象标记为可回收。
4.4 垃圾回收算法
- 标记-清除:标记不再使用的对象并清除它们,但会导致内存碎片化。
- 复制算法:将存活对象复制到另一块内存区域,适用于新生代回收。
- 标记整理:结合标记-清除与整理,消除内存碎片。
- 分代收集:根据对象生命周期划分内存区域,采用不同回收策略。
4.5 内存分配与回收策略
- 优先在Eden区分配对象,必要时触发Minor GC。
- 大对象直接进入老年代,避免频繁复制。
- 长期存活的对象逐渐晋升至老年代。
5. HTTP协议
5.1 HTTP连接复用
HTTP 1.1引入了持久连接特性,允许多个请求共享同一个TCP连接,减少了重复建立连接的时间开销。
5.2 Keep-Alive与TCP KEEPALIVE的区别
- HTTP Keep-Alive:保持TCP连接活跃,允许多次请求复用同一连接。
- TCP KEEPALIVE:检测TCP连接状态,防止因长时间无活动而导致连接断开。
HTTP位于应用层,主要用于优化网络通信效率;TCP KEEPALIVE属于传输层,用于维持连接稳定性。
6. Git操作
- git cherry-pick:将指定提交应用于另一个分支。
- git revert:创建新的提交撤销指定更改。
- git rebase:合并多个提交为一个完整提交。
- git reset:回退到指定的历史版本。
7. 基本数据类型
7.1 字符串替换方法
- replace():简单替换字符串。
- replaceAll():使用正则表达式匹配并替换所有符合条件的子串。
- replaceFirst():仅替换第一个匹配项。
7.2 int与Integer的比较
Integer与int之间存在自动装箱拆箱机制。当两个Integer对象数值在-128到127范围内时,==比较返回true;超出此范围或涉及new操作时,即使数值相同也会返回false。int与Integer比较时,后者会被自动拆箱为前者,结果始终为true。
8. 其他
8.1 XML解析方式
- SAX:事件驱动模型,适合处理大型XML文件,速度快且占用资源少。
- DOM:文档对象模型,将整个XML文档加载到内存中,便于随机访问节点,但占用较多内存。
- PULL:类似于SAX,但提供更灵活的控制,允许中途停止解析。