java基础八股

怎么理解面向对象?简单说说封装继承多态

面向对象是一种编程范式,它将现实世界中的事物抽象为对象,对象具有属性(称为字段或属性)和行为(称为方法)。面向对象编程的设计思想是以对象为中心,通过对象之间的交互来完成程序的功能,具有灵活性和可扩展性,通过封装和继承可以更好地应对需求变化。

Java面向对象的三大特性包括:封装、继承、多态。

https和http的区别?

一、根本区别:是否加密

特性 HTTP HTTPS
全称 HyperText Transfer Protocol HyperText Transfer Protocol Secure
安全性 ❌ 明文传输,数据可被窃听、篡改 加密传输,防窃听、防篡改、防冒充
端口 80 443
URL 前缀 http:// https://

序列化和反序列化的原理

  • 序列化 :把内存中的对象 → 转成可存储或传输的格式(如字节流、JSON、XML);
  • 反序列化 :把字节流/文本 → 重新变回内存中的对象
  1. 序列化过程
  • 程序遍历对象的字段(属性)
  • 按照某种格式规则 (如 Java 的 ObjectOutputStream、JSON、Protobuf);
  • 将对象的类型信息 + 数据值 转为字节流或字符串
  • 可保存到文件、数据库,或通过网络发送。
  1. 反序列化过程
  • 读取字节流/字符串;
  • 根据其中的类型信息,创建对应类的新实例;
  • 把数据填充回对象的字段
  • 最终得到一个结构和数据都相同的对象(但内存地址不同)。

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(不完善)
  1. 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中的线程安全的集合是什么?

早期线程安全集合

  1. Vector
  • 线程安全的 List
  • 所有方法加 synchronized全局锁);
  • 性能差,已被 CopyOnWriteArrayListCollections.synchronizedList() 替代。
  1. Hashtable
  • 线程安全的 Map
  • 不允许 null,所有方法加 synchronized
  • 已被 ConcurrentHashMap 完全取代
  1. Stack
  • 继承自 Vector,同样线程安全但性能差;
  • 推荐用 Deque 代替(如 ArrayDeque)。
    java.util.concurrent 包下的高性能并发集合
  1. ConcurrentHashMap
  • 线程安全的 Map
  • JDK 1.8+ 使用 CAS + synchronized(锁桶头),支持高并发读写;
  1. CopyOnWriteArrayList
  • 线程安全的 List
  • 写时复制(Copy-On-Write) :每次修改(add/remove)都复制整个底层数组,读操作直接访问原数组(无锁);
  • 适合"读多写少" 场景(如监听器列表、白名单);
  • 写操作开销大,不适合频繁修改。
  1. CopyOnWriteArraySet
  • 基于 CopyOnWriteArrayList 实现的线程安全 Set
  • 同样适用于读多写少场景。
  1. BlockingQueue(阻塞队列) ------ 生产者-消费者模型核心
特点
ArrayBlockingQueue 有界队列,基于数组,FIFO
LinkedBlockingQueue 可选有界(默认无界),基于链表,高吞吐
SynchronousQueue 不存储元素,每个 put 必须等待 take
PriorityBlockingQueue 支持优先级的无界队列

✅ 所有操作线程安全,天然支持线程间协作

ArrasyLIst线程不安全,多线程下会报什么异常?

1、最常见的异常:ConcurrentModificationException(并发修改异常)

什么时候抛出?

  • 当一个线程正在遍历(迭代) ArrayList(比如用 for-eachiterator()),
  • 另一个线程同时修改了列表结构(如添加、删除元素),
  1. 数组越界(ArrayIndexOutOfBoundsException)
  • 在扩容过程中(ensureCapacityInternal),多个线程同时修改 size 和底层数组 elementData
  • 可能导致某个线程写入的索引超出当前数组长度。

LinkdeList多线程下会报什么异常?

  1. ConcurrentModificationException(并发修改异常)
  • 触发场景 :一个线程在遍历 LinkedList(如 for-each、iterator()),另一个线程同时修改结构add/remove)。
  • 原因LinkedList 也实现了 fail-fast 机制 ,内部有 modCount 计数器,迭代时发现修改就抛异常。
  1. NullPointerException(空指针异常)
  • 这是 LinkedList 特有的高风险问题!
  • 原因LinkedList 的节点(Node)通过 prevnext 指针连接。
    多线程并发修改(如两个线程同时 add)可能导致:
    • 链表结构被破坏(指针错乱);
    • 某个节点的 nextprev 被设为 null,但逻辑上不应为空;
    • 后续操作(如遍历、get())访问 null.next → 抛 NPE

CopyonWriteArraylist是如何实现线程安全的

CopyOnWriteArrayList底层也是通过一个数组保存数据,使用volatile关键字修饰数组,保证当前线程对数组对象重新赋值后,其他线程可以及时感知到。

java 复制代码
public class CopyOnWriteArrayList<E> {
    private transient volatile Object[] array; // 底层数组,volatile 保证可见性
    final transient ReentrantLock lock = new ReentrantLock();
}
  • arrayvolatile 的:确保一个线程修改后,其他线程能立即看到新数组;
  • 所有写操作先加锁,再复制数组,最后原子地替换 array 引用。

集合遍历的方法有哪些?

  • 普通 for 循环: 可以使用带有索引的普通 for 循环来遍历 List。

  • 增强 for 循环(for-each循环): 用于循环访问数组或集合中的元素。

  • Iterator 迭代器: 可以使用迭代器来遍历集合,特别适用于需要删除元素的情况。

    java 复制代码
    Iterator<String> iterator = list.iterator();
    while(iterator.hasNext()) {
        String element = iterator.next();
        System.out.println(element);
    }
  • 使用 forEach 方法: Java 8引入了 forEach 方法,可以对集合进行快速遍历。

    java 复制代码
    list.forEach(element ->System.out.println(element));
  • 使用 forEach 方法: Java 8引入了 forEach 方法,可以对集合进行快速遍历。

    java 复制代码
    list.stream().forEach(element ->System.out.println(element));

Hashtable的底层原理

底层数据结构:数组 + 链表

  • Hashtable 内部使用一个 Entry[] table 数组(JDK 8 之前);
  • 每个 Entry 是一个链表节点 ,包含:
    • hash:键的哈希值
    • key
    • value
    • next:指向下一个节点(解决哈希冲突)
java 复制代码
private 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

java线程-并发编程-CSDN博客

讲讲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),每段独立加锁

  1. 底层结构
  • ConcurrentHashMap 内部包含一个 Segment[] segments 数组
  • 每个 Segment 继承自 ReentrantLock相当于一个小的 HashMap
  • 每个 Segment 内部是一个 HashEntry[] 数组 + 链表。'
  1. 并发机制:分段锁(Segment Locking)
  • 默认有 16 个 SegmentconcurrencyLevel = 16);
  • 当线程操作 key 时:
    1. 先通过 hash(key) 计算出属于哪个 Segment;
    2. 只对该 Segment 加锁(其他 Segment 可并发访问);
    3. 在该 Segment 内部执行 put/get/remove。
  1. 扩容
  • 每个 Segment 独立扩容(不是整个 Map 扩容);
  • 扩容时锁住当前 Segment,不影响其他 Segment。
    JDK 1.8 版本:CAS + synchronized(取消 Segment)

核心思想:"更细粒度的锁" ------ 直接对每个桶(bin)加锁,结合 CAS 优化

  1. 底层结构(与 HashMap 几乎一致)
  • 直接使用 Node[] table(类似 HashMap 的 Node);
  • 支持 链表 + 红黑树(链表长度 ≥8 且容量 ≥64 时转树);
  • 取消了 Segment,结构更简洁。
  1. 并发机制:CAS + synchronized

当执行 put(key, value) 时:

  1. 计算 hash(key),定位到 table[i]
  2. 如果 table[i] == null
    • 使用 CAS 尝试直接插入新节点(无锁);
  3. 如果 table[i] != null(存在链表或树):
    • table[i] 的头节点加 synchronized
    • 在同步块内遍历链表/树,执行插入或更新。

优点

  • 锁粒度更细:只锁单个桶(bin),不是整个 Segment;
  • 减少内存开销(无需预分配 Segment);
  • 利用 CAS 避免锁竞争(空桶直接 CAS 插入);
  • 支持红黑树,提升长链表性能。
  1. 读操作(get)完全无锁
  • 利用 volatile 语义 保证可见性:
    • Node.valNode.next 都是 volatile
    • 写操作通过 Unsafe.putObjectVolatile() 保证立即可见;
  • 所以 get() 不需要加锁,性能极高。
  1. 扩容(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

java 复制代码
Map<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)

java 复制代码
Map<Integer, String> map = new HashMap<>();
for (User user : users) {
    map.put(user.getId(), user.getName());
}
相关推荐
【非典型Coder】2 小时前
JVM G1 和 CMS 详解与对比
java·jvm
dddaidai1232 小时前
深入JVM(二):字节码文件的结构
java·开发语言·jvm
bruk_spp2 小时前
linux gpio获取
java·linux·服务器
郝学胜-神的一滴2 小时前
Linux C++会话编程:从基础到实践
linux·运维·服务器·开发语言·c++·程序人生·性能优化
AA陈超2 小时前
LyraStarterGame_5.6 Experience系统分析
开发语言·c++·笔记·学习·ue5·lyra
SadSunset2 小时前
(5)spring的set注入
java·笔记·spring·架构
长而不宰2 小时前
使用 Canal实时监听数据库变化
java·数据库·spring boot
骚团长2 小时前
SQL server 配置管理器-SQL server 服务-远程过程调试失败 [0x800706be]-(Express LocalDB卸载掉)完美解决!
java·服务器·express
lly2024062 小时前
jQuery AJAX 简介
开发语言