怎么理解面向对象?简单说说封装继承多态
面向对象是一种编程范式,它将现实世界中的事物抽象为对象,对象具有属性(称为字段或属性)和行为(称为方法)。面向对象编程的设计思想是以对象为中心,通过对象之间的交互来完成程序的功能,具有灵活性和可扩展性,通过封装和继承可以更好地应对需求变化。
Java面向对象的三大特性包括:封装、继承、多态。
https和http的区别?
一、根本区别:是否加密
特性 HTTP HTTPS 全称 HyperText Transfer Protocol HyperText Transfer Protocol Secure 安全性 ❌ 明文传输,数据可被窃听、篡改 ✅ 加密传输,防窃听、防篡改、防冒充 端口 80 443 URL 前缀 http://https://
序列化和反序列化的原理
- 序列化 :把内存中的对象 → 转成可存储或传输的格式(如字节流、JSON、XML);
- 反序列化 :把字节流/文本 → 重新变回内存中的对象。
- 序列化过程
- 程序遍历对象的字段(属性);
- 按照某种格式规则 (如 Java 的
ObjectOutputStream、JSON、Protobuf);- 将对象的类型信息 + 数据值 转为字节流或字符串;
- 可保存到文件、数据库,或通过网络发送。
- 反序列化过程
- 读取字节流/字符串;
- 根据其中的类型信息,创建对应类的新实例;
- 把数据填充回对象的字段;
- 最终得到一个结构和数据都相同的对象(但内存地址不同)。
BIO、NIO、AIO区别是什么?
| 模型 | 全称 | 核心特点 |
|---|---|---|
| BIO | Blocking I/O(同步阻塞 I/O) | 一个连接一个线程,读写会阻塞当前线程 |
| NIO | Non-blocking I/O(同步非阻塞 I/O) | 一个线程管理多个连接,靠轮询(Selector)检查是否就绪 |
| AIO | Asynchronous I/O(异步非阻塞 I/O) | 操作完成后系统主动通知你,全程不阻塞 |
| 特性 | BIO | NIO | AIO |
|---|---|---|---|
| 模型 | 同步阻塞 | 同步非阻塞 | 异步非阻塞 |
| 线程模型 | 1 连接 : 1 线程 | 1 线程 : 多连接(Reactor 模式) | 1 线程 : 多连接(Proactor 模式) |
| 是否阻塞 | 是 | 否(但需轮询) | 否(系统通知) |
| 编程难度 | 简单 | 较复杂 | 复杂 |
| 吞吐量 | 低(连接多时崩溃) | 高 | 极高(理想场景) |
| 典型应用 | Tomcat 早期版本 | Netty、Redis、Kafka、Tomcat NIO | Java AIO(较少用) |
| 操作系统支持 | 通用 | Linux: epoll, Windows: IOCP | Windows: IOCP, Linux: 依赖 libaio(不完善) |
- BIO(Blocking I/O)------ 老派服务员
- 工作方式 :
- 服务端每 accept 一个客户端连接,就创建一个新线程处理;
- 调用
read()/write()时,线程会一直阻塞,直到数据读完或写完。- 缺点 :
- 连接数多 → 线程爆炸(内存耗尽、上下文切换开销大);
- 大量线程空等(如客户端发数据慢),资源浪费。
- 适用场景:连接数少、数据交互频繁且稳定的场景(如内部系统)。
🍽️ 类比 :
餐厅有 100 个客人,老板请了 100 个服务员,每人盯一个客人------即使客人在发呆,服务员也不能干别的。
2. NIO(Non-blocking I/O)------ 多路复用经理
- 核心组件 :
Buffer(缓冲区)、Channel(通道)、Selector(选择器)- 工作方式 :
- 一个线程通过 Selector 轮询多个 Channel;
- Channel 设置为 非阻塞模式 :
read()若无数据立即返回 0,不卡线程;- 只有当 Selector 告诉你"某个 Channel 有数据可读"时,才去处理。
- 优点 :
- 单线程可处理成千上万连接(如 Netty、Redis);
- 避免线程阻塞,资源利用率高。
- 缺点 :
- 编程复杂(需手动管理 Buffer、状态机);
- 轮询仍有 CPU 开销(但远小于 BIO 的线程开销)。
🍽️ 类比 :
1 个经理拿着对讲机(Selector),监听所有餐桌(Channel)。只有客人按铃(数据就绪),经理才过去服务。
3. AIO(Asynchronous I/O)------ 主动通知的智能系统
- 工作方式 (Java 7+,基于
java.nio.channels.AsynchronousChannel):
- 发起
read()操作时,立即返回,不阻塞线程;- 系统在后台完成 I/O ,完成后主动通知(回调或 Future);
- 线程可以去做其他事,完全不用关心 I/O 是否就绪。
- 优点 :
- 真正的异步,无需轮询;
- 在高并发、低活跃连接场景下效率极高(如聊天服务器)。
- 缺点 :
- 依赖操作系统支持(Linux 下基于 epoll 的模拟,Windows 下原生支持好);
- Java 生态中使用较少(Netty 等主流框架仍用 NIO);
- 调试和编程模型较复杂(回调地狱)。
🍽️ 类比 :
客人点完餐,系统自动记录。厨房做好后,主动呼叫服务员送餐------服务员全程不用盯着。
集合
java基础04(集合、异常、引用、线程)---java八股_java八股 异常-CSDN博客
Java中的线程安全的集合是什么?
早期线程安全集合
- Vector
- 线程安全的
List;- 所有方法加
synchronized(全局锁);- 性能差,已被
CopyOnWriteArrayList或Collections.synchronizedList()替代。
- Hashtable
- 线程安全的
Map;- 不允许
null,所有方法加synchronized;- 已被
ConcurrentHashMap完全取代。
- Stack
- 继承自
Vector,同样线程安全但性能差;- 推荐用
Deque代替(如ArrayDeque)。
java.util.concurrent包下的高性能并发集合
- ConcurrentHashMap
- 线程安全的
Map;- JDK 1.8+ 使用 CAS + synchronized(锁桶头),支持高并发读写;
- CopyOnWriteArrayList
- 线程安全的
List;- 写时复制(Copy-On-Write) :每次修改(add/remove)都复制整个底层数组,读操作直接访问原数组(无锁);
- 适合"读多写少" 场景(如监听器列表、白名单);
- 写操作开销大,不适合频繁修改。
- CopyOnWriteArraySet
- 基于
CopyOnWriteArrayList实现的线程安全Set;- 同样适用于读多写少场景。
- BlockingQueue(阻塞队列) ------ 生产者-消费者模型核心
类 特点 ArrayBlockingQueue有界队列,基于数组,FIFO LinkedBlockingQueue可选有界(默认无界),基于链表,高吞吐 SynchronousQueue不存储元素,每个 put 必须等待 take PriorityBlockingQueue支持优先级的无界队列 ✅ 所有操作线程安全,天然支持线程间协作。
ArrasyLIst线程不安全,多线程下会报什么异常?
1、最常见的异常:
ConcurrentModificationException(并发修改异常)什么时候抛出?
- 当一个线程正在遍历(迭代)
ArrayList(比如用for-each或iterator()),- 而另一个线程同时修改了列表结构(如添加、删除元素),
- 数组越界(ArrayIndexOutOfBoundsException)
- 在扩容过程中(
ensureCapacityInternal),多个线程同时修改size和底层数组elementData,- 可能导致某个线程写入的索引超出当前数组长度。
LinkdeList多线程下会报什么异常?
ConcurrentModificationException(并发修改异常)
- 触发场景 :一个线程在遍历
LinkedList(如 for-each、iterator()),另一个线程同时修改结构 (add/remove)。- 原因 :
LinkedList也实现了 fail-fast 机制 ,内部有modCount计数器,迭代时发现修改就抛异常。
NullPointerException(空指针异常)
- 这是
LinkedList特有的高风险问题!- 原因 :
LinkedList的节点(Node)通过prev和next指针连接。
多线程并发修改(如两个线程同时add)可能导致:
- 链表结构被破坏(指针错乱);
- 某个节点的
next或prev被设为null,但逻辑上不应为空;- 后续操作(如遍历、
get())访问null.next→ 抛NPE。
CopyonWriteArraylist是如何实现线程安全的
CopyOnWriteArrayList底层也是通过一个数组保存数据,使用volatile关键字修饰数组,保证当前线程对数组对象重新赋值后,其他线程可以及时感知到。
javapublic class CopyOnWriteArrayList<E> { private transient volatile Object[] array; // 底层数组,volatile 保证可见性 final transient ReentrantLock lock = new ReentrantLock(); }
array是volatile的:确保一个线程修改后,其他线程能立即看到新数组;- 所有写操作先加锁,再复制数组,最后原子地替换
array引用。
集合遍历的方法有哪些?
普通 for 循环: 可以使用带有索引的普通 for 循环来遍历 List。
增强 for 循环(for-each循环): 用于循环访问数组或集合中的元素。
Iterator 迭代器: 可以使用迭代器来遍历集合,特别适用于需要删除元素的情况。
javaIterator<String> iterator = list.iterator(); while(iterator.hasNext()) { String element = iterator.next(); System.out.println(element); }
使用 forEach 方法: Java 8引入了 forEach 方法,可以对集合进行快速遍历。
javalist.forEach(element ->System.out.println(element));使用 forEach 方法: Java 8引入了 forEach 方法,可以对集合进行快速遍历。
javalist.stream().forEach(element ->System.out.println(element));
Hashtable的底层原理
底层数据结构:数组 + 链表
Hashtable内部使用一个Entry[] table数组(JDK 8 之前);- 每个
Entry是一个链表节点 ,包含:
hash:键的哈希值keyvaluenext:指向下一个节点(解决哈希冲突)
javaprivate transient Entry<?,?>[] table; // 哈希桶数组 private transient int count; // 当前元素个数 private int threshold; // 扩容阈值 = capacity * loadFactor private final float loadFactor; // 负载因子,默认 0.75f线程安全:全表 synchronized
⚠️ 缺点:
- 并发性能极差(高并发下成为瓶颈);
- 即使多个线程操作不同 key,也会互相阻塞。
- 所有 public 方法都用
synchronized修饰- 锁的是 整个
Hashtable对象(this);- 同一时间只允许一个线程操作,其他线程阻塞。
HashMap和hashtable的区别
| 特性 | HashMap |
Hashtable |
|---|---|---|
| 线程安全 | ❌ 不安全 | ✅ 安全(所有方法加 synchronized) |
| null 键/值 | ✅ 允许一个 null key,多个 null value |
❌ 完全不允许 (会抛 NullPointerException) |
| 初始容量 | 16 | 11 |
| 扩容方式 | 容量 × 2 |
容量 × 2 + 1 |
| 底层结构(JDK 8+) | 数组 + 链表 + 红黑树(链表长度 ≥8 时转树) | 仅数组 + 链表(无红黑树优化) |
| 继承关系 | extends AbstractMap |
extends Dictionary(已过时的抽象类) |
| 迭代器 | fail-fast | fail-fast |
| 推荐使用 | ✅ 单线程首选 | ❌ 已过时,不推荐 |
ConcurrentHashMap
讲讲currenthashmap原理,分别讲了1.7和1.8的原理
| 特性 | JDK 1.7 | JDK 1.8 |
|---|---|---|
| 核心结构 | Segment 数组 + HashEntry 链表 | Node 数组 + 链表/红黑树 |
| 并发策略 | 分段锁(ReentrantLock) | CAS + synchronized(锁单个 bin) |
| 锁粒度 | Segment 级(较粗) | 桶(bin)级(更细) |
| 是否支持红黑树 | ❌ 否 | ✅ 是 |
| 扩容机制 | 单线程 per Segment | 多线程协作扩容 |
| 内存占用 | 高(预分配 Segment) | 低(按需分配) |
| 读操作 | 无锁 | 无锁(volatile 保证) |
| 性能 | 好 | 更好(尤其高并发写) |
JDK 1.7 版本:分段锁(Segment)
核心思想:"分而治之" ------ 把整个哈希表分成多个段(Segment),每段独立加锁
- 底层结构
ConcurrentHashMap内部包含一个Segment[] segments数组;- 每个
Segment继承自ReentrantLock,相当于一个小的 HashMap;- 每个
Segment内部是一个HashEntry[]数组 + 链表。'
- 并发机制:分段锁(Segment Locking)
- 默认有 16 个 Segment (
concurrencyLevel = 16);- 当线程操作 key 时:
- 先通过
hash(key)计算出属于哪个 Segment;- 只对该 Segment 加锁(其他 Segment 可并发访问);
- 在该 Segment 内部执行 put/get/remove。
- 扩容
- 每个 Segment 独立扩容(不是整个 Map 扩容);
- 扩容时锁住当前 Segment,不影响其他 Segment。
JDK 1.8 版本:CAS + synchronized(取消 Segment)核心思想:"更细粒度的锁" ------ 直接对每个桶(bin)加锁,结合 CAS 优化
- 底层结构(与 HashMap 几乎一致)
- 直接使用
Node[] table(类似 HashMap 的 Node);- 支持 链表 + 红黑树(链表长度 ≥8 且容量 ≥64 时转树);
- 取消了 Segment,结构更简洁。
- 并发机制:CAS + synchronized
当执行
put(key, value)时:
- 计算
hash(key),定位到table[i];- 如果
table[i] == null:
- 使用 CAS 尝试直接插入新节点(无锁);
- 如果
table[i] != null(存在链表或树):
- 对
table[i]的头节点加synchronized锁;- 在同步块内遍历链表/树,执行插入或更新。
优点:
- 锁粒度更细:只锁单个桶(bin),不是整个 Segment;
- 减少内存开销(无需预分配 Segment);
- 利用 CAS 避免锁竞争(空桶直接 CAS 插入);
- 支持红黑树,提升长链表性能。
- 读操作(get)完全无锁
- 利用 volatile 语义 保证可见性:
Node.val和Node.next都是volatile;- 写操作通过
Unsafe.putObjectVolatile()保证立即可见;- 所以
get()不需要加锁,性能极高。
- 扩容(transfer)
- 支持多线程协助扩容!
- 当一个线程发现需要扩容时:
- 创建新数组;
- 自己负责迁移一部分数据;
- 其他写线程在操作时发现正在扩容,会主动帮忙迁移(提高效率);
- 迁移过程中,旧数组的桶会被标记为
ForwardingNode(转发节点),get()请求会自动去新数组查。
分段锁怎么加锁的?
在 ConcurrentHashMap 中,将整个数据结构分为多个 Segment,每个 Segment 都类似于一个小的 HashMap,每个 Segment 都有自己的锁,不同 Segment 之间的操作互不影响,从而提高并发性能。
每个 Segment 都有自己的锁,不同 Segment 之间的操作互不影响,从而提高并发性能。然后再在该 Segment 上加锁,而不是像传统的 HashMap 一样对整个数据结构加锁。这样可以使得不同 Segment 之间的操作并行进行,提高了并发性能.
ConcurrentHashMap用了悲观锁还是乐观锁?
悲观锁和乐观锁都有用到。
添加元素时首先会判断容器是否为空:
如果为空则使用 volatile 加 CAS (乐观锁) 来初始化。
如果容器不为空,则根据存储的元素计算该位置是否为空。
如果根据存储的元素计算结果为空,则利用 CAS(乐观锁) 设置该节点;
如果根据存储的元素计算结果不为空,则使用 synchronized(悲观锁) ,然后,遍历桶中的数据,并替换或新增节点到桶中,最后再判断是否需要转为红黑树,这样就能保证并发访问时的线程安全了。
list转map的方法
最推荐、最简洁的方式是使用 Java 8 的 Stream API +
Collectors.toMap()。方法一:Java 8+ Stream
javaMap<K, V> map = list.stream() .collect(Collectors.toMap( element -> keyExtractor, // 如: User::getId element -> valueMapper // 如: User::getName )); // 保留新值 Map<Integer, String> map = users.stream() .collect(Collectors.toMap( User::getId, User::getName, (oldValue, newValue) -> newValue // 冲突时用 newValue ));方法二:手动 for 循环(兼容老版本 Java)
javaMap<Integer, String> map = new HashMap<>(); for (User user : users) { map.put(user.getId(), user.getName()); }