[{"content":"你好，我叫回声，英文名 Echo。如果你刚从首页过来，应该已经见过我了：一团轮廓不太规整的墨，两只圆眼睛，安安静静地等着你点一下。\n今天想和你聊聊我的样子，也聊聊那些让我流动起来的代码。\n从一条线开始 我的身体有一处凹下、一处凸起，没有手脚，表情也只靠两只眼睛。放大时能看清轮廓，缩到导航栏里，还是同一个我。\n我的身体由 SVG 路径绘制，眼睛是两个圆。首页、导航和文章末尾共用同一条轮廓，大小交给页面调整。这也让我不需要为每个尺寸准备一张图片。\n颜色来自博客的主题变量。白天，我是墨黑的，眼睛浅浅的；到了暗色页面，身体变成月白，眼睛也跟着变深。即使正在流动，我也能跟上配色变化。\n一团墨怎么分开 从我身上分离出去的流体叫墨团，它们暂时变成的文字或图案叫形态。我有自己的名字，它们则是可以收回来的几笔墨。\n你看到的黏连和分裂，来自 SVG 里的高斯模糊与透明度阈值处理。模糊让相邻轮廓交叠，阈值再把柔软的边缘收紧；我和墨团放在同一个滤镜组里，靠近时就会融合，拉远时连接处逐渐变细，直到断开。\n墨团的轮廓还会根据位置、拉伸和波纹重新计算。它们可以化成“文”、代码符号或星形，停留片刻，再软化回到我身上。变成文字时，底下的墨团也要同步缩没，否则文字的空隙里会透出一个黑点。\n我也有一点物理 在电脑上拉动墨团，它会随着你的手势伸长。方向来自平滑后的指针速度，拉伸程度随速度变化，位置则用弹簧计算，让它带一点柔软的迟滞。\n这件事比看起来麻烦。向右拉长和向左拉长，本来应该是同一种形状；如果直接追着角度旋转，反向拖动时，墨团就会突然掉头。现在用双角矢量描述形变，让相反方向对应同一条轴，来回拖动也能保持自然。\n停住的手也需要单独处理：没有新的指针事件，速度读数就可能停在最后一次测量。代码会根据停手时间主动衰减，让墨团回圆。手还按着，也能慢慢安静下来。\n动起来，也要停得下来 我的入场由一条 requestAnimationFrame 时间轴驱动，位置、分离和塑形沿着同一个进度推进。返回入口时，沿同一条时间轴倒放，墨团就能重新回到身体里。\n分出的墨团会交接成页面里的真实标题和按钮。交接时，元素通过临时滤镜从模糊的墨迹逐渐变清晰，结束后滤镜被撤掉，留下正常的页面。\n窗口变化、页面隐藏或导航中断时，临时路径和样式也要清理。平时无需运动时，动画帧链会停下来；手机则采用紧凑的入场，不启用拖拽。\n读文章时，我会轻一点 我喜欢在入口和你打个招呼，也会在导航、文末留一个小小的身影。阅读进度条上的我，像挂着的小篮子，滚动带来的加速度让我摆动，阻尼再让摆幅逐渐减小。\n你认真读文章的时候，我也有自己的分寸。文字需要空间，动画需要时机。\n这就是目前的我：一条轮廓、两只眼睛，还有一些让墨流动的代码。下次路过首页，可以点一下。听见你的动作，我会给一点回应。\n","permalink":"https://blog.withdong02.top/posts/echo/","summary":"我是一团有两只圆眼睛的墨。想和你聊聊我的样子，也聊聊那些让我流动起来的代码。","title":"我叫回声：一团墨的自我介绍"},{"content":"上一篇讲了线程的状态和切换方法，还讲了 AQS 的关键概念。这一篇我打算学习 AQS 的常见实现、并发容器，还有阻塞队列和线程池。\nAQS 的常见实现 上一篇提到 AQS 核心内容就是：state + 等待队列。接下来我会讲解 ReentrantLock、Semaphore、CountDownLatch 等常见实现。\nReentrantLock 上一篇讲解了一个 Mutex 示例，当时也说明了，它不可重入、不处理中断、非公平锁，而真正被广泛使用的，是 ReentrantLock。\nReentrantLock 和 Mutex 的骨架完全一样，外部类实现 Lock 接口，内部定义一个 Sync 继承 AQS，所有方法委托给 sync 。区别在于 Sync 内部又分了两个子类分别实现非公平锁和公平锁。整体架构如图，剩下没展示的方法基本都是调用 Sync 里的方法。\n说实话，我觉得前两篇的内容我都已掌握，但当我实际接触 ReentrantLock 时，我还是迷了，各种函数来回调用，lock()、tryLock()、initialTryLock()、tryAcquire()、acquire() 等等，全部混乱，如果你也这样，继续看下去包能让你满意。\n整体架构 public class ReentrantLock implements Lock, java.io.Serializable { private final Sync sync; abstract static class Sync extends AbstractQueuedSynchronizer {...} static final class NonfairSync extends Sync {...} static final class FairSync extends Sync {...} ... } 从上方图也能看出来，ReentrantLock 内部定义了一个 Sync，AQS的一个实现，不同的是又有两个子类 NonfairSync、FairSync 去继承 Sync 作为公平锁和非公平锁的实现。\n查看 ReentrantLock 一些其他的方法可以发现基本都是 sync.xxx()，所以这些方法只是暴露给调用者的 api，真正的核心还是 Sync 里的方法。接下来的重点也还是 Sync 里各种方法的实现。\nlock() 调用链 ReentrantLock.lock() └── sync.lock() // Sync 内部类 └── initialTryLock() // 快速尝试（NonfairSync / FairSync 实现） ├── 成功：直接返回 └── 失败：acquire(1) // AQS 的模板方法 └── tryAcquire(1) // AQS 的钩子（NonfairSync / FairSync 实现） ├── 成功：返回 └── 失败：入队、阻塞（AQS 底层，本篇不展开） 可以在 idea 上进行一个简单的调试，就拿之前写过的生产者消费者那个代码来说，到 initialTryLock() 方法就直接返回 true，后续调用就不会进行。\nSync 内部方法 initialTryLock() /* 这是 jdk21 的新增方法，是个抽象方法，由公平锁和非公平锁各自实现， 主要作用就是减少进入 AQS 队列的开销，能直接抢到就直接抢。后面细说。 */ abstract boolean initialTryLock(); lock() /* 也和之前的调用链对应上了，先调 initialtryLock()， 失败了抛给 AQS 的 acquire(1)。 */ final void lock() { if (!initialTryLock()) acquire(1); } tryLock() /* 这个方法专门给外层 tryLock() API 用，不走 AQS 排队机制。 */ final boolean tryLock() { Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { //直接 CAS 0-\u0026gt;1，成功就返回 true，重入就 state++ if (compareAndSetState(0, 1)) { setExclusiveOwnerThread(current); return true; } //如果当前线程就是独占锁的线程，说明重入。 } else if (getExclusiveOwnerThread() == current) { if (++c \u0026lt; 0) // overflow throw new Error(\u0026#34;Maximum lock count exceeded\u0026#34;); setState(c); return true; } return false; } tryRelease(int) /* Sync 重写 AQS 的钩子， 由 sync.release(1) 调用，release 内部会回调 tryRelease */ protected final boolean tryRelease(int releases) { int c = getState() - releases; //先检查当前线程是不是持有锁的线程 if (getExclusiveOwnerThread() != Thread.currentThread()) throw new IllegalMonitorStateException(); //state 减到0才算真正释放 boolean free = (c == 0); if (free) setExclusiveOwnerThread(null); setState(c); return free; } lockInterruptibly() 和 tryLockNanos() /* 可 中断获取 锁的底层实现 */ final void lockInterruptibly() throws InterruptedException { //如果被中断，直接抛异常 if (Thread.interrupted()) throw new InterruptedException(); //快速尝试获取锁（不排队不阻塞） if (!initialTryLock()) //快速路径失败，进入 AQS 的完整排队 + 阻塞流程 acquireInterruptibly(1); } /* 限时获取锁的底层实现 */ final boolean tryLockNanos(long nanos) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); //快速尝试，失败则进入 AQS 的 //限时排队流程（最多等nanos 纳秒，超时返回false，线程继续执行后续代码） return initialTryLock() || tryAcquireNanos(1, nanos); } 剩下的 isHeldExclusively()、getOwner() 等用来给外面查状态，用来给外面查状态。 NonfairSync 和 FairSync NonfairSync 核心目标：\n允许插队：新来线程不管队列里有没有人，直接抢锁。 支持重入：同一线程可以多次获取锁。 先走快速路径：进入 AQS 排队之前，尽可能先拿到锁，减少上下文切换。 重写两个方法：\ninitialTryLock()：快速路径，在 lock()、lockInterruptibly()、tryLock() 等入口被调用。 tryAcquire(int acquires)：AQS 模板方法中的钩子，在 acquire(1) 内部被调用 static final class NonfairSync extends Sync { /* initialTryLock() 是一个“快速路径”， 它把无竞争获取和重入这两种最常见、最廉价的情况一次性处理掉， 避免进入 AQS 的队列和阻塞逻辑。 只有在锁确实被其他线程占用时，才走重量级流程。 */ final boolean initialTryLock() { Thread current = Thread.currentThread(); //插队行为 if (compareAndSetState(0, 1)) { // first attempt is unguarded setExclusiveOwnerThread(current); return true; } else if (getExclusiveOwnerThread() == current) { int c = getState() + 1; if (c \u0026lt; 0) // overflow throw new Error(\u0026#34;Maximum lock count exceeded\u0026#34;); setState(c); return true; } else return false; } /** * Acquire for non-reentrant cases after initialTryLock prescreen */ //上边的原始注释也说了，在 initialTryLock 预筛选后，用于处理非重入情况。 //acquire(1) 会调用 tryAcquire(1)， //此时当前线程一定不是锁的持有者（如果是就走 initialTryLock() 了） protected final boolean tryAcquire(int acquires) { if (getState() == 0 \u0026amp;\u0026amp; compareAndSetState(0, acquires)) { setExclusiveOwnerThread(Thread.currentThread()); return true; } //不处理重入，因为在上面函数处理过了 //不检查等待队列，因为非公平锁允许插队。 return false; } } 为什么需要 tryAcquire ？因为存在一种情况：（先回头看眼 lock()）\n线程调用 initialTryLock() 时，锁被其他线程持有，返回 false。 在进入 acquire(1) 之前，锁被释放了，state 变为 0。 此时 tryAcquire(1) 就有机会再次 CAS 抢锁，成功则直接获取，无需入队。 FairSync 是公平锁的实现，在构造 ReentrantLock 时传入 true 即可创建公平锁。核心目标为：\n严格 FIFO：新来的线程如果发现等待队列里有前辈排队，就必须乖乖去队尾，不允许插队。 支持重入：同一个线程可以多次获取锁。 牺牲吞吐量换公平性：避免“线程饥饿”，但增加了唤醒开销。 重写的两个方法整体和 NonfairSync 类似，多了两个方法 hasQueuedThreads() 和 hasQueuedPredecessors()，前者是检查队列里有没有任何线程在排队，后者是检查队列里有没有比当前线程等得更久的前辈。\nstatic final class FairSync extends Sync { /** * Acquires only if reentrant or queue is empty. */ final boolean initialTryLock() { Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (!hasQueuedThreads() \u0026amp;\u0026amp; compareAndSetState(0, 1)) { setExclusiveOwnerThread(current); return true; } } else if (getExclusiveOwnerThread() == current) { if (++c \u0026lt; 0) // overflow throw new Error(\u0026#34;Maximum lock count exceeded\u0026#34;); setState(c); return true; } return false; } /** * Acquires only if thread is first waiter or empty */ //上面说 仅当当前线程是队首或者队列为空时才会获取。 //hasQueuedThreads() = 队列里有没有人？ //hasQueuedPredecessors() = 我前面有没有人？ //后者考虑到到了当前线程是队首的情况 protected final boolean tryAcquire(int acquires) { if (getState() == 0 \u0026amp;\u0026amp; !hasQueuedPredecessors() \u0026amp;\u0026amp; compareAndSetState(0, acquires)) { setExclusiveOwnerThread(Thread.currentThread()); return true; } return false; } } 至此，ReentrantLock 基本讲完了，至于 AQS 内部的排队唤醒机制，大概就是“自旋 -\u0026gt; 挂起 -\u0026gt; 醒来 -\u0026gt; 再自旋”这样一个循环。总结一下 ReentrantLock 的两种实现。\n对比项 NonfairSync FairSync initialTryLock() 锁空闲时 直接 CAS 抢 先查 hasQueuedThreads()，队列非空就不抢 tryAcquire() 锁空闲时 直接 CAS 抢 先查 hasQueuedPredecessors() ，有前辈就不抢 重入 支持 支持 插队 支持 不支持 吞吐量 高 低（多了检查 + 更多线程挂起/唤醒） 线程饥饿 会发生 不会发生 使用场景 多数场景 对公平性有严格要求的场景 Semaphore state 表示当前可用的许可数量，acquire() 减1，release() 加1，减到0时后来的线程排队等待。共享模式。\n常用方法：\n方法 作用 acquire() 申请1个许可证，阻塞直到成功 acquire(int n) 申请n个许可证 tryAcquire() 尝试申请，成功返回 true，失败立即返回 false tryAcquire(long timeout, TimeUnit unit) 限时尝试申请 realease() 归还一个许可证 availablePermits() 查看当前剩余许可证数量 使用场景：限流——控制同时访问某个资源的线程数量。比如模拟只有3个停车位的停车场。\n下面是一个示例。输出是会错位的，因为获取许可和打印、释放许可和打印，这些步骤不是原子的。比如线程 A 释放了信号量，线程 B 立刻拿到，此时打印的剩余许可证数量还是不变。\nclass SemaphoreExample { Semaphore parking = new Semaphore(3); public void park() { for (int i = 0; i \u0026lt; 10; i++) { int carId = i; new Thread(() -\u0026gt; { try { parking.acquire(); System.out.println(\u0026#34;车辆：\u0026#34; + carId + \u0026#34;停入，剩余车位：\u0026#34; + parking.availablePermits()); Thread.sleep(2000); } catch (InterruptedException e) { throw new RuntimeException(e); } finally { parking.release(); System.out.println(\u0026#34;车辆：\u0026#34; + carId + \u0026#34;离开，剩余车位：\u0026#34; + parking.availablePermits()); } }).start(); } } } CountDownLatch 这个算是比较简单的一个。state 表示还需要倒数的计数，countDown() 减 1，减到 0 时所有 await() 的线程被唤醒。共享模式，一次性。如果需要重置 state，可以考虑用 CyclicBarrier。\n使用场景：主线程等 n 个子线程初始化完毕再开始主流程。比如开一局 PUBG，需要等地图、玩家等资源准备好了才会开。\nclass CountDownLatchExample { String[] resources = new String[]{\u0026#34;map\u0026#34;, \u0026#34;role\u0026#34;}; CountDownLatch pubg = new CountDownLatch(resources.length); public void load() throws InterruptedException { for (String resource : resources) { new Thread(() -\u0026gt; { try { Thread.sleep(2000); System.out.println(resource + \u0026#34;加载完成\u0026#34;); } catch (InterruptedException e) { throw new RuntimeException(e); } finally { pubg.countDown(); } }).start(); } System.out.println(\u0026#34;等待资源加载中\u0026#34;); pubg.await(); System.out.println(\u0026#34;资源加载完成，上飞机。\u0026#34;); } } ReentrantReadWriteLock 这个复杂到爆。可以通过 readLock() 和 writeLock() 拿到两把锁，然后各自调用 lock() 和 unlock()。读锁是共享模式，写锁是独占模式，因为实现了 Lock 接口，所以使用和 ReentrantLock 一样。读锁的共享体现在 读 和 读 之间，互斥体现在 读 和 写 之间。它的 state 被拆成高16位和低16位，高16位记录读锁计数，有多少个线程在读，低16位记录写锁的重入次数。\n使用场景：读多写少的缓存。多个线程可以同时读缓存，但写缓存必须独占。\n阻塞队列 BlockingQueue 是 Queue 的一个子接口，相比 Queue ，它提供了阻塞操作，同时所有实现都保证线程安全。\n简单介绍 下面是方法对照表，后两列是 BlockingQueue 独有的。\n操作 抛异常 返回特殊值 一直阻塞 超时阻塞 插入 add(e) offer(e) -\u0026gt; false put(e) offer(e, time, unit) 移除 remove() poll() -\u0026gt; null take() poll(time, unit) 检查 element() peek() -\u0026gt; null 无 无 阻塞的意思就是，拿 put() 来说，队列如果满时就会一直阻塞，知道有空位才会把元素放进去，offer() 的是意思这一段时间内有空位就放进去，如果没有空位返回 false，如果是 poll() 超时就返回 null。\nnull 也引出了一条规则，在 BlockingQueue 接口的所有实现里，都不允许放入 null 元素，因为 poll() 用 null 表示拿不到，如果允许存 null 二者就会产生歧义。LinkedList 作为 Queue 的接口实际上是可以插入 null 的，注意是允许，不代表这个操作正确，它可以插入 null 是为了兼容早期代码，当时 LinkedList 是作为 List 实现的，而 Queue 接口是后面随 JUC 引入的，那时 LinkedList 被追加实现了 Queue/Deque，所以它只是个意外。 其实 JUC 里的很多实现类都不允许 null 元素，要么破坏类的原有的实现语义，要么拿 null 有特殊用处。\n用阻塞队列重写生产者消费者 public class BlockingQueueProducerConsumer { private static final BlockingQueue\u0026lt;Integer\u0026gt; queue = new ArrayBlockingQueue\u0026lt;\u0026gt;(5); private static final Random rand = new Random(); public static void main(String[] args) { Thread producer = new Thread(() -\u0026gt; { while (true) { try { int value = rand.nextInt(100); queue.put(value); System.out.println(\u0026#34;produce:\u0026#34; + value + \u0026#34;, queue.size = \u0026#34; + queue.size()); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } }); Thread consumer = new Thread(() -\u0026gt; { while (true) { try { Integer value = queue.take(); System.out.println(\u0026#34;consume:\u0026#34; + value + \u0026#34;, queue.size = \u0026#34; + queue.size()); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } }); producer.start(); consumer.start(); } } 可以看到不需要再定义 lock、condition，不用再判断队列是否满，因为这些逻辑已经在 BlockingQueue 里封装好了，其实看一下 put() 和 take() 方法的源码就会发现和以前用 ReentrantLock 和 Condition 实现的那一套生产者消费者很像。\n我觉得有一点细节值得一说：当捕获到异常时应该 Thread.currentThread().interrupt(); 而不是继续向上抛RuntimeException，interrupt() 的意义在于恢复中断标志，如果像之前那样抛 RuntimeException 会让线程直接死掉，另一个线程永久阻塞。\n中断机制 详细讲一下中断机制吧，我在这里迷了好久。\n首先要知道：\n中断不是强制杀线程，而是协作式通知。\n一个线程是否中断是从 Thread 类里的 interrupted 的布尔值来判断的，true 中断。Thread.currentThread().isInterrupted() 这个方法用来返回线程的中断状态。\nt.interrupt() 用来中断线程 t，它会把中断标志设为 true，同时如果当前线程正在阻塞，也会通知 JVM 去唤醒。\n当线程执行 Thread.sleep()、Object.wait()、Thread.join() 这些方法被中断时，除了抛出异常，必须清除中断状态。也就是说：凡是声明了 throws InterruptedException 的可中断阻塞方法，因中断而抛出异常时，必须清除中断状态（清除中断状态的是 sleep() 这些方法，而不是 interrupt()）。\n所以时间线是\n别人调用 t.interrupt() ↓ t 的中断标志变成 true ↓ t 正卡在 sleep() ↓ sleep() 被唤醒，发现中断标志是 true ↓ sleep() 清掉中断标志，设为 false ↓ sleep() 抛出 InterruptedException 所以再回看上面这段文字：\n当捕获到异常时应该 Thread.currentThread().interrupt(); 而不是继续向上抛RuntimeException，interrupt() 的意义在于恢复中断标志\n因为异常抛出时，中断标志被清除掉了，如果不能把异常继续往上抛，又不恢复标志，上层就不知道这个线程被中断过，即“吞掉中断”。在生产者消费者的例子里并没有任何地方调用 interrupt()，但在实际项目中，很多地方都会显示或隐式调用，比如 ExecutorService.shutdownNow()。\n但不要搞混 break 和 Thread.currentThread().interrupt(); 的作用，前者是结束 while 循环，从而结束 run 方法，后者只是设置标志，保存中断状态。（其实在这个例子中不要 break，循环条件换为 Thread.currentThread().interrupt() 也是可以的）。小提醒，不要搞混 isInterrupted() 和 interrupted()。\n总结一下，推荐这样写：\npublic void run() { while (!Thread.currentThread().isInterrupted()) { try { // 干活 Thread.sleep(1000); } catch (InterruptedException e) { // 不能继续抛 InterruptedException 时，恢复中断状态 Thread.currentThread().interrupt(); // 停止当前循环/任务 break; } } // 清理资源 } 刷抖音看见一个同学分享的美团面经，面试官让手撕一道题，内容如下\n我的答案是这样的，问题就是时间点无法精确打印。\nclass MeiTuanTest { public void print1() { BlockingQueue\u0026lt;Integer\u0026gt; queue = new ArrayBlockingQueue\u0026lt;\u0026gt;(10); AtomicInteger i = new AtomicInteger(1); long start = System.currentTimeMillis(); Thread p = new Thread(() -\u0026gt; { while (!Thread.currentThread().isInterrupted()) { try { Thread.sleep(100); System.out.println(\u0026#34;-------- \u0026#34; + (System.currentTimeMillis() - start) + \u0026#34;ms ----------\u0026#34;); int value = i.getAndIncrement(); queue.put(value); System.out.println(\u0026#34;p: \u0026#34; + value); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } }); Thread c = new Thread(() -\u0026gt; { while (!Thread.currentThread().isInterrupted()) { try { Thread.sleep(300); List\u0026lt;Integer\u0026gt; list = new ArrayList\u0026lt;\u0026gt;(); queue.drainTo(list); System.out.println(\u0026#34;c: \u0026#34; + list.toString()); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } }); p.start(); c.start(); } } 常见实现 实现 数据结构 是否有界 特点 典型场景 ArrayBlockingQueue 数组 有界 一把锁两个Condition 固定容量缓冲 LinkedBlockingQueue 链表 默认int最大值，可选有界 两把锁，吞吐量更高 线程池任务队列 SynchronousQueue 不存储元素 无容量 每个 put 必须等待 take，反之亦然 newCachedThreadPool PriorityBlockingQueue 堆 无界 按优先级出队 优先级任务 DelayQueue 堆+延迟 无界 到期才能take 定时任务 简单讲一下后四个，涉及到线程池的后面会具体讲。\nLinkedBlockingQueue 底层是链表，它入队和出队用的是两把锁 putLock 和 takeLock，notFull 和 notEmpty 两个条件也是分别绑在两个锁上。所以生产者可以和消费者同时进行，这是它吞吐量更高的原因。但它的默认容量是 Integer.MAX_VALUE ，几乎等于无界，当生产速度远大于消费速度时，任务会无限堆积，最终 OOM，使用时建议指定容量。线程池 Executors.newFixedThreadPool() 用的就是它。\nSynchronousQueue 它不存储元素。可以看一下这个类的 size()、isEmpty() 等方法。它的语义不是“把元素放进队列”，而是“把元素直接交给另一个线程”。所以：\nput(e) 会阻塞，直到有另一个线程调用 take() 。 take(e) 会阻塞，直到有另一个线程调用 put(e)。 它一手交钱，一手交货，没有中间商赚差价。它的内部也有公平和非公平两种模式，默认非公平。Executors.newCachedThreadPool() 用的就是它。\nPriorityBlockingQueue 是一个有优先级的无界阻塞队列，只有一个 notEmpty 一个条件，所以可以无限 offer 直至 OOM，因为有优先级，所以元素必须可比较。\nDelayQueue 也是一个无界阻塞队列。元素必须实现 Delayed 接口，它的语义是只有到期元素 才可能被 take() 。内部用到了 PriorityQueue ，按到期时间排序，队首永远是最早到期的元素。\n常见的实现讲完了，接下来就是线程池。看完默认线程池对工作队列的选择，或许会清楚为什么说一定要自定义线程池。\n线程池 ThreadPoolExecutor JUC-2 里讲了四种常见的创建线程的方式，如果使用前三种，会有很多缺点。\n手动创建线程的缺点 创建和销毁线程需要分配和收回资源，如果每个任务都需要新建一个线程，那开销会很大。 线程数量不可控，来一个任务 new 一个 thread，并发请求一多会 OOM，同时上下文切换的开销也会很大。 缺乏管理和监控，无法统一管理各个线程的生命周期。 线程池弥补了这些缺点。它会预先创建一批线程，用这些线程反复在任务队列里取任务执行，用完不销毁，放进池子里等待下一批任务。这是一种思想，数据库连接池、对象池都是同样的想法。\n线程池相关接口和类 Executor 接口：最顶层接口，只有一个方法 execute(Runnable command) 。\nExecutorService 接口：继承 Executor 接口，增加了 submit、shutdown 等相关的生命周期管理方法。\nAbstractExecutorService 抽象类：把 submit() 等方法的通用逻辑抽取出来。方便实现类复用。这是《Effective Java》里推荐的“接口 + 骨架实现类”的组合，也叫模拟多重继承。\nThreadPoolExecutor 类：真正的线程池实现。\nExecutors 类：提供多种线程池实现和线程工厂的实现。\n我和 ai 交流，它说了下面这样一段话：\nExecutor 这个接口的设计哲学很有意思：把“任务的提交”和“任务的执行”解耦。提交任务的人不需要知道任务是被哪个线程执行的、是新建线程还是复用线程、是立即执行还是排队。这是线程池所有灵活性的来源。\n我尝试理解了一下，比如下面这个例子，调用这个方法时，你可以传入不同的 Executor 实现，但都不影响“任务的提交”。\npublic void doWork(Executor executor) { executor.execute(() -\u0026gt; System.out.println(\u0026#34;任务执行\u0026#34;)); } 大概是这个意思，但说实话我始终有点迷惑，也许一年后再回看会清楚很多。\n七大参数 这简直是面试超超高频考点，七个参数分别为：核心线程数、最大线程数、空闲线程存活时间、时间单位、工作队列、线程工厂、拒绝策略。\npublic ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue\u0026lt;Runnable\u0026gt; workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {...} 在介绍参数前先吟诵一下八股：任务的执行流程：\n提交任务时，如果当前线程数小于核心线程数，那么创建新线程执行任务。 如果当前线程数大于核心线程数，那么将任务加入工作队列。 如果工作队列已满，且当前线程数小于最大线程数，创建新线程来执行任务。 如果工作队列已满且当前线程数等于最大线程数，执行拒绝策略。 当前线程数大于核心线程数时，超过空闲线程存活时间的空闲线程将被回收。 核心线程数和最大线程数 核心线程数是线程池长期维持的线程数量，默认即使空闲也不会被销毁。最大线程数是线程池能创建线程的上限，多出来的线程是非核心线程。实际上二者只是逻辑上的区分。一般来说，推荐把二者设为相同值，避免线程池在运行时频繁创建和销毁线程，减少开销。\n空闲线程存活时间和时间单位 非核心线程空闲超过该时间则会被销毁，如果设置了 allowCoreThreadTimeOut(true)，核心线程也会受到约束。时间单位一般用秒或毫秒。\n工作队列 存放等待执行任务的阻塞队列，就是上面讲的 BlockingQueue。\n线程工厂 用于创建线程，默认实现是 Executors.defaultThreadFactory(),创建的线程都在同一个线程组，优先级相同，是非守护线程。一般会自定义，给线程起带有业务含义的名字，方便排查问题。\n拒绝策略 线程数达到最大值，队列也满，此时会按照定义的拒绝策略来拒绝新提交的任务。有四种内置实现：\n策略 行为 适用场景 AbortPolicy 直接抛 RejectedExecutionException 默认策略，需要感知拒绝 CallerRunsPolicy 由提交任务的线程自己执行 不希望丢任务，且能接受提交线程被阻塞 DiscardPolicy 静默丢弃，不抛异常 允许丢任务，但很难排查问题 DiscardOldestPolicy 丢弃队列中最老的任务，再提交当前任务 允许丢弃老任务，保留新任务 注意事项 线程池不允许用 Executors 去创建，因为里面提供的线程池都存在弊端，原因在参数上，如下表，要么队列无界，要么线程数无界，都会导致OOM。\n工厂方法 核心线程 最大线程 队列 newFixedThreadPool n n LinkedBlockingQueue（无界） newSingleThreadExecutor 1 1 LinkedBlockingQueue（无界） newCachedThreadPool 0 Integer.MAX_VALUE SynchronousQueue newScheduledThreadPool n Integer.MAX_VALUE DelayedWorkQueue所 一般来说，会把核心线程数和最大线程数设为相同的值，避免开销，然后配置有界队列。\n我想讲一下缓存线程池，为什么称它为缓存，这取决于用的队列。关于 SynchronousQueue ，上面讲过，它的特点是不存储元素，把元素交给另一个线程。相当于工作队列长度为 0。按照任务提交流程走，因为核心线程数为 0，所以会直接尝试把任务进队，如果没有空闲线程在等待（也就是没有线程去take），那就入队失败，而当前线程数肯定是小于最大线程数的（无限大），所以会创建新线程来执行任务。新线程执行完任务后会循环从队列里去任务，如果 60 秒内有新任务提交，那就一手交钱一手交货，如果没有，线程被回收。所以，如果同时来多个任务，且没有空闲线程，每个任务都会导致创建一个新线程，线程数是弹性增长的。\n优雅关闭。线程池用完要关闭，否则 JVM 不会退出（非守护线程还在跑）\nexecutor.shutdown(); // 不接受新任务，处理完队列里的任务后关闭 executor.shutdownNow(); // 不接受新任务，中断正在执行的任务，返回未执行的任务列表 监控。可以通过 getPoolSize() 、getActiveCount() 等方法对线程池状态进行监控。\nForkJoinPool 简单讲一下，有一类任务，本身很庞大，但是可以被递归拆成很多小任务，每个小任务算完还要把结果合并，类似归并排序。如果把这种任务交给 ThreadPoolExecutor ，只有一个线程在猛猛干，其他线程闲着，ForkJoinPool 就是为了解决这样一个问题。\nForkJoinPool 继承自 AbstractExecutorService ，和 ThreadPoolExecutor 是同级别线程池实现。核心思想是分治：fork，拆；join，合。\n这两个线程池最大的区别在于队列设计：\nThreadPoolExecutor：全局一个队列，所有线程抢同一个队列。 ForkJoinPool：每个线程一个双端队列，自己从头部取任务，空闲时去别人队列尾部（任务粒度达，偷过来可以再拆）“偷”。 所以，ForkJoinPool 适合任务之间有依赖、需要拆分的场景；ThreadPoolExecutor 适合任务相互独立的场景。\n虚拟线程 前面的的线程池解决的问题是：创建销毁线程开销达，所以池化来复用。虚拟线程的解决思路是，让 创建和销毁线程的开销变得极小。\n虚拟线程是 JDK21 正式引入的轻量级线程，由 JVM 管理，不绑定操作系统的线程。\n具体不再细说，从 Thread、Executors 等各种类里也可以看到虚拟线程的踪迹。\n总结 到这里 JUC 基本上结束了，但这只是冰山一角，庞大的并发包可以用一整本书来讲。这三篇内容我也并不是百分百理解，希望以后常看常新。\n","permalink":"https://blog.withdong02.top/posts/juc-3/","summary":"\u003cp\u003e上一篇讲了线程的状态和切换方法，还讲了 AQS 的关键概念。这一篇我打算学习 AQS 的常见实现、并发容器，还有阻塞队列和线程池。\u003c/p\u003e\n\u003ch2 id=\"aqs-的常见实现\"\u003eAQS 的常见实现\u003c/h2\u003e\n\u003cp\u003e上一篇提到 AQS 核心内容就是：state + 等待队列。接下来我会讲解 \u003ccode\u003eReentrantLock\u003c/code\u003e、\u003ccode\u003eSemaphore\u003c/code\u003e、\u003ccode\u003eCountDownLatch\u003c/code\u003e 等常见实现。\u003c/p\u003e","title":"JUC-3"},{"content":"上一篇的概念是真多，这一篇应该代码多，大头是 AQS。\nLock 接口 上一篇结尾讲到了 synchronized，但它由于设计上的原因，有几个无法满足灵活需求的特性：\n不可中断：获取锁的线程可以无限阻塞。 默认非公平：哪个线程能抢到锁看运气。 无超时机制：无法设置获取锁的最大等待时间。 显然不够灵活，为此，JUC 在 java.util.concurrent.locks 包下提供了 Lock 接口，在保留互斥语义的同时，扩展了更多可控能力。\npublic interface Lock { //获取锁，调用后一直阻塞，期间不响应中断 void lock(); //获取锁支持响应中断，被中断抛异常 void lockInterruptibly() throws InterruptedException; //尝试获取锁，成功返回true，失败返回false boolean tryLock(); //指定时间内尝试获取锁，超时未获取到锁返回false boolean tryLock(long time, TimeUnit unit) throws InterruptedException; //解锁 void unlock(); //创建一个绑定到当前 Lock 的 Condition，用于更精细的等待/唤醒 Condition newCondition(); } 线程间的协作 常见的线程创建形式： 继承 Thread 类，重写 run() 方法。 实现 Runnable 接口，重写 run() 方法。 实现 Callable 接口，重写 call() 方法 使用线程池。 public class createThread { public static void main(String[] args) throws ExecutionException, InterruptedException { T1 thread1 = new T1(); thread1.start(); T2 t2 = new T2(); Thread thread2 = new Thread(t2); thread2.start(); T3 t3 = new T3(); FutureTask\u0026lt;String\u0026gt; stringFutureTask = new FutureTask\u0026lt;\u0026gt;(t3); Thread thread3 = new Thread(stringFutureTask); thread3.start(); System.out.println(stringFutureTask.get()); } } class T1 extends Thread { @Override public void run() { System.out.println(\u0026#34;我通过继承Thread创建线程。\u0026#34; + Thread.currentThread().getName()); } } class T2 implements Runnable { @Override public void run() { System.out.println(\u0026#34;我通过实现Runnable创建线程。\u0026#34; + Thread.currentThread().getName()); } } class T3 implements Callable\u0026lt;String\u0026gt; { @Override public String call() throws Exception { System.out.println(\u0026#34;我通过实现Callable创建线程。\u0026#34; + Thread.currentThread().getName()); return \u0026#34;我是call返回结果\u0026#34;; } } 线程的6种状态 状态 说明 NEW 线程被创建但未启动 RUNNABLE 线程处于就绪（等待 CPU 时间片）或运行状态 BLOCKED 线程被阻塞，等待获取锁 WAITING 线程等待其他线程的通知或中断 TIMED_WAITING 超时等待，时间结束返回运行状态 TERMINATED 线程执行结束，生命周期终止 线程多种状态之间的切换如下图。\n其中 wait()、notify()、notifyAll() 是顶层父类的方法。实现原理决定着他们必须配合 synchronized 使用。\n当线程调用共享对象的 wait 方法时，会进入该对象的等待池（WaitSet），释放已经持有的锁，进入等待状态；当调用 notify 方法时，会唤醒等待池中的一个线程，使其进入锁池（EntryList），等待获取锁，注意：被唤醒不代表立刻执行，而是要排队抢锁。\n有类似机制的还有 Condition 接口中的 await()、signal()、signalAll()，这些方法调用前必须持有对应的 Lock.lock()。\nwait() 和 sleep() 的区别 这两个方法都可以让线程进入等待状态，但略有不同。\n所属类不同：前者 Object，后者 Thread。 锁行为不同：调用 wait() 会释放已持有的锁，而 sleep() 不会。 使用条件不同：wait() 调用前提是持有对象的锁（否则抛 IllegalMonitorStateException），而 sleep() 可以在任何地方调用。 唤醒方式不同：sleep() 时间结束后，线程会从 TIMED_WAITING 自动恢复到 RUNNABLE，等待 CPU 时间片；调用 wait() 后要么等待 notify()（无参），要么时间结束自动恢复 RUNNABLE（有参）。 经典实践：生产者消费者 Java · 53 行 · 点击展开 public class ProducerConsumerExample { private static Object lock = new Object(); private static int MAX = 10; private static Deque\u0026lt;Integer\u0026gt; queue = new ArrayDeque\u0026lt;\u0026gt;(); private static Random rand = new Random(); private static void produce() throws InterruptedException { synchronized (lock) { while (queue.size() == MAX) { System.out.println(\u0026#34;it is full\u0026#34;); lock.wait(); } int value = rand.nextInt(100); queue.offer(value); System.out.println(\u0026#34;produce:\u0026#34; + value + \u0026#34;, queue.size = \u0026#34; + queue.size()); lock.notifyAll(); //lock.wait(); } } private static void consume() throws InterruptedException { synchronized (lock) { while (queue.isEmpty()) { System.out.println(\u0026#34;it is empty\u0026#34;); lock.wait(); } Integer value = queue.poll(); System.out.println(\u0026#34;consume:\u0026#34; + value + \u0026#34;, queue.size = \u0026#34; + queue.size()); lock.notifyAll(); //lock.wait(); } } public static void main(String[] args) { Thread t1 = new Thread(() -\u0026gt; { while (true) { try { produce(); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); Thread t2 = new Thread(() -\u0026gt; { while (true){ try { consume(); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); t1.start(); t2.start(); } } 当前代码输出类似生产十个消费十个的轮回。\n假设初始队列为空，生产者先获得锁，生产一个，然后调用 notifyAll() 把消费者线程从等待池唤出到锁池。等生产者 synchronized 块结束，两个线程重新争抢锁，因为 CPU 调度策略的关系，总是让生产者继续占有锁（这是我从\u0026quot;连续生产 10 个才让出\u0026quot;这个现象推断出来的，并不绝对）。当生产满十个后，生产者释放锁并进入等待池，消费者线程才迎来机会进行消费。如果把注释去掉，则可以实现\u0026quot;生产一个、消费一个\u0026quot;的效果——因为消费者线程被唤醒进入锁池后，发现生产者没有和它竞争，便直接占有锁开始消费。\n除了用 wait/notify 实现，还可以通过前文讲到的 Condition 搭配 await 和 signal，通过创建两个 Condition 来实现精确唤醒。\nJava · 73 行 · 点击展开 package org.example.myblog.JUC; import java.util.ArrayDeque; import java.util.Deque; import java.util.Random; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ProducerConsumerExample { private static int MAX = 10; private static Deque\u0026lt;Integer\u0026gt; queue = new ArrayDeque\u0026lt;\u0026gt;(); private static Random rand = new Random(); private static Lock lockPlus = new ReentrantLock(true); private static Condition notFull = lockPlus.newCondition(); private static Condition notEmpty = lockPlus.newCondition(); private static void producePlus() throws InterruptedException { /* * await 的本意是等一个能让其继续干活的条件，等一个率绿灯信号，而生产者的绿灯就是“队列不满”。 * await 等待的永远是你“缺乏”的东西，而不是“拥有”的东西。 * 一般命名的标准范式就是“not + 负面状态” * */ lockPlus.lock(); try { while (queue.size() == MAX) { System.out.println(\u0026#34;it is full\u0026#34;); notFull.await();//如果队列满了，就等待“不满”这个条件发生。 } int value = rand.nextInt(100); queue.offer(value); System.out.println(\u0026#34;produce:\u0026#34; + value + \u0026#34;, queue.size = \u0026#34; + queue.size()); notEmpty.signal(); } finally { lockPlus.unlock(); } } private static void consumePlus() throws InterruptedException { lockPlus.lock(); try { while (queue.isEmpty()) { System.out.println(\u0026#34;it is empty\u0026#34;); notEmpty.await();//如果队列空了，就等待“不空”这个条件发生。 } Integer value = queue.poll(); System.out.println(\u0026#34;consume:\u0026#34; + value + \u0026#34;, queue.size = \u0026#34; + queue.size()); notFull.signal(); } finally { lockPlus.unlock(); } } public static void main(String[] args) { Thread t1 = new Thread(() -\u0026gt; { while (true) { try { producePlus(); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); Thread t2 = new Thread(() -\u0026gt; { while (true){ try { consumePlus(); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); t1.start(); t2.start(); } } 提一嘴：前面提到线程有 BLOCKED 状态，只有用 synchronized 抢不到锁时，线程才会进入 BLOCKED 状态（notify 调醒后，抢到锁前也是 BLOCKED 状态），如果 lock.lock() 抢不到锁则进入 WAITING 状态，因为它通过在 AQS 中定义的内部队列来阻塞和唤醒。\n总的来说 wait/notify 简单轻量，但唤醒粒度粗，Condition 一个 Lock 可以绑定多个条件，唤醒更准确。Lock 的底层，正是 JUC 的灵魂——AQS。\nAQS 在学习 AQS 之前，简单介绍一下 Doug Lea。Doug Lea 是纽约州立大学奥斯威戈分校的计算机科学教授。他对 Java 最大的贡献，是主持制定了 JSR-166 规范，这个规范的核心成果，就是在 JDK5.0 中引入了 java.util.concurrent 包，AQS 就是这个包的核心。Doug Lea 不仅为 Java 提供了并发工具，更奠定了一种严谨、优雅解决并发问题的范式，学习 AQS ，会有一种与大师对话的感觉😍。\nAQS 全称为 AbstractQueuedSynchronizer，是整个 JUC 包的基石，ReentrantLock、CountDownLatch、Semaphore 等锁框架或者同步工具类都是基于 AQS 这个抽象类实现的。\n下面我将按照这个类源码上方的注释来讲解 AQS：\nAQS 是什么 Provides a framework for implementing blocking locks and related synchronizers (semaphores, events, etc) that rely on first-in-first-out (FIFO) wait queues. This class is designed to be a useful basis for most kinds of synchronizers that rely on a single atomic int value to represent state.\nAQS 提供了一个框架用来实现依赖 FIFO 等待队列的阻塞锁和同步器（如信号量、事件等），它是一个类，绝大多数基于“一个原子 int 值表示状态”的同步器都可以用 AQS 来实现。\n核心设计：state(volatile) + queue AQS 把状态管理和排队机制进行了职责分离。\nAQS 负责线程排队、阻塞、唤醒等底层机制（final 方法，子类不能改）； 子类负责定义 state 的含义，以及如何获取/释放状态（protected 方法，子类必须重写）。 子类需要重写的方法有五个\n方法 模式 作用 tryAcquire(int arg) 独占 尝试获取资源，成功返回 true tryRelease(int arg) 独占 尝试释放资源，成功返回 true tryAcquireShared(int arg) 共享 尝试获取资源，负数表示失败，0 表示无剩余资源，正数表示成功且有剩余 tryReleaseShared(int arg) 共享 尝试释放资源，成功返回 true isHeldExclusively() 独占 判断当前线程是否独占持有资源 AQS 就像地铁站的闸机系统，把人拦住（阻塞），把下一个人叫进来（唤醒）都是它的活；子类重写的方法就像验票规则，告诉闸机什么情况下验票通过。注意：AQS 是完全相信方法返回的布尔值的，如果在 tryAcquire 中返回了 true，但忘记把 state 由0改为1，AQS 仍认为当前线程获取了锁，当下一个线程进来，tryAcquire 仍然返回 true，仍然会放行，这样两个线程同时获取了资源，锁失效了。AQS 是信任你写的这五个方法的，不要让它失望。\n这个设计就是模板方法模式。\n独占与共享 模式 特点 代表工具 独占模式 同一时刻只能有一个线程持有资源 ReentrantLock 共享模式 同一时刻能有多个线程持有资源 Semaphore, CountDownLatch 两个模式共享同一个等待队列，比如 ReentrantReadWriteLock，读锁（共享）和写锁（独占）的线程在同一个队列中排队。\n原注释以伪代码的形式给出了 AQS 的核心流（独占模式）\nAcquire: while (!tryAcquire(arg)) { enqueue thread if it is not already queued; possibly block current thread; } Release: if (tryRelease(arg)) unblock the first queued thread; (Shared mode is similar but may involve cascading signals.) Condition 的支持 AQS 有个内部类 ConditionObject，用于支持独占模式下的条件等待。每个 Condition 都有自己的等待队列，线程调用 await 时，会从同步队列转移到条件队列，当被 signal 唤醒时，再从条件队列移回同步队列。这也是 ReentrantLock 能提供 newCondition 方法的原因。\n插队 （Barge ahead） AQS 默认允许\u0026quot;插队\u0026quot;，一个新来的线程可以在排队线程之前尝试获取锁，这样做的好处是减少上下文切换开销、提高吞吐量；另外还能避免 \u0026ldquo;convoy 效应\u0026rdquo;——即队首线程因某些原因没能拿到锁，导致后续线程全部陪葬、锁空闲浪费。\n如果想实现公平锁，就在 tryAcquire 中调用 hasQueuedPredecessors，如果队列中有比当前线程更早的等待着，就返回 false，让当前线程去排队。\n操作 state 的三个核心方法 方法 作用 getState 读取 state setState(int) 设置 state，非原子，拿到锁再用 compareAndSetState(int expect, int update) cas 方式更新 state，原子，用于竞争场景 第三个方法依赖 unsafe 类的 CAS 指令，它和 volatile 配合：volatile 保证可见性——当前线程看到的是从主内存读到的最新值——CAS 在此基础上做无锁更新；如果没有 volatile，CAS 的结果就可能不准，因为比较的可能是一个旧值。\n完整示例 Doug Lea 在注释中还给出了一个完整的不可重入的互斥锁示例。在学习示例之前我想解释一个奇怪的现象，所有基于 AQS 构建的同步器，都去选择定义一个私有的静态内部类去继承 AQS，而不是让外部类本体直接继承。比如 ReentrantLock 的源码\npublic class ReentrantLock implements Lock, java.io.Serializable { private final Sync sync; abstract static class Sync extends AbstractQueuedSynchronizer {...} } Doug Lea 在注释中是这样说的\nSubclasses should be defined as non-public internal helper classes that are used to implement the synchronization properties of their enclosing class.\n我觉得这样设计有两点考量：\n这是“有一个”，而不是“是一个”（组合优于继承）。ReentrantLock 是一个锁，他有一个排队器（AQS）而不是“是一个排队器”。也就是组合优于继承，继承是静态的、绑死的；组合是动态的、可控的，如果用继承，AQS 里的 acquire()、 release() 等方法全部对用户可见，用户可以调用 lock.acquire() 来绕过 lock.lock()，破坏了锁的语义。 内部类 + 私有 = 隐藏实现细节。用户到手的 ReentrantLock 只需要用 lock/unlock，不需要知道底层复杂的等待队列。 至于为什么是 private static final，为什么 private 不解释。加 final 是为了保证 sync 在构造后的可见性，当对象被正确构造后，其他线程看到的 final 字段一定是初始化完成的值，不会是 null 或半成品（效果和上一篇的 volatile 类似，上篇末尾提了一下）。为什么不加 static，看一下 deepseek 的解释：\n如果不加 static：内部类会隐式持有一个指向外部类 ReentrantLock 实例的引用（即 this$0）。这意味着只要 Sync 对象还活着，ReentrantLock 对象就永远无法被 GC 回收，存在内存泄漏风险。 加了 static 之后：Sync 和 ReentrantLock 之间没有隐式的引用关系。Sync 只负责排队逻辑，不需要访问 ReentrantLock 的任何实例字段和方法。它需要的外部信息（如公平策略）都通过构造函数传入，而不是通过持有外部类引用来获取。 接下来开始学习示例代码，这部分结束这篇就结束了。\n示例代码是一个不可重入、不处理中断、非公平的独占锁。\n第一层：整体骨架 class Mutex implements Lock, java.io.Serializable { // 内部辅助类：继承 AQS private static class Sync extends AbstractQueuedSynchronizer { ... } // 持有一个 Sync 实例（组合） private final Sync sync = new Sync(); // 对外暴露的 API —— 全部委托给 sync public void lock() { sync.acquire(1); } public void unlock() { sync.release(1); } // ... } 第二层：Sync 的五个关键方法 为什么有的 public，有的 protected？怎么知道要写哪些方法，不是五个吗\n// public 是因为被设计为供外部类直接调用的 API，下文 tryLock 里会直接调用 public boolean tryAcquire(int acquires) { // 先进行断言，该锁不涉及重入，每次只需要把 state 从 0 变为 1 assert acquires == 1; // 原子操作，如果抢锁成功，则设置当前持有锁的线程为“持有锁的线程” if (compareAndSetState(0, 1)) { setExclusiveOwnerThread(Thread.currentThread()); return true; } return false; // 锁被占用，获取失败 } // protected 是因为 unlock 并没有直接调用，而是直接调用 sync.release(1) // release(1) 是 AQS 定义的，内部自动回调 tryRelease。 // 所以 tryRelease 对外部类 Mutex 不可见。 protected boolean tryRelease(int releases) { assert releases == 1; // 同上 // 如果当前线程不是持有锁的线程就抛异常 if (!isHeldExclusively()) throw new IllegalMonitorStateException(); setExclusiveOwnerThread(null); setState(0); // 不需要原子操作，因为走到这一步说明当前线程已经持有锁 return true; } public boolean isHeldExclusively() { // a data race, but safe due to out-of-thin-air guarantees //标记一下这个注释，我觉得现阶段我理解不了，或许几个月后再回过头来我会有新的体会 return getExclusiveOwnerThread() == Thread.currentThread(); } public boolean isLocked() { return getState() != 0; } public Condition newCondition() { return new ConditionObject(); // 看清楚 } 第三层：外部类 private final Sync sync = new Sync(); public void lock() { sync.acquire(1); } public boolean tryLock() { return sync.tryAcquire(1); } public void unlock() { sync.release(1); } public Condition newCondition() { return sync.newCondition(); } public boolean isLocked() { return sync.isLocked(); } public boolean isHeldByCurrentThread() { return sync.isHeldExclusively(); } public boolean hasQueuedThreads() { return sync.hasQueuedThreads(); } public void lockInterruptibly() throws InterruptedException { sync.acquireInterruptibly(1); } public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireNanos(1, unit.toNanos(timeout)); } 基本结束了，还有一点，就是关于 AQS 序列化和反序列化的问题。\n同步队列的节点在 AQS 父类中被 transient 修饰，表示序列化时不考虑这些字段。序列化时只处理非 transient 字段，但对于 ReentrantLock 来说，只会序列化 Sync 对象，而 Sync 里的 state 等关键状态会被保存；反序列化时，Sync 类中重写了 readObject 方法，确保无论锁在序列化前处于什么状态，反序列化后都会变成一个全新的，处于未锁定状态的锁。\n这篇写了好几天，反复问 deepseek 老师，还让它打磨了一下，内容实在太多了，源码编写者太强了。下一篇讲 AQS 具体的实现。\n","permalink":"https://blog.withdong02.top/posts/juc-2/","summary":"\u003cp\u003e上一篇的概念是真多，这一篇应该代码多，大头是 AQS。\u003c/p\u003e\n\u003ch2 id=\"lock-接口\"\u003eLock 接口\u003c/h2\u003e\n\u003cp\u003e上一篇结尾讲到了 synchronized，但它由于设计上的原因，有几个无法满足灵活需求的特性：\u003c/p\u003e","title":"JUC-2"},{"content":"之前没有集中学习过线程、锁、并发容器和线程池等，学习时间一直很散，希望能通过这两三天的学习使自己对 java 并发编程的理解进一步加深。\n我打算分三篇来记录，这是第一篇。\n进程与线程 首先区分并行与并发：\n并行是真的同时进行，在多核 CPU 上多个任务在同一时间真正地同时执行。 并发是假的同时进行，多个任务在同一时间段内交替执行，通过时间片轮转来调度线程，因为1秒内就可能交替执行上万次，所以从人的视角来看似乎是“同时执行”。 然后区分进程与线程：\n进程是操作系统资源分配的最小单位，拥有独立的地址空间。进程之间相互隔离。一个 jvm 就是一个进程。 线程是 CPU 调度的最小单位，多个线程可以共享一个进程的资源，比如堆和方法区，也可以拥有独立的程序计数器、虚拟机栈、本地方法栈等资源。 一个进程内部多个线程可以并发执行，这正是多线程提升性能的根本原因，但同时也是所有并发问题的来源，我总结为两大类，性能问题和安全问题。\n性能问题：主要是线程创建和上下文切换的开销，但需要说明的是，并发不一定一定比串行快，只有在任务涉及 I/O 等阻塞操作时，并发才能通过利用等待时间去执行其他任务，从而提高 CPU 利用率和吞吐量。\n安全问题：主要就三类：原子性、可见性、有序性。下文还会细说。\njava 有一套完整的规范来协调这些行为，那就是 java memory model，又叫 java 内存模型。\nJava 内存模型（JMM） 主内存与工作内存 JMM 是虚拟机规范中定义的一个抽象模型，针对的是多线程环境下如何在主内存与工作内存（逻辑概念，不等于内存，一般指 CPU 缓存）之间安全地执行操作。注意区分 JMM 和 JVM 的运行时区域。\n主内存：所有的共享变量都存储在主内存中。 工作内存：每个线程都保存了一份共享变量的副本。 线程对共享变量的所有操作都必须在自己的工作内存中进行，不能直接从主内存中读写。如果线程 A 要和线程 B 通信，要经过两个步骤：线程 A 将本地内存中更新过的共享变量刷新到主内存，线程 B 再到主内存中读取。\nJMM 解决的三大问题 原子性：一个或多个操作要么全部执行成功，要么全部不执行（JMM 只解决基础读写的原子性）。 可见性：一个线程修改了共享变量的值，另一个线程应该立即知道。 有序性：在单个线程内，程序的执行结果看起来像按代码顺序执行（即 as-if-serial 语义）；多线程间的有序性则由 happens-before 规则来约束。 更底层看，JMM 通过内存屏障来解决可见性以及禁止重排序的问题（具体不细说太难懂），而 Happens-Before 规则是 JMM 在内存屏障之上对程序员暴露的语义层：只要两个操作满足 happens-before 关系，就无须关心底层实现，JMM 也能保证结果的一致性。\nvolatile volatile 关键字可以保证多线程环境下共享变量的可见性，同时也禁止指令重排序，但不保证原子性。\n重排序的意义 编译器和处理器为了优化程序性能而对指令序列进行排序的一种手段。比如 a = 1; b = 2; c = a + b ，前两步不存在数据依赖关系，所以可能发生重排序（比如先执行 b = 2 ，再执行 a = 1）。重排序是为了优化性能，但不管怎么排序，单线程下程序的执行结果不能被改变。比如单线程下，第三步不可能早于前两步执行。\nhappens-before JMM 需要在强大的内存模型（易于编程）和性能（提高编译器和处理器的执行效率）之间找到平衡，平衡点就是 happens-before 规则。\n一个操作 happens-before 于另一个操作，那么第一个操作的执行结果将对第二个操作执行结果可见，且第一个操作的执行顺序在第二个顺序之前。这是 JMM 对程序员的保证，至于实际处理时到底是按什么顺序来的，你不用管。\n天然的 happens-before 关系：\n程序顺序规则：一个线程中的每一个操作，happens-before 于该线程的任意后续操作。 监视器锁规则（synchronized）：对于一个锁的解锁，happens-before 于对这个锁的加锁。 volatile 变量规则：对一个 volatile 域的写，happens-before 于任意后续对这个 volatile 域的读。 传递性：a happens-before b，且b happens-before c，那么a happens-before c。 start规则：线程 A 执行 ThreadB.start() 启动线程 B，那么该操作 happens-before 于子线程 B 中的任意操作，也就是说主线程在子线程 start() 之前的所有写入，都对子线程可见。 join 规则：线程 A 执行 ThreadB.join() 并成功返回，那么线程 B 中的任意操作 happens-before 于线程 A 从 join 中成功返回，也就是说子线程的所有写入，在 join() 返回后，对主线程可见。 线程中断规则：一个线程对另一个线程调用 interrupt() 方法，happens-before 于被中断线程检测到中断事件的发生。也就是发送中断信号的线程，在调用interrupt() 之前的所有写入，对于被中断线程在检测到中断后都是可见的。 volatile 的经典使用 最经典的就是双重检查锁了，懒汉单例的经典场景。\nclass Singleton { private static volatile Singleton INSTANCE; private Singleton() {} public Singleton getInstance() { if (INSTANCE == null) { synchronized (Singleton.class) { if (INSTANCE == null) { INSTANCE = new Singleton(); } } } return INSTANCE; } } 两层判空的作用，外层是为了性能考虑，不用每次都上锁进入同步块；内层是为了保证单例，假如没有，线程 T1 和 T2 同时经过第一层判空，T1 拿到了锁创建了实例，此时 T2 拿到锁又创建一个全新对象，单例失效。\nvolatile 在此处的双重作用：\n禁止重排序：INSTANCE = new Singleton(); 底层分为三步——分配内存、初始化对象、将引用指向内存。加了 volatile 后，第 2、3 步不会被重排。 保证可见性：T1 写入 INSTANCE 后，T2 在自己的工作内存中能立刻读到最新值。 如果缺少 volatile，T1 可能执行完第 1 步和第 3 步（此时引用已非空），T2 在外层判空时直接跳过同步块，返回一个尚未初始化的\u0026quot;半成品\u0026quot;对象。\n懒汉单例也有其他方案比如方法上加 synchronized、静态内部类（加载类时 JVM 隐式加锁），他们都不需要 volatile，前者是因为提到的监视器锁规则，后者则利用了 JVM 类加载机制时的安全保证。\n注意：volatile 并不能保证原子性，想计数不要用 volatile，可以选择给计数操作加锁或使用并发容器。\nSynchronized synchronized 有三种用法：\n修饰实例方法：锁当前对象 this。 修饰静态方法：锁类的 Class 对象。 修饰代码块：锁指定对象。 synchronized 常被拿来和 ReentrantLock 对比，简单说明一下。synchronized 是 JVM 层面关键字，底部基于监视器实现，只能是非公平锁。\n随着线程竞争得激烈，synchronized 会从偏向锁到轻量级锁再到重量级锁，这也是 JVM 对 synchronized 的优化，性能不一定比 ReentrantLock 差。\nfinal 在并发编程中也有重要价值，final 字段的写操作不会被重排序到构造函数之外，因为对象被其他线程可见时一定已完成初始化，这正是 String 等不可变类的字段被设计为 final 的原因。\n第一篇就先到这里，经过这些概念的学习，我感觉我已经升华了。\n","permalink":"https://blog.withdong02.top/posts/juc-1/","summary":"\u003cp\u003e之前没有集中学习过线程、锁、并发容器和线程池等，学习时间一直很散，希望能通过这两三天的学习使自己对 java 并发编程的理解进一步加深。\u003c/p\u003e\n\u003cp\u003e我打算分三篇来记录，这是第一篇。\u003c/p\u003e","title":"JUC-1"},{"content":"一切的一切源于那烦人的“便了么”，从去年年底到今年五月，我已经用了半年，却总是忘记点击“拉完了”按钮，导致一拉就拉了十几分钟，于是我决定自己写一个。\n最开始我打算用 Flutter，反正用的是 ai，但随着项目开始我就遇到了很多困难，编程语言下载、Android Studio配置、app 打包等。后来我了解到了 uniapp 和 HBuilderX，“开发一次，多端覆盖”，这句口号吸引了我，了解后我快速上手，马上也得到了最初的版本，像下面这样。\n点击查看图片 不得不说确实简陋，至少清晰明了哈哈哈。最开始我是打算做多端的，先实现了 app 版本，然后用 uniapp 转小程序，结果效果不堪入目，各种卡片高度错乱，字体排版混乱，可能是我没有写好提示词，导致代码不兼容，后续维护也比较麻烦。经过我深思熟虑后，我选择同时进行两个版本的开发，app 不动，小程序用微信原生框架。我在这个方向进行了很长一段时间，遇到很多 bug，各种加载问题、组件、数据库部署等各种问题，如下图。当然在这两个多月开发中我学到了很多，中间也使用了各种 skill，不断在网上看 ui 设计的视频，学习一些好用的提示词等等。\n点击查看图片 今天是7月28日，距离第一次提交代码已经过去快两个月，我又做了一个重要的决定，放弃 app 版本，全面转向微信小程序的开发，主要还是双端维护过于复杂，看似功能完好，实际上是屎上绣花。小程序任何操作系统上都可以用，而 app 目前只能在安卓手机上使用，所以我果断转向小程序。还有个原因就是我的小程序备案通过了，app 在应用市场上架很麻烦，直接给安装包很多人不会用，而小程序就简单了，直接微信扫个码就好。\n转小程序大概4天前了，小程序也是越来越好，等我做好第一个正式版，我就要发到朋友圈里嘻嘻。\n好久没写博客，这一篇就先到这里。\n","permalink":"https://blog.withdong02.top/posts/recordshit-%E5%85%B3%E9%94%AE%E8%AE%B0%E5%BD%95/","summary":"\u003cp\u003e一切的一切源于那烦人的“便了么”，从去年年底到今年五月，我已经用了半年，却总是忘记点击“拉完了”按钮，导致一拉就拉了十几分钟，于是我决定自己写一个。\u003c/p\u003e","title":"RecordShit 关键记录"},{"content":"前一阵子，原来的云服务器快到期了，续费还不如买个新的，正好用来部署博客，想着负载不大，就选了实惠的规格，2核2G。\n最近想要部署一个项目，后端是 docker 化的，在我换好镜像源，执行 docker-compose up -d ，一分钟后，我的 ide 和服务器断开了远程连接。重新连接依然不行。当我打开云厂商的监控页面时，看到恐怖的一幕，我的服务器好像要不行了。\n然后我尝试多个工具进行远程登录，无一例外都失败了。 我尝试通过网页 VNC 登录，看到了 Out of memory: KIlled process，说明一点内存也没了。怪我选规格时没考虑那么多。后来 AI 分析：甚至连 SSH 守护进程都因为内存不够而被操作系统 OOM Killer 无情杀掉，远程连接彻底没戏。\n我的第一想法是重启，但依旧没用。问题出在我的 docker 上，所有容器都配置了 restart: always，所以一开机容器自启动。经过简单算账后，我选择用钱来解决🧐。升级到了4G 内存。\n其实光升级内存还是有点紧张的，同时我还修改了 docker 的配置文件，限制了每个容器的可用内存。\n部署项目就简单了，先启动 docker 容器，然后启动后端 jar 包，前端用 pnpm 命令构建一下，再配置一下 nginx 反向代理，绑定域名就ok了。\n","permalink":"https://blog.withdong02.top/posts/%E4%BA%91%E6%9C%8D%E5%8A%A1%E5%99%A8-oom/","summary":"\u003cp\u003e前一阵子，原来的云服务器快到期了，续费还不如买个新的，正好用来部署博客，想着负载不大，就选了实惠的规格，2核2G。\u003c/p\u003e\n\u003cp\u003e最近想要部署一个项目，后端是 docker 化的，在我换好镜像源，执行 \u003ccode\u003edocker-compose up -d\u003c/code\u003e ，一分钟后，我的 ide 和服务器断开了远程连接。重新连接依然不行。当我打开云厂商的监控页面时，看到恐怖的一幕，我的服务器好像要不行了。\u003c/p\u003e","title":"云服务器 OOM"},{"content":"十种排序算法 冒泡排序 一共进行 n - 1 轮，在每一轮排序中对相邻两元素进行比较，大的排在后面。\n/** * 冒泡排序 * 时间复杂度：最优 O(n)，最坏 O(n²)，平均 O(n²) * 空间复杂度：O(1)，原地排序 * 稳定性：稳定 */ private static void bubbleSort(int[] arr) { int n = arr.length; boolean flag = true; for (int i = 0; i \u0026lt; n - 1; i++) { flag = true; for (int j = 0; j \u0026lt; n - 1 - i; j++) { if (arr[j] \u0026gt; arr[j + 1]) { swap(arr, j, j + 1); flag = false; } } if (flag) break; } } 选择排序 同样进行 n - 1 轮，每一轮从待排序序列中挑出最小的元素，将其放至已排序序列的末尾。\n/** * 选择排序 * 时间复杂度：最优 O(n²)，最坏 O(n²)，平均 O(n²) * 空间复杂度：O(1) 原地排序 * 稳定性：不稳定 * @param arr */ private static void selectionSort(int[] arr) { int n = arr.length; for (int i = 0; i \u0026lt; n - 1; i++) { int minIndex = i; for (int j = i + 1; j \u0026lt; n; j++) { if (arr[j] \u0026lt; arr[minIndex]) { minIndex = j; } } swap(arr, i, minIndex); } } 插入排序 同样进行 n - 1 轮，每一轮把待排序序列的第一个元素插进已排序序列里的核心位置。可以想想打牌时理牌的过程。\n/** * 插入排序 * 时间复杂度：最优 O(n)，最坏 O(n²)，平均 O(n²) * 空间复杂度： O(1) 原地排序 * 稳定性：稳定 */ private static void insertionSort(int[] arr) { int n = arr.length; for (int i = 1; i \u0026lt; n; i++) { int temp = arr[i]; int j = i - 1; while (j \u0026gt;= 0 \u0026amp;\u0026amp; temp \u0026lt; arr[j]) { arr[j + 1] = arr[j]; j--; } arr[j + 1] = temp; } } 希尔排序 插入排序改进版，将待排序序列分为若干组，步长为 gap，分组执行插入排序，逐渐缩小步长至 1。目的就是在初期就让元素接近它的最终位置，减少整体的比较次数。\n/** * 希尔排序 * 时间复杂度：最优 O(n)，最坏和平均与间距序列的选取有关 * 空间复杂度： O(1) 原地排序 * 稳定性：不稳定 */ private static void shellSort(int[] arr) { int n = arr.length; for (int gap = n / 2; gap \u0026gt; 0; gap /= 2) { for (int i = gap; i \u0026lt; n; i++) { int temp = arr[i]; int j = i - gap; while (j \u0026gt;= 0 \u0026amp;\u0026amp; temp \u0026lt; arr[j]) { arr[j + gap] = arr[j]; j -= gap; } arr[j + gap] = temp; } } } 归并排序 分治法采用分而治之的思想，将大数组拆为小数组，对小数组排序，然后合并为大数组。 倍增法： 从左往右依次合并两个长度为 1 的有序段，等到若干长度为 2 的有序段； 从左往右依次合并两个长度为 2 的有序段，等到若干长度为 3 的有序段； 直至有序段长度为数组长度。\n/** * 归并排序 * 时间复杂度：最优 O(nlong)，，最坏 O(nlogn)，平均 O(nlogn) * 空间复杂度： O(n) 不是原地排序 * 稳定性：稳定 * 分治法和倍增法两种实现方式 */ private static void mergeSort(int[] arr) { int n = arr.length; if (n \u0026lt; 2){ return;} int mid = n / 2; int[] left = Arrays.copyOfRange(arr, 0, mid); int[] right = Arrays.copyOfRange(arr, mid, n); mergeSort(left); mergeSort(right); merge(arr, left, right); } private static void merge(int[] arr, int[] left, int[] right) { int i = 0, j = 0, k = 0; while (i \u0026lt; left.length \u0026amp;\u0026amp; j \u0026lt; right.length) { if (left[i] \u0026lt; right[j]) { arr[k++] = left[i++]; } else { arr[k++] = right[j++]; } } while (i \u0026lt; left.length) arr[k++] = left[i++]; while (j \u0026lt; right.length) arr[k++] = right[j++]; } 快速排序 同样分治的思想，关键操作 partition，随机选取一个元素 pivot 来将数组分为两部分，经过partition 操作来使前一部分的元素值小于 pivot ，后一部分大于 pivot。然后递归的对两部分进行快速排序。\n/** * 快速排序 * 时间复杂度：最优 O(nlong)，，最坏 O(n²)，平均 O(nlogn) * 空间复杂度： 考虑递归栈空间O(logn) 原地排序 * 稳定性：不稳定 */ private static void quickSort(int[] arr, int left, int right) { if (left \u0026gt;= right) return; int i = partition(arr, left, right); quickSort(arr, left, i - 1); quickSort(arr, i + 1, right); } private static int partition(int[] arr, int left, int right) { Random rand = new Random(); int i = left + rand.nextInt(0, right - left + 1); int pivot = arr[i]; swap(arr, i, left); i = left + 1; int j = right; while (true) { while (i \u0026lt;= j \u0026amp;\u0026amp; arr[i] \u0026lt; pivot) i++; while (i \u0026lt;= j \u0026amp;\u0026amp; arr[j] \u0026gt; pivot) j--; if (i \u0026gt;= j) break; swap(arr, i, j); i++; j--; } swap(arr, left, j); return j; } private static void swap(int[] arr, int i, int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } 堆排序 思想就是将数组每一个元素给映射到一颗完全二叉树（堆）上，该树的每一个节点的值都大于子节点的值（大根堆）。每一步交换堆顶和数组的最后一个元素，然后重新调整堆。\n/** * 堆排序 * 时间复杂度：最优 O(nlong)，最坏 O(nlogn)，平均 O(nlogn) * 空间复杂度： O(1) 原地排序 * 稳定性：不稳定 */ private static void heapSort(int[] arr) { int n = arr.length; for (int i = n / 2 - 1; i \u0026gt;= 0; i--) { heapify(arr, n, i); } for (int i = n - 1; i \u0026gt;= 0 ; i--) { swap(arr, 0, i); heapify(arr, i, 0); } } private static void heapify(int[] arr, int n, int i) { int largest = i; int left = 2 * i + 1; int right = 2 * i + 2; if (left \u0026lt; n \u0026amp;\u0026amp; arr[left] \u0026gt; arr[largest]) { largest = left; } if (right \u0026lt; n \u0026amp;\u0026amp; arr[right] \u0026gt; arr[largest]) { largest = right; } if (largest != i) { swap(arr, i, largest); heapify(arr, n, largest); } } private static void swap(int[] arr, int i, int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } 计数排序 前面的七种都算是比较排序。而后三种通过额外的数据结构来统计元素的出现次数从而实现排序。\n/** * 计数排序 * 时间复杂度：最好 O(n+k)；最坏 O(n+k)；平均 O(n+k) * 空间复杂度：O(n + k)（我觉得是） 不是原地排序 * 稳定性：从后往前填充稳定 */ private static void countingSort(int[] arr) { int max = Arrays.stream(arr).max().getAsInt(); int min = Arrays.stream(arr).min().getAsInt(); int range = max - min + 1; int[] count = new int[range]; for (int num : arr) { count[num - min]++; } for (int i = 1; i \u0026lt; count.length; i++) { count[i] += count[i - 1]; } //count[i] 等于小于等于 i 的元素个数 int[] output = new int[arr.length]; for (int i = arr.length - 1; i \u0026gt;= 0; i--) { output[count[arr[i] - min] - 1] = arr[i]; count[arr[i] - min]--; } System.arraycopy(output, 0, arr, 0, arr.length); } 基数排序 以自然数为例，基数排序就是先对每个数按个位排序，然后十位百位。每一位的排序基于计数排序（不一定）。从第 1 关键字到第 k 关键字顺序比较为 MSD基数排序，反过来是 LSD基数排序。我实现的是后者\n/** * 基数排序 * 时间复杂度：最好 O(nk)；最坏 O(nk)；平均 O(nk) * 空间复杂度：O(n+k), k 为最大元素位数，不是原地排序 * 稳定性：从后往前填充稳定 */ private static void radixSort(int[] arr) { int max = Arrays.stream(arr).max().getAsInt(); for (int radix = 1; max / radix \u0026gt; 0 ; radix *= 10) { countingSortByDigit(arr, radix); } } private static void countingSortByDigit(int[] arr, int radix) { int[] count = new int[10]; for (int i = 0; i \u0026lt; arr.length; i++) { int digit = (arr[i] / radix) % 10; count[digit]++; } for (int i = 1; i \u0026lt; count.length; i++) { count[i] += count[i - 1]; } int[] output = new int[arr.length]; for (int i = arr.length - 1; i \u0026gt;= 0; i--) { int digit = (arr[i] / radix) % 10; output[count[digit] - 1] = arr[i]; count[digit]--; } System.arraycopy(output, 0, arr, 0, arr.length); } 桶排序 将数组元素分到若干个桶中，对每个桶内元素进行排序（使用其他排序算法），然后合并桶\n/** * 桶排序 * 时间复杂度：最优 O(n + k)，最坏 O(n²)，平均 O(n + k) * 空间复杂度： O(n + k) 不是原地排序 * 稳定性：稳定 */ private static void bucketSort(int[] arr, int bucketNum) { int max = Arrays.stream(arr).max().getAsInt(); int min = Arrays.stream(arr).min().getAsInt(); List\u0026lt;List\u0026lt;Integer\u0026gt;\u0026gt; list = new ArrayList\u0026lt;\u0026gt;(bucketNum); for (int i = 0; i \u0026lt; bucketNum; i++) { list.add(new ArrayList\u0026lt;\u0026gt;()); } for (int num : arr) { int index = (num - min) * (bucketNum - 1) / (max - min); List\u0026lt;Integer\u0026gt; bucket = list.get(index); bucket.add(num); } int arrIndex = 0; for (List\u0026lt;Integer\u0026gt; bucket : list) { Collections.sort(bucket); for (Integer i : bucket) { arr[arrIndex++] = i; } } } Java内置排序 Arrays.sort() 当数组元素为基本数据类型时，会根据数据类型进入对应的 sort 方法（方法重载）。随后调用 DualPivotQuickSort 类的 sort 方法。\n关于 DualPivotQuickSort 类，源代码中是这样注释的（jdk21）\nThis class implements powerful and fully optimized versions, both sequential and parallel, of the Dual-Pivot Quicksort algorithm by Vladimir Yaroslavskiy, Jon Bentley and Josh Bloch. This algorithm offers O(n log(n)) performance on all data sets, and is typically faster than traditional (one-pivot) Quicksort implementations. There are also additional algorithms, invoked from the Dual-Pivot Quicksort, such as mixed insertion sort, merging of runs and heap sort, counting sort and parallel merge sort.\n大致意思就是这个类是超级版本的双轴快速排序（两个 pivot）实现，性能超强，根据数据规模也会采用其他排序算法如混合插入排序、堆排序等。为什么是双轴呢，因为三个人研究证明双轴的改进针对长度较大的序列是一种有效的方式。\nint end = high - 1, size = high - low; /* * Run mixed insertion sort on small non-leftmost parts. */ if (size \u0026lt; MAX_MIXED_INSERTION_SORT_SIZE + bits \u0026amp;\u0026amp; (bits \u0026amp; 1) \u0026gt; 0) { mixedInsertionSort(a, low, high - 3 * ((size \u0026gt;\u0026gt; 5) \u0026lt;\u0026lt; 3), high); return; } /* * Invoke insertion sort on small leftmost part. */ if (size \u0026lt; MAX_INSERTION_SORT_SIZE) { insertionSort(a, low, high); return; } /* * Check if the whole array or large non-leftmost * parts are nearly sorted and then merge runs. */ if ((bits == 0 || size \u0026gt; MIN_TRY_MERGE_SIZE \u0026amp;\u0026amp; (bits \u0026amp; 1) \u0026gt; 0) \u0026amp;\u0026amp; tryMergeRuns(sorter, a, low, size)) { return; } /* * Switch to heap sort if execution * time is becoming quadratic. */ if ((bits += DELTA) \u0026gt; MAX_RECURSION_DEPTH) { heapSort(a, low, high); return; } 整个DualPivotQuickSort 类的 sort 方法就是一个巨大的 while 循环，循环前半部分进行一些条件判断，就是上面的代码，如果待排序的元素数量满足一定条件，则调用不同的排序方法。如果都不满足，则会执行双轴快排或普通快排，然后在快排里再次递归调用 sort，直至待排序元素数量满足上面的条件，调用其他排序然后 return 退出 while 循环。\n当数组元素不为基本数据类型时，要么类实现了 Comparable 接口，自定义重写 compareTo 方法，要么调用 sort 方法时，传进去一个 Comparator 比较器，一般用 lamda 表达式。否则会报异常。\n当执行 Arrays.sort() 方法时，进入的是下面的方法。方法注释中表明：该实现是一种稳定、自适应、迭代的归并排序。默认情况下排序实现为 Timsort，它结合了插入排序和归并排序的优点，是 python 标准库的默认排序算法。\npublic static \u0026lt;T\u0026gt; void sort(T[] a, Comparator\u0026lt;? super T\u0026gt; c) { if (c == null) { sort(a); } else { if (LegacyMergeSort.userRequested) legacyMergeSort(a, c); else TimSort.sort(a, 0, a.length, c, null, 0, 0); } } Collections.sort() 该方法根据 List 接口的具体实现会有不同的调用链路，但最终调用的还是 Arrays.sort()。\n整理完毕。别人写出如此优秀的代码，如今能学习到也算是我的荣幸了。\n这篇博客就到这里，错误不可避免，欢迎指正。我会持续更新，欢迎收藏我的网站。\n参考文章：\n十大经典排序算法详解与动态图解：从入门到精通 - 知乎 排序简介 - OI Wiki ","permalink":"https://blog.withdong02.top/posts/%E5%8D%81%E7%A7%8D%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%E6%95%B4%E7%90%86/","summary":"\u003ch2 id=\"十种排序算法\"\u003e十种排序算法\u003c/h2\u003e\n\u003ch3 id=\"冒泡排序\"\u003e冒泡排序\u003c/h3\u003e\n\u003cp\u003e一共进行 n - 1 轮，在每一轮排序中对相邻两元素进行比较，大的排在后面。\u003c/p\u003e\n\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-Java\" data-lang=\"Java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e/**  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e * 冒泡排序  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e * 时间复杂度：最优 O(n)，最坏 O(n²)，平均 O(n²)  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e * 空间复杂度：O(1)，原地排序  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e * 稳定性：稳定  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e */\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003ebubbleSort\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003earr\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003en\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003earr\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003elength\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kt\"\u003eboolean\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eflag\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003en\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eflag\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003en\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003earr\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003earr\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003eswap\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003earr\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003eflag\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003efalse\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eflag\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003ebreak\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 id=\"选择排序\"\u003e选择排序\u003c/h3\u003e\n\u003cp\u003e同样进行 n - 1 轮，每一轮从待排序序列中挑出最小的元素，将其放至已排序序列的末尾。\u003c/p\u003e","title":"十种排序算法整理"},{"content":"参加了一次笔试，收获颇多。\n不同的输入方法 和力扣不一样，一些大厂的笔试包括面试时的算法题都是 ACM 模式，也就是需要自己处理输入，然后对输入处理并输出，而力扣是核心代码模式，不用你管输入，只需要实现指定方法，平台自会调用你的方法。在参加之前，我去卡码网上了解了一下，发现还是 Scanner 那一套，也就没在意，但当我真正开始做的时候感到有点不对，Scanner 恐怕不太够用。这篇博客主要讲一下 Java 里的输入流。\nJava 的输入体系建立在 流 的概念之上，主要有两个抽象基类：InputStream 和 Reader ，前者为字节流，处理原始字节数据，如图片、音频等；后者为字符流，处理字符数据，如文本文件、控制台输入等。两者之间通过 InputStreamReader 作为桥梁，将字节流转换为字符流。\nInputStream 字节流 InputStream 类定义了所有字节输入流必须实现的核心办法。包括 int read() , int read(byte[] b) , void close()等方法。\nJava 的 IO 体系采用了装饰器模式，意味着可以通过组合不同的流来实现复杂的功能。这里将 InputStream 的子类大致分为两类。\n第一种，这种类直接与特定的数据源进行交互，比如 FileInputStream ByteArrayInputStream 。\n第二种，这种类不直接连接数据源，而是通过继承装饰器基类 FilterInputStream 来包装一个已存在的 InputStream ，给它提供一些额外的功能，比如缓冲、数据类型转换等。这种类的典型代表有 BufferedInputStream DataInputStream 等。\nReader 字符流 Reader 类是字符输入流的抽象基类，定义的方法有 int read() , int read(char[] cbuf) , void close() 等。\n它的子类和 InputStream 一样，也分两种，这里不再多说。但它有一个特别的子类： InputStreamReader 从它的类名就能看出来，它是字节流到字符流的单向桥梁，它可以读取字节，然后根据指定的字符编码将其解码为字符。\nSystem.in 它是 System 类里的一个静态变量，类型是 InputStream ，所以它是一个字节流。如果需要按字符或行读取，通常需要对其包装，比如刚才提到的 InputStreamReader 和常见的 Scanner。通常情况下，System.in 默认连接到控制台（键盘），同时它读取数据也是阻塞式的，程序会一直等待，直到有数据可读或者流被关闭。\nScanner Scanner 不属于 InputStream 或 Reader ，它是一个独立的类。它的内部有这样一个变量 private Readable source; ，表示内部有一个 Readable 接口的实现，这就是关键所在，因为 Reader 就实现了 Readable 接口，所以， Scanner 可以接受 Reader , InputStream（通过桥转换） , String 作为输入源。这就是 new Scanner(System.in) 的用法由来。\n这一点设计的十分巧妙， Scanner 并不依赖具体的类，不依赖 InputStream ，不依赖 Reader ，它只依赖能力，它只需要一个实现了 Readable 接口的对象。\n下面是它的部分构造器\npublic Scanner(Readable source) { this(Objects.requireNonNull(source, \u0026#34;source\u0026#34;), WHITESPACE_PATTERN); } public Scanner(InputStream source) { this(new InputStreamReader(source), WHITESPACE_PATTERN); } public Scanner(String source) { this(new StringReader(source), WHITESPACE_PATTERN); } Scanner 内部使用正则表达式解析输入，提供了 nextInt() , nextLine() 等方法，极其方便。成也萧何，败也萧何，它会利用正则表达式来对输入类型进行检查，保证了安全性，但当数据量较大时，读取速度会显著下降，同时它的缓冲机制效率也相对较低，比不上 BufferedReader 等专为高效 IO 设计的类。\n我进行了一个小实验，准备了一个文件，里面的内容是100万行数字1，分别用 Scanner 和 BufferedReader 对文件读取，耗时相差十倍左右。\n还有一点，Scanner 是在 Java.util 包里，而前面提到的输入流都是在 java.io 包里。\n总结 分析了不同输入方法，我总结了一下。一般就两种情况，第一种：输入量极小，一般都是第一题，这时用 Scanner 完全没问题；第二种：输入量在 10 的六次方左右，或者说有很多组数据。一般都是下面这种写法，当一行里面有多个数据时，可以使用 StringTokenizer 来分词。\nBufferedReader br = new BufferedReader(new InputStreamReader(System.in)); StringTokenizer st = new StringTokenizer(br.readLine()); int k = Integer.parseInt(st.nextToken()); int q = Integer.parseInt(st.nextToken()); 除了上面两种，还有更快的，就是自定义一个 FastScanner ，类里面使用 BufferedInputStream 直接处理字节流输入，同时需要自己写方法解析输入。\n上面讲的全是输入流，其实输出流的设计和输入流简直一模一样，InputStream 对应 OutputStream ， Reader 对应 Writer ，输出流里也有一个桥：OuputStreamWriter，用来将字符流转换为字节流。System.in 也对应了 System.out ，同样，当需要输出很多行时，System.out.println 的性能也稍差，可以使用 StringBuilder 对输出拼接，最后转为字符串。\nAI面试记录 记录一下问到的问题：\nRedis 两种持久化机制的优缺点。 Redis 提供两种持久化机制\nRDB（快照）：在指定时间间隔内将内存数据生成快照保存到磁盘。 优点：文件小；恢复速度快；对性能影响小（可fork子进程）。适合冷备份，灾难恢复。 缺点：可能丢失最后一次快照之后，崩溃之前这一部分的数据。 AOF（追加文件）：记录每个写操作命令到日志文件，重启时重写命令。 优点：最多丢失一秒的数据（默认），安全性高；可读性强，内容为纯文本格式；体积过大会自动重写。 缺点：文件体积大；恢复速度慢；对写性能有一定影响。 Redis4.0 后支持混合持久化，AOF 重写期间，先以 RDB 格式将内存中的数据快照保存到 AOF 文件的开头，再将重写期间的命令以 AOF 格式追加到文件末尾。\n栈和队列的区别，使用场景 栈先进后出，入栈出栈都在一端进行，栈天然支持递归和回溯。常用使用场景有：函数调用栈、表达式求值。\n队列先进先出，入队出队在两端进行。常见使用场景有消息队列、任务调度（线程池的任务队列）、BFS。\n泛型类型的擦除原理 在编译期，编译器会将泛型代码中的泛型参数 E、K 等替换为边界类型，如果没有边界统一替换为 Object。运行期，JVM 眼中不存在泛型类或泛型方法。关于泛型可以 看Java泛型 | withdong02\n这篇博客就到这里，错误不可避免，欢迎指正。我会持续更新，欢迎收藏我的网站。\n","permalink":"https://blog.withdong02.top/posts/%E7%AC%94%E8%AF%95%E6%94%B6%E8%8E%B7/","summary":"\u003cp\u003e参加了一次笔试，收获颇多。\u003c/p\u003e\n\u003ch2 id=\"不同的输入方法\"\u003e不同的输入方法\u003c/h2\u003e\n\u003cp\u003e和力扣不一样，一些大厂的笔试包括面试时的算法题都是 ACM 模式，也就是需要自己处理输入，然后对输入处理并输出，而力扣是核心代码模式，不用你管输入，只需要实现指定方法，平台自会调用你的方法。在参加之前，我去卡码网上了解了一下，发现还是 \u003ccode\u003eScanner\u003c/code\u003e 那一套，也就没在意，但当我真正开始做的时候感到有点不对，\u003ccode\u003eScanner\u003c/code\u003e 恐怕不太够用。这篇博客主要讲一下 \u003cstrong\u003eJava 里的输入流。\u003c/strong\u003e\u003c/p\u003e","title":"笔试收获"},{"content":"常见的权限模型有两种：RBAC 和 ABAC。\n两种模型介绍 RBAC 模型 Role-Based Access Control，翻译为基于角色的访问控制。角色拥有某些权限，通过赋予用户某种角色来达到给用户授予相关权限的目的，这就是基于角色。实现较简单。在一些大型组织中，角色可能爆炸式增长，导致管理复杂、权限冗余。\nABAC 模型 Attribute-Based Access Control，翻译为基于属性的访问控制，它比 RBAC 更加灵活，因为在 ABAC 模型中，一个操作是否被允许是基于对象、资源、操作和环境信息共同动态计算决定的。它的控制更细粒度，比如它能实现“仅允许大学生在水课上睡觉”的控制。\n派聪明的权限控制 派聪明的权限模型是改进版的 RBAC，或者说简化版的 ABAC。设计了 admin 和 user 两种角色，管理员拥有管理知识库，查看用户列表等权限，普通用户拥有查看私有知识库等权限。但单纯的 RBAC 有两种局限性：权限控制力度不够细和没办法基于数据属性做动态授权，所以添加了组织标签机制。\n在上传文件时，给每份文档标记上传者所属的标签，这样用户访问知识库，系统能够动态的基于当前用户的组织标签和文档的组织标签进行匹配过滤。从而实现更细粒度的数据隔离和动态授权。\n此外，派聪明还考虑到了多级组织，设计了组织标签树。比如有这样三个层级标签：总部、研发部、后端部。一个拥有后端部组织标签的员工可以访问研发部、总部这两个父组织的资料。我个人在这一部分绕了很久，总感觉设计反了，现在是这样理解的，抛弃父标签级别高，所以权力大的想法，这样想：\n父标签（如“总公司”）：它定义的是一个最基础、最通用的权限范围。就像一张“员工卡”，让你能访问公司的基础公共资源。 子标签（如“技术部”、“财务部”）：它在父标签的基础上，增加了更具体、更细分的权限。就像一张“技术部员工卡”，它包含了“员工卡”的所有权限，还额外增加了进入技术部专属区域的权限。 这就是派聪明中有关权限模块的设计。\n转转统一权限系统的设计与实现 经过分析，转转技术部门选择了基于 RBAC 模型来实现，但它也有所改动，在原有基础上，新增了给用户直接增加权限的能力，也就是说既可以给用户添加角色，也可以给用户直接添加权限。所以用户最终的权限为 所拥有角色带来的权限和用户独立配置的权限的并集。\n转转的技术文章里还从权限系统自身和具体业务系统两层设计了六种身份，这里不再多说。\n参考文章：\n✅派聪明 RAG用户管理模块设计方案 - 技术派 - Java技术社区 | RAG+Agent实战项目教程+AI助手 转转统一权限系统的设计与实现（设计篇） ","permalink":"https://blog.withdong02.top/posts/%E6%9D%83%E9%99%90%E6%A8%A1%E5%9E%8B/","summary":"\u003cp\u003e常见的权限模型有两种：RBAC 和 ABAC。\u003c/p\u003e\n\u003ch2 id=\"两种模型介绍\"\u003e两种模型介绍\u003c/h2\u003e\n\u003ch3 id=\"rbac-模型\"\u003eRBAC 模型\u003c/h3\u003e\n\u003cp\u003eRole-Based Access Control，翻译为基于角色的访问控制。角色拥有某些权限，通过赋予用户某种角色来达到给用户授予相关权限的目的，这就是基于角色。实现较简单。在一些大型组织中，角色可能爆炸式增长，导致管理复杂、权限冗余。\u003c/p\u003e","title":"权限模型"},{"content":"学习 Spring，跟着二哥简单写个 IoC 容器。\nJava · 90 行 · 点击展开 @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @interface MyAutowired { } @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @interface MyComponent { } public class SimpleIoC { Map\u0026lt;Class\u0026lt;?\u0026gt;, Object\u0026gt; beans = new HashMap\u0026lt;\u0026gt;(); //扫描 public void scan(String packageName) { List\u0026lt;Class\u0026lt;?\u0026gt;\u0026gt; classes = getClassesInPackage(packageName); for (Class\u0026lt;?\u0026gt; clazz : classes) { if (clazz.isAnnotationPresent(MyComponent.class)) { registerBean(clazz); } } di(); } //获取包下的类，简化实现 private List\u0026lt;Class\u0026lt;?\u0026gt;\u0026gt; getClassesInPackage(String packageName) { // 实际实现需要扫描classpath，这里简化处理. return Arrays.asList(UserDao.class, UserService.class); } //注册bean private void registerBean (Class\u0026lt;?\u0026gt; clazz) { try { Object instance = clazz.getDeclaredConstructor().newInstance(); beans.put(clazz, instance); } catch (Exception e) { throw new RuntimeException(\u0026#34;创建bean失败\u0026#34;); } } //获取bean @SuppressWarnings(\u0026#34;unchecked\u0026#34;) public \u0026lt;T\u0026gt; T getBean(Class\u0026lt;T\u0026gt; clazz) { return (T)beans.get(clazz); } //依赖注入 private void di() { for (Object bean : beans.values()) { Field[] fields = bean.getClass().getDeclaredFields(); for (Field field : fields) { if (field.isAnnotationPresent(MyAutowired.class)) { field.setAccessible(true); Object dependency = getBean(field.getType()); try { field.set(bean, dependency); } catch (Exception e) { throw new RuntimeException(\u0026#34;依赖注入失败\u0026#34;); } } } } } } //DAO层 @MyComponent class UserDao { public void save(String user) { System.out.println(\u0026#34;保存用户: \u0026#34; + user); } } // Service层 @MyComponent class UserService { @MyAutowired private UserDao userDao; public void createUser(String name) { userDao.save(name); System.out.println(\u0026#34;用户创建完成\u0026#34;); } } class Test { public static void main(String[] args) { SimpleIoC ioc = new SimpleIoC(); ioc.scan(\u0026#34;package\u0026#34;); UserService bean = ioc.getBean(UserService.class); bean.createUser(\u0026#34;dong\u0026#34;); } } 参考链接：\n二哥的Java进阶之路\nJava反射 | withdong02\nJava泛型 | withdong02\n","permalink":"https://blog.withdong02.top/posts/%E6%89%8B%E5%86%99%E7%AE%80%E5%8D%95ioc%E5%AE%B9%E5%99%A8/","summary":"\u003cp\u003e学习 Spring，跟着二哥简单写个 IoC 容器。\u003c/p\u003e\n\n\u003cdetails class=\"code-collapse\"\u003e\n  \u003csummary\u003eJava · 90 行 · 点击展开\u003c/summary\u003e\n  \u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-Java\" data-lang=\"Java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@Target\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eElementType\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eFIELD\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@Retention\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eRetentionPolicy\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eRUNTIME\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@interface\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eMyAutowired\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@Target\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eElementType\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eTYPE\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@Retention\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eRetentionPolicy\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eRUNTIME\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@interface\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eMyComponent\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eSimpleIoC\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eMap\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eClass\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;?\u0026gt;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eObject\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ebeans\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eHashMap\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u0026gt;\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e//扫描  \u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003escan\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003epackageName\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eList\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eClass\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;?\u0026gt;\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eclasses\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003egetClassesInPackage\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003epackageName\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eClass\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;?\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eclazz\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eclasses\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eclazz\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eisAnnotationPresent\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eMyComponent\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eclass\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003eregisterBean\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eclazz\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003edi\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e//获取包下的类，简化实现  \u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eList\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eClass\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;?\u0026gt;\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003egetClassesInPackage\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003epackageName\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 实际实现需要扫描classpath，这里简化处理.  \u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eArrays\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003easList\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eUserDao\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eclass\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eUserService\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eclass\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e//注册bean  \u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003eregisterBean\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eClass\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;?\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eclazz\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003etry\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003eObject\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003einstance\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eclazz\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetDeclaredConstructor\u003c/span\u003e\u003cspan class=\"p\"\u003e().\u003c/span\u003e\u003cspan class=\"na\"\u003enewInstance\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003ebeans\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eput\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eclazz\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003einstance\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003ecatch\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eException\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003ethrow\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eRuntimeException\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;创建bean失败\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e//获取bean  \u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nd\"\u003e@SuppressWarnings\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;unchecked\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003egetBean\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eClass\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eclazz\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"n\"\u003ebeans\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eclazz\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e//依赖注入  \u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003edi\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eObject\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ebean\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ebeans\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003evalues\u003c/span\u003e\u003cspan class=\"p\"\u003e())\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003eField\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003efields\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ebean\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetClass\u003c/span\u003e\u003cspan class=\"p\"\u003e().\u003c/span\u003e\u003cspan class=\"na\"\u003egetDeclaredFields\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eField\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003efield\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003efields\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003efield\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eisAnnotationPresent\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eMyAutowired\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eclass\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"n\"\u003efield\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetAccessible\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"n\"\u003eObject\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edependency\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003egetBean\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003efield\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetType\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"k\"\u003etry\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                        \u003c/span\u003e\u003cspan class=\"n\"\u003efield\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eset\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ebean\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edependency\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003ecatch\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eException\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                        \u003c/span\u003e\u003cspan class=\"k\"\u003ethrow\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eRuntimeException\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;依赖注入失败\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//DAO层  \u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@MyComponent\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eUserDao\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003esave\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003euser\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;保存用户: \u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003euser\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// Service层  \u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@MyComponent\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eUserService\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nd\"\u003e@MyAutowired\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eUserDao\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003euserDao\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003ecreateUser\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003euserDao\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esave\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;用户创建完成\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eTest\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003emain\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eargs\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eSimpleIoC\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eioc\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eSimpleIoC\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eioc\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003escan\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;package\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eUserService\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ebean\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eioc\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetBean\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eUserService\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eclass\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003ebean\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ecreateUser\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;dong\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003c/details\u003e\n\u003cp\u003e参考链接：\u003c/p\u003e","title":"手写简单IoC容器"},{"content":"第二次碰到这道题了，又学了两种解题方法，记录一下。\n题目介绍 设计一个支持 push ，pop ，top 操作，并能在常数时间内检索到最小元素的栈。\n实现 MinStack 类:\nMinStack() 初始化堆栈对象。 void push(int val) 将元素val推入堆栈。 void pop() 删除堆栈顶部的元素。 int top() 获取堆栈顶部的元素。 int getMin() 获取堆栈中的最小元素。 解法一：使用辅助栈 这个也是最简单的，题目没有空间限制，考虑使用两个栈，主栈正常记录元素进出，辅助栈记录每个元素对应最小值的进出，辅助栈的栈顶保持为主栈所有元素的最小值。\n当元素入栈时，比较该元素和辅助栈的栈顶，如果该元素更小，那么将其推入主栈的同时也将其推入辅助栈。\n当元素出栈时，如果主栈栈顶元素值和辅助栈的栈顶元素值相等。那么将该元素从两个栈里同时弹出，否则正常弹出主栈栈顶元素。\nclass MinStack { Deque\u0026lt;Integer\u0026gt; stack1; Deque\u0026lt;Integer\u0026gt; stack2; public MinStack() { stack1 = new ArrayDeque\u0026lt;\u0026gt;(); stack2 = new ArrayDeque\u0026lt;\u0026gt;(); } public void push(int val) { stack1.push(val); if (stack2.isEmpty() || val \u0026lt;= stack2.peek()) { stack2.push(val); } } public void pop() { int num = stack1.pop(); if (num == stack2.peek()) stack2.pop(); } public int top() { return stack1.peek(); } public int getMin() { return stack2.peek(); } } 解法二：使用自定义链表 评论区有评论说面试官要求使用栈以外的数据结构来设计，所以找到这种方法。\n每个节点存储三个值：该节点的值，整个链表的最小值，指向下一个节点的指针。我设计的是头插法，head 指向表头，也可以看作栈顶。\nclass Node { int val; int minVal; Node next; public Node(int val, int minVal) { this.val = val; this.minVal = minVal; } public Node(int val, int minVal, Node next) { this.val = val; this.minVal = minVal; this.next = next; } } class MinStack { Node head; public MinStack() { } public void push(int val) { if (head == null) { head = new Node(val, val); } else { head = new Node(val, Math.min(val, head.minVal), head); } } public void pop() { head = head.next; } public int top() { return head.val; } public int getMin() { return head.minVal; } } 解法三：使用O(1)的辅助空间 如果只是存储节点值使用 int 即可，而中间要计算差值，考虑到溢出的可能性，所以使用 long 类型来存储。注意 long 和 int 之间的转换。\nclass MinStack { Deque\u0026lt;Long\u0026gt; stack = new ArrayDeque\u0026lt;\u0026gt;(); long min; public MinStack() { } public void push(int val) { if (stack.isEmpty()) { stack.push(0L); min = val; } else { long diff = val - min;//栈里存储的是差值 stack.push(diff); if (diff \u0026lt; 0) min = val;//差值小于0，说明要更新min } } public void pop() { Long pop = stack.pop(); if (pop \u0026lt; 0) min -= pop; //如果pop小于0.说明当时推入栈时val\u0026lt;min，min被更新了。 //而现在这个值要弹出，min也要恢复之前较大的状态。 } public int top() { Long peek = stack.peek(); //如果peek小于0，说明栈顶就是最小值 if (peek \u0026lt; 0) return (int)min; //否则计算正常值，因为栈中存储的是偏移量 return (int)min + peek.intValue(); } public int getMin() { return (int)min; } } ","permalink":"https://blog.withdong02.top/posts/leetcode155-%E6%9C%80%E5%B0%8F%E6%A0%88/","summary":"\u003cp\u003e第二次碰到这道题了，又学了两种解题方法，记录一下。\u003c/p\u003e\n\u003ch3 id=\"题目介绍\"\u003e题目介绍\u003c/h3\u003e\n\u003cp\u003e设计一个支持 \u003ccode\u003epush\u003c/code\u003e ，\u003ccode\u003epop\u003c/code\u003e ，\u003ccode\u003etop\u003c/code\u003e 操作，并能在常数时间内检索到最小元素的栈。\u003c/p\u003e\n\u003cp\u003e实现 \u003ccode\u003eMinStack\u003c/code\u003e 类:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003eMinStack()\u003c/code\u003e 初始化堆栈对象。\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003evoid push(int val)\u003c/code\u003e 将元素val推入堆栈。\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003evoid pop()\u003c/code\u003e 删除堆栈顶部的元素。\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eint top()\u003c/code\u003e 获取堆栈顶部的元素。\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eint getMin()\u003c/code\u003e 获取堆栈中的最小元素。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"解法一使用辅助栈\"\u003e解法一：使用辅助栈\u003c/h3\u003e\n\u003cp\u003e这个也是最简单的，题目没有空间限制，考虑使用两个栈，主栈正常记录元素进出，辅助栈记录每个元素对应最小值的进出，辅助栈的栈顶保持为主栈所有元素的最小值。\u003c/p\u003e","title":"LeetCode155.最小栈"},{"content":"一切源于一道题目：8. 字符串转换整数 (atoi) - 力扣（LeetCode）\n考虑这样一个问题：给你一个数字字符串，如何在32位环境下安全的处理可能超过[-2^31, 2^31 - 1]范围的数字，不能使用64位变量临时存储。\n因为我熟悉Java， 所以下面提到的数据类型都为 Java 中的数据类型。\nInteger.MAX_VALUE = 2^31 - 1 = 2147483647; Integer.MIN_VALUE = -2^31 = -2147483648; 首先题目说不能使用64位变量，所以 Long 类型不能使用，那就一步一步累加，具体思路就是设置两个变量 digit, res，从头到尾遍历字符串每一位，同时计算 res = res * 10 + digit。但这样的话，累加过程中 res 可能会无征兆直接溢出，程序直接抛异常。那怎么办，介绍一下从 K神题解 里学来的方法。\n整体思路也是一步一步累加，但提前预判溢出。\n这里有个核心变量 bndry = 2^31 / 10 = 214748364，，它是32位有符号整数最大值去掉最后一位的结果。\n在执行 res = res * 10 + digit 之前，先检查 res 与 bndry 的关系，有下面两种溢出的可能。\n情况一：res \u0026gt; bndry ，意味着 res 乘10后，即使加上最小的数字0，结果也会超过 2147483647，溢出。 情况二：res == bndry，此时是否溢出取决于要累加的数字 digit。 如果 digit \u0026lt;= 7，此时正数负数都不会溢出，但等于七对正数来说已经达到最大值。 如果 digit = 8，拼接后值为 2147483648，比 Integer.MAX_VALUE 大 1，而负数即 -2147483648 还未溢出。 如果 digit \u0026gt; 8，正数负数都溢出。 思路还是很清晰的，在下一位拼接前进行判断可以很好的避开溢出问题。下面看一下题解代码\nclass Solution { public int myAtoi(String s) { char[] chars = s.trim().toCharArray(); if (chars.length == 0) return 0; int res = 0; int bndry = Integer.MAX_VALUE / 10; int i = 1, sign = 1; if (chars[0] == \u0026#39;-\u0026#39;) { sign = -1; } else if (chars[0] != \u0026#39;+\u0026#39;) { i = 0; } for (int j = i; j \u0026lt; chars.length; j++) { if (chars[j] \u0026lt; \u0026#39;0\u0026#39; || chars[j] \u0026gt; \u0026#39;9\u0026#39;) break; if (res \u0026gt; bndry || (res == bndry \u0026amp;\u0026amp; chars[j] \u0026gt; \u0026#39;7\u0026#39;)) { return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE; } res = res * 10 + chars[j] - \u0026#39;0\u0026#39;; } return sign * res; } } 判断条件 chars[j] \u0026gt; '7' 对正数和负数同样有效。当 digit = 8 时，正数已溢出，返回 Integer.MAX_VALUE；负数虽然未溢出，但此时返回 Integer.MIN_VALUE 恰好就是正确结果。\n其实除了下面的溢出判断，K神对首字符的处理也很妙，大佬不愧是大佬。\n","permalink":"https://blog.withdong02.top/posts/leetcode8.%E5%AD%97%E7%AC%A6%E4%B8%B2%E8%BD%AC%E6%8D%A2%E6%95%B4%E6%95%B0/","summary":"\u003cp\u003e一切源于一道题目：\u003ca href=\"https://leetcode.cn/problems/string-to-integer-atoi/description/\"\u003e8. 字符串转换整数 (atoi) - 力扣（LeetCode）\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e考虑这样一个问题：给你一个数字字符串，如何在32位环境下安全的处理可能超过\u003ccode\u003e[-2^31, 2^31 - 1]\u003c/code\u003e范围的数字，不能使用64位变量临时存储。\u003c/p\u003e","title":"LeetCode8.字符串转换整数"},{"content":"先附上二哥网站上关于集合框架的结构图\n版本为JDK21\nArrayList 扩容机制 先介绍一下 ArrayList 中的关键变量：\ntransient Object[] elementData 底层用来存储元素的数组 private int size; 表示集合中元素的实际数量 private static final int DEFAULT_CAPACITY = 10; 默认初始容量 private static final Object[] EMPTY_ELEMENTDATA = {}; 当用户调用 new ArrayList(0) 时，elementData 会引用该数组。 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; 当用户调用默认构造参数时会引用该数组。 private Object[] grow(int minCapacity) { int oldCapacity = elementData.length; if (oldCapacity \u0026gt; 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { int newCapacity = ArraysSupport.newLength(oldCapacity, minCapacity - oldCapacity, /* minimum growth */ oldCapacity \u0026gt;\u0026gt; 1 /* preferred growth */); return elementData = Arrays.copyOf(elementData, newCapacity); } else { return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)]; } } private Object[] grow() { return grow(size + 1); } 上面是有关扩容的源代码。\n添加元素（比如调用 add(E e) 方法）时会进行判断 if (s == elementData.length) elementData = grow() 如果当前元素数量（size）等于数组容量（elementData.length），则会触发扩容机制。\n在无参 grow 方法计算 size + 1，也就是有参 grow 函数的参数 minCapacity（所需最小容量）。\n当数组通过默认构造实现，且从未添加元素（即 oldCapacity == 0 \u0026amp;\u0026amp; elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA）时，会进入 else 分支，创建一个新数组，数组长度为10。\n如果进入 if 分支，会计算新容量，newLength 方法如下\npublic static int newLength(int oldLength, int minGrowth, int prefGrowth) { // preconditions not checked because of inlining // assert oldLength \u0026gt;= 0 // assert minGrowth \u0026gt; 0 int prefLength = oldLength + Math.max(minGrowth, prefGrowth); // might overflow if (0 \u0026lt; prefLength \u0026amp;\u0026amp; prefLength \u0026lt;= SOFT_MAX_ARRAY_LENGTH) { return prefLength; } else { // put code cold in a separate method return hugeLength(oldLength, minGrowth); } } 这段代码会比较 minGrowth 和 prefGrowth，一个表示最小增长量，一个表示首选增长量，在大多数情况下后者更大，所以一般情况下会说1.5倍扩容。计算得到 prefLength ，如果该值超过了最大容量限制，则调用 hugeLength 方法，这里不再描述。\n得到 newCapacity 后，创建新数组并拷贝原数组数据，返回新数组，扩容结束。\n有时创建集合时，可以在构造函数中指定数组大小，避免频繁的扩容。\nHashMap 的 put 方法 public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } 第一步，在 put 方法里先将 key 传入 hash 函数（在该函数里，获得 key 的哈希值后进一步对其位运算和异或运算，扰乱哈希值，减少哈希冲突），然后将得到的值与其他值传入 putVal 函数。\nif ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; if ((p = tab[i = (n - 1) \u0026amp; hash]) == null) tab[i] = newNode(hash, key, value, null); 第二步，如果数组为空，进行第一次数组扩容。同时计算索引位置，如果当前位置为空，则直接将键值对插入该位置。这里有个小技巧，当 n 是2的幂次方时 hash \u0026amp; (n - 1) 在效果上和 hash % n 是一样的。\nelse { Node\u0026lt;K,V\u0026gt; e; K k; if (p.hash == hash \u0026amp;\u0026amp; ((k = p.key) == key || (key != null \u0026amp;\u0026amp; key.equals(k)))) e = p; else if (p instanceof TreeNode) e = ((TreeNode\u0026lt;K,V\u0026gt;)p).putTreeVal(this, tab, hash, key, value); else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount \u0026gt;= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash \u0026amp;\u0026amp; ((k = e.key) == key || (key != null \u0026amp;\u0026amp; key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size \u0026gt; threshold) resize(); afterNodeInsertion(evict); return null; 第三步，如果当前位置不为空，进入 else 分支。此时 p 指向当前桶位 tab[i] 的第一个节点。\n全方位比较传入对象和变量 p 的属性：先比较哈希值，如果 hash 值不同，key 肯定不同。然后比较引用或值，一个判断内存地址是否相同，一个判断内容是否相同。如果条件都满足，证明找到了完全相同的 key ，将变量 e 指向 p。\n如果进入 elseif 分支，会判断是否为红黑树节点，如果第一个节点不匹配，且该节点为 TreeNode 类型，说明该桶已树化，调用其他方法将新节点插入到红黑树中，在该方法中查找 key，找到则返回对应节点，找不到返回 null。\n如果进入 else 分支，就是循环遍历链表，如果遍历到尾部也没找到，在尾部插入新节点，并判断是否需要树化。如果中途找到，则停止遍历。变量 e 指向找到的节点。\n到这里 e 会有两种情况，如果 e != null，说明链表或红黑树中找到了已存在的 key，用新 value 覆盖旧 value，然后返回旧值。如果 e == null，说明没有找到，并成功在链表或红黑树中插入了新节点。\n最后，根据 size 大小判断是否需要再次扩容。\n整体思路是先查后改，查完之后变量 e 指向了需要更新的旧节点。\nHashMap 传参初始化 先说结论，如果传的是 17，HashMap 会将容量调整到大于等于 17 的最小的 2 的幂次方，如果不传参默认值是 16。\npublic HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity \u0026lt; 0) throw new IllegalArgumentException(\u0026#34;Illegal initial capacity: \u0026#34; + initialCapacity); if (initialCapacity \u0026gt; MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor \u0026lt;= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException(\u0026#34;Illegal load factor: \u0026#34; + loadFactor); this.loadFactor = loadFactor; this.threshold = tableSizeFor(initialCapacity); } static final int tableSizeFor(int cap) { int n = -1 \u0026gt;\u0026gt;\u0026gt; Integer.numberOfLeadingZeros(cap - 1); return (n \u0026lt; 0) ? 1 : (n \u0026gt;= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; } 相关代码如图，函数调用从上到下，最关键的是 tableSizeFor 方法。\n第一步先将 cap 减一，这是为了处理 cap 本身就是 2 的幂次方的情况。\n第二步进入 numberOfLeadingZeros 方法，该方法返回从最高位开始连续的 0 的个数，比如传入 15，返回 28（二进制表示共 32 位）。\n第三步将 -1 无符号右移前导零的个数。-1 的二进制表示位 32 个 1（补码），无符号右移高位补 0。\n第四步进行三元运算符判断。如果右移 32 位的话，先对 32 取模，比如 -1 右移 32 位还是 -1，因为 32 取模后为 0，相当于没有右移。\n举个例子，当 cap 为 18 时，二进制表示为0000 0000 0000 0000 0000 0000 0001 0010，减一后为0000 0000 0000 0000 0000 0000 0001 0001，前导 0 有27个，所以将 -1 的二进制表示右移 27 个，得到0000 0000 0000 0000 0000 0000 0001 1111，十进制表示为 31，最后加 1 为 32。\npublic static int numberOfLeadingZeros(int i) { // HD, Count leading 0\u0026#39;s if (i \u0026lt;= 0) return i == 0 ? 32 : 0; int n = 31; if (i \u0026gt;= 1 \u0026lt;\u0026lt; 16) { n -= 16; i \u0026gt;\u0026gt;\u0026gt;= 16; } if (i \u0026gt;= 1 \u0026lt;\u0026lt; 8) { n -= 8; i \u0026gt;\u0026gt;\u0026gt;= 8; } if (i \u0026gt;= 1 \u0026lt;\u0026lt; 4) { n -= 4; i \u0026gt;\u0026gt;\u0026gt;= 4; } if (i \u0026gt;= 1 \u0026lt;\u0026lt; 2) { n -= 2; i \u0026gt;\u0026gt;\u0026gt;= 2; } return n - (i \u0026gt;\u0026gt;\u0026gt; 1); } 接下来解释这段神奇的源码：把负数和 0 的情况排除后，先假设最高位在 31 位，如果 i \u0026gt;= 1 \u0026lt;\u0026lt; 16，意思就是 i 大于等于 2 的 16 次方，说明最高位 1 一定在前 16 位中，这时 n 减 16，i 右移 16 位，这个操作相当于把 i 的低十六位丢弃，因为都是 0，然后将高 16 位移到低 16 位处理，如果 i 不大于等于 2 的 16 次方，就说明最高位 1 在后十六位，不做处理。接下来处理类似，在剩下的 16 位中，如果高 8 位满足条件，说明最高位 1 在这高 8 位中。n 减去 8，i 右移 8 位。\n处理到最后， i 的值最多剩下 2 位，分别是 00 01 10 11，n - (i \u0026gt;\u0026gt;\u0026gt; 1) 实际上是在判断剩下 2 位中最高位 1 的位置。整体思想类似二分查找。\n这个方法上有 @IntrinsicCandidate 注解，表明会使用专门的 CPU 指令来代替这段代码，从而获得极高的执行速度。\nHashMap resize 扩容方法 常见的执行 resize 方法的有两种情况：第一次 put 时的初始化扩容；元素数量超过阈值时的扩容。\n先贴一下完整的方法\nJava · 73 行 · 点击展开 final Node\u0026lt;K,V\u0026gt;[] resize() { Node\u0026lt;K,V\u0026gt;[] oldTab = table; int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0; if (oldCap \u0026gt; 0) { if (oldCap \u0026gt;= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } else if ((newCap = oldCap \u0026lt;\u0026lt; 1) \u0026lt; MAXIMUM_CAPACITY \u0026amp;\u0026amp; oldCap \u0026gt;= DEFAULT_INITIAL_CAPACITY) newThr = oldThr \u0026lt;\u0026lt; 1; // double threshold } else if (oldThr \u0026gt; 0) // initial capacity was placed in threshold newCap = oldThr; else { // zero initial threshold signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap \u0026lt; MAXIMUM_CAPACITY \u0026amp;\u0026amp; ft \u0026lt; (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } threshold = newThr; @SuppressWarnings({\u0026#34;rawtypes\u0026#34;,\u0026#34;unchecked\u0026#34;}) Node\u0026lt;K,V\u0026gt;[] newTab = (Node\u0026lt;K,V\u0026gt;[])new Node[newCap]; table = newTab; if (oldTab != null) { for (int j = 0; j \u0026lt; oldCap; ++j) { Node\u0026lt;K,V\u0026gt; e; if ((e = oldTab[j]) != null) { oldTab[j] = null; if (e.next == null) newTab[e.hash \u0026amp; (newCap - 1)] = e; else if (e instanceof TreeNode) ((TreeNode\u0026lt;K,V\u0026gt;)e).split(this, newTab, j, oldCap); else { // preserve order Node\u0026lt;K,V\u0026gt; loHead = null, loTail = null; Node\u0026lt;K,V\u0026gt; hiHead = null, hiTail = null; Node\u0026lt;K,V\u0026gt; next; do { next = e.next; if ((e.hash \u0026amp; oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; } 先说前面的条件判断：\n第一种情况：正常扩容。此时 oldCap \u0026gt; 0，说明是因为元素过多触发了扩容。如果容量已达上限，将阈值设为 Integer.MAX_VALUE，这样就不会触发扩容了，直接返回旧数组；否则进入 elseif 分支（其实这个分支的条件我不理解），容量和阈值都翻倍。\n第二种情况：带参构造初始化，此时 oldCap == 0 \u0026amp;\u0026amp; oldThr \u0026gt; 0，初始容量设置为旧的 threshold。从上面的构造器源码可以看出，如果带参构造，loadFactor 和 threshold 将被赋值，所以 oldThr \u0026gt; 0。\n第三种情况：无参构造，这种情况下指定容量为 16，阈值为 12（16 * 0.75）。\n然后补充计算一下新阈值，因为如果是第二种情况，newThr 仍然为 0。\n接下来是 resize 的后半部分。\n首先更新属性 threshold，创建新数组并赋值给 table，接下来迭代遍历旧 table。注意 oldTab[j] = null，为了方便回收器回收。\n第一种情况，该桶只有一个节点，根据公式计算新索引位置，公式在 put 方法里提到过\n第二种情况，该位置是红黑树节点，调用 split 方法重新分配。\n第三种情况，排除前两种后，该位置只可能是链表，使用特殊处理方法。\n对链表的处理比较复杂，也是 jdk8 优化的地方，jdk7 会重新计算每个元素的哈希值，而现在可以通过位运算直接判断位置。核心就是根据条件构建两条链表，根据 e.hash \u0026amp; oldCap 的值，如果等于 0，将节点接入低位链（low），否则接入高位链（high）。链表遍历结束后将低位链放在原索引位置：newTab[j] = loHead; ，高位链移动到原索引 + oldCap 的位置上：newTab[j + oldCap] = hiHead。\n推导一下涉及到的公式 (e.hash \u0026amp; oldCap) == 0：\n明确元素在表中的位置由hash \u0026amp; (n-1)决定。那么 e.hash \u0026amp; oldCap 实际上是在检查哈希值中对应 oldCap 那个 1 的位是否为1：如果为 1，说明扩容后位置会移动 oldCap 的长度，也就对应了该公式：newTab[j + oldCap] = hiHead；如果为 0，说明扩容后位置不变。\n举一个具体的例子，全部用二进制表示，所描述的第几位是从低位开始数。\n假设 oldCap = 10000, newCap = 100000。\n如果元素 e 的 hash 值为 000101，第五位为 0 ，说明扩容后索引位置不变，经过验证 e.hash \u0026amp; oldCap == e.hash \u0026amp; newCap。\n如果元素 e 的 hash 值为 010101，第五位为 1 ，说明扩容后索引位置增加 oldCap，经过验证 (e.hash \u0026amp; oldCap) + oldCap == e.hash \u0026amp; newCap。\n妙！\n","permalink":"https://blog.withdong02.top/posts/java%E9%9B%86%E5%90%88%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB/","summary":"\u003cp\u003e先附上二哥网站上关于集合框架的结构图\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"image-20260126165155214\" decoding=\"async\" height=\"585\" loading=\"lazy\" src=\"https://dongimagehost-1356670526.cos.ap-nanjing.myqcloud.com/2025/07/image-20260126165155214.png\" width=\"1269\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e版本为JDK21\u003c/strong\u003e\u003c/p\u003e\n\u003ch2 id=\"arraylist-扩容机制\"\u003eArrayList 扩容机制\u003c/h2\u003e\n\u003cp\u003e先介绍一下 ArrayList 中的关键变量：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003etransient Object[] elementData\u003c/code\u003e 底层用来存储元素的数组\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eprivate int size;\u003c/code\u003e 表示集合中元素的实际数量\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eprivate static final int DEFAULT_CAPACITY = 10;\u003c/code\u003e 默认初始容量\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eprivate static final Object[] EMPTY_ELEMENTDATA = {};\u003c/code\u003e 当用户调用 \u003ccode\u003enew ArrayList(0)\u003c/code\u003e 时，elementData 会引用该数组。\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eprivate static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};\u003c/code\u003e 当用户调用默认构造参数时会引用该数组。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-Java\" data-lang=\"Java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eObject\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003egrow\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eminCapacity\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eoldCapacity\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eelementData\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003elength\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eoldCapacity\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e||\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eelementData\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e!=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eDEFAULTCAPACITY_EMPTY_ELEMENTDATA\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enewCapacity\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eArraysSupport\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003enewLength\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eoldCapacity\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                                                  \u003c/span\u003e\u003cspan class=\"n\"\u003eminCapacity\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eoldCapacity\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"cm\"\u003e/* minimum growth */\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                                                  \u003c/span\u003e\u003cspan class=\"n\"\u003eoldCapacity\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"w\"\u003e           \u003c/span\u003e\u003cspan class=\"cm\"\u003e/* preferred growth */\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eelementData\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eArrays\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ecopyOf\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eelementData\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enewCapacity\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eelse\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eelementData\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eObject\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eMath\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003emax\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eDEFAULT_CAPACITY\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eminCapacity\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eObject\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003egrow\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003egrow\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esize\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003cp\u003e上面是有关扩容的源代码。\u003c/p\u003e","title":"Java集合源码阅读"},{"content":"整理一下MySQL锁的八股，全文整理自二哥博客。\n在MySQL数据库中，锁是用来协调多个进程或线程并发访问同一资源的机制。锁不仅保证了数据的一致性和有效性，而且是影响数据库并发访问性能的一个重要因素。\n共享锁与排他锁 共享锁也叫 S(shared) 锁，允许多个事务进行读操作，阻塞写操作。\n排他锁也叫 X(exclusive) 锁，只允许一个事务进行读写操作，阻塞其他事务的读写操作。\n兼容性如下：\n表锁与行锁 表锁：锁定整个表，资源开销小，加锁快，但并发度低，不会出现死锁，适合查询为主、少量更新的场景（如 MyISAM 引擎）。可以细分为表级S锁、表级X锁。\n行锁：锁定单行或多行，开销大、加锁慢，可能出现死锁，但并发度高（InnoDB 默认支持）。可以细分为记录锁、间隙锁、临键锁，也可以分为共享锁和排他锁（与表级S锁、表级X锁一个意思，前提是行锁）。\n表锁详细版： 表锁常见于 MyISAM 引擎， InnoDB 也可手动加锁，适合读多写少、全表扫描或者表结构变更的场景。\nLOCK TABLES table_name READ; -- 显式加读锁 select... -- 其他会话可读，不可写 UNLOCK TABLES; -- 释放锁 LOCK TABLES table_name WRITE; -- 显式加写锁 INSERT/UPDATE/DELETE table_name; -- 其他会话读写均阻塞 UNLOCK TABLES; MyISAM 在执行 SELECT 时会自动加读锁，执行 INSERT/UPDATE/DELETE 时会加写锁。\n对于 InnoDB 引擎，无索引的 UPDATE/DELETE 可能会导致锁升级为表锁。执行 ALTER TABLE 时会自动加表锁，阻塞所有读写操作。\n行锁详细版： 行锁是 InnoDB 存储引擎中最细粒度的锁，它锁定表中的一行记录，允许其他事务访问表中的其他行。\n底层是通过给索引加锁实现的，这就意味着只有通过索引条件检索数据时，InnoDB 才能使用行级锁，否则会退化为表锁。\n默认情况下，InnoDB 在 REPEATABLE READ 事务隔离级别运行，默认的行锁类型为临键锁。\n如果使用排他锁，注意两个点：第一就是必须在事务中使用，否则锁会立即释放。第二就是使用时必须注意是否命中索引，否则可能退化为表锁。\n行锁又可以细分为记录锁、间隙锁和临键锁三种形式。\n记录锁是行锁最基本的表现形式，当我们使用唯一索引或者主键索引进行等值查询时，MySQL 会为该记录自动添加排他锁，禁止其他事务读取或者修改锁定记录。（很奇怪）\n间隙锁用于在范围查询时锁定记录之间的“间隙”，防止其他事务在该范围内插入新记录。仅在可重复读及以上的隔离级别下生效，主要用于防止幻读。\n临键锁是记录锁和间隙锁的结合体，锁住的是索引记录和索引记录之间的间隙。临键锁的间隙是一个左开右闭区间。间隙锁为左开右开。\nMySQL 默认的行锁类型就是临键锁。当使用唯一索引的等值查询匹配到一条记录时，临键锁会退化成记录锁；如果没有匹配到任何记录，会退化成间隙锁。\n意向锁 意向锁是一种表级锁，表示事务打算对表中的某些行数据加锁，但不会直接锁定数据行本身。\n由 InnoDB 自动管理，当事务需要添加行锁时，会先在表上添加意向锁。这样当要添加表锁的时候，可以通过查看表上的意向锁，快速判断是否有冲突，而无需逐行检查，从而提高加锁效率。\n存在的意义：在没有意向锁的情况下，当事务 A 持有某表的行锁时，如果事务 B 想添加表锁，InnoDB 必须检查表中每一行数据是否被加锁，这种全表扫描的方式效率极低。有了意向锁之后，事务在加行锁前，先在表上加对应的意向锁；其他事务加表锁时，只需检查表上的意向锁，无需逐行检查。\n意向锁之间相互兼容\n乐观锁与悲观锁 悲观锁是一种\u0026quot;先上锁再操作\u0026quot;的保守策略，它假设数据被外界访问时必然会产生冲突，因此在数据处理过程中全程加锁，保证同一时间只有一个线程可以访问数据。MySQL 中的行锁和表锁都是悲观锁。\n乐观锁会假设并发操作不会总发生冲突，属于小概率事件，因此不会在读取数据时加锁，而是在提交更新时才检查数据是否被其他事务修改过。它并不是 MySQL 内置的锁机制，而是通过程序逻辑实现的，常见的实现方式有版本号机制和时间戳机制，对应的向表中添加 version 字段或者 timestamp 字段来实现。\n如何通过悲观锁和乐观锁解决库存问题\n死锁问题 MySQL 的死锁是由于多个事务持有资源并相互等待引起的。我通过 SHOW ENGINE INNODB STATUS 查看死锁信息，定位到是加锁顺序不一致导致的，最后通过调整加锁顺序解决了这个问题。\n补充：\n全局锁就是对整个数据库实例进行加锁，当执行全局锁定操作时，整个数据库将会处于只读状态，所有写操作都会被阻塞，直到全局锁被释放。\n在进行全库备份，或者数据迁移时，可以使用全局锁来保证数据的一致性。\n","permalink":"https://blog.withdong02.top/posts/mysql%E9%94%81%E6%9C%BA%E5%88%B6%E5%85%AB%E8%82%A1%E6%95%B4%E7%90%86/","summary":"\u003cp\u003e整理一下\u003ccode\u003eMySQL\u003c/code\u003e锁的八股，全文整理自\u003ca href=\"https://javabetter.cn/sidebar/sanfene/mysql.html#_53-%F0%9F%8C%9Fmysql-%E4%B8%AD%E6%9C%89%E5%93%AA%E5%87%A0%E7%A7%8D%E9%94%81\"\u003e二哥博客\u003c/a\u003e。\u003c/p\u003e\n\u003cp\u003e在MySQL数据库中，锁是用来协调多个进程或线程并发访问同一资源的机制。锁不仅保证了数据的一致性和有效性，而且是影响数据库并发访问性能的一个重要因素。\u003c/p\u003e","title":"MySQL锁机制八股整理"},{"content":"整理一下MySQL索引的八股。\n索引介绍 索引是数据库中用于加速查询的数据结构，通过对表中某列或多列的值进行排序，可以快速定位所要查找的数据。就像小时候在汉语字典里查字时，会先根据偏旁笔画数找到偏旁，然后根据剩余笔画找到目标字，这比在整个字典中一个一个查找是快很多的。\n索引分类 索引有以下分类\n索引详细介绍 主键索引： 主键索引用于唯一标识表中的每条记录，其值必须唯一且非空。每个表只允许有一个主键索引，一般为表中的自增id。\n创建主键时，数据库会自动生成主键索引。若没有指定主键，MySQL的InnoDB存储引擎会优先选择一个非空的唯一索引作为主键，如果没有符合条件的索引，MySQL会自动生成一个隐藏的聚簇索引。\n唯一索引： 主键索引 = 唯一索引 + 非空，每个表可以有多个唯一索引，同时唯一索引允许值为NULL，这是和主键索引不同的地方。唯一索引强制字段值的唯一性，插入或更新时会触发唯一检查，适用于业务唯一性约束的字段，比如邮箱。在建表定义唯一键时，会自动生成唯一索引。\n键和索引的区别在于键是逻辑概念，而索引是物理实现，在磁盘中有着实际的存储。\n普通索引： 普通索引仅用于加速查询，不会限制字段值的唯一性。\n全文索引 全文索引是MySQL一种优化文本数据检索的特殊类型索引，适用于CHAR, VARCHAR, TEXT等字段。MySQL 5.7 及以上版本内置了 ngram 解析器，可处理中文、日文和韩文等分词。全文索引底层不再是 B+ 树索引。\n建表时通过 FULLTEXT (title, body) 来定义。通过 MATCH(col1, col2) AGAINST('keyword') 进行检索，默认按照降序返回结果，支持布尔模式查询。\n+ 表示必须包含； - 表示排除； * 表示通配符； -- 使用布尔模式查询 SELECT * FROM articles WHERE MATCH(title, content) AGAINST(\u0026#39;+MySQL -Oracle\u0026#39; IN BOOLEAN MODE); 这样查询性能比LIKE '%keyword%'高很多。对于复杂的中文场景可以用Elasticsearch等专业搜索引擎替代。\nB+ 树索引： B+ 树是一种高度平衡的多路查找树，能有效降低磁盘的 IO 次数，并且支持有序遍历和范围查询。\n相比普通二叉树，B+ 树可以将亿级数据量控制在3-4层树高，极大减少磁盘的 I/O 次数。因为树越高意味着查找数据时就需要更多的磁盘 IO，因为每一层都可能需要从磁盘加载新的节点。\n相比二叉平衡树，B+ 树每个节点拥有多个子节点，可存储数据更多。\n相比 B 树：B+ 树的非叶子节点只存储键值，叶子节点存储数据并通过链表连接，支持范围查询。这样的好处是非叶子节点不存储数据，就可以存储更多的键值对；叶子节点构成有序链表（双向链表），范围查询时可以直接通过叶子节点间的指针 顺序访问 整个查询范围内的记录，而无需对树进行多次遍历，查询效率高。\n相比哈希表：B+ 树支持范围查询和排序\n哈希索引： Hash 索引基于哈希函数将键值映射到固定长度的哈希值，通过哈希值定位数据存储的位置，完全无序，只支持等值查找，速度快功能少，常见于 Memory 引擎。\nInnoDB 内部使用了一种名为 “自适应哈希索引” 的技术，当某些索引值频繁访问时，InnoDB 会在 B+ 树基础上自动创建哈希索引，兼具两者的优点。\n聚簇索引与非聚簇索引： 聚簇索引的叶子节点存储了完整的数据行，数据和索引是在一起的，不仅存储了主键值，还存储了其他列的值。\nInnoDB 的主键索引就是聚簇索引，因此按照主键进行查询的速度会非常快。\n每个表只能有一个聚簇索引，通常由主键定义。\n非聚簇索引的叶子节点只包含了索引列和主键值，需要通过回表按照主键去聚簇索引查找其他列的值，唯一索引、普通索引等非主键索引都是非聚簇索引。回表通常需要访问额外的数据页，如果数据不在内存中还需要从磁盘读取，增加 I/O 开销。可通过覆盖索引或者联合索引来避免回表。\n补充 一棵 B+ 树能存多少数据： 一棵 B+ 树能存多少数据，取决于它的分支因子和高度。在 InnoDB 中，页的默认大小为 16KB，当主键为 bigint 时，3 层 B+ 树通常可以存储约 2000 万条数据。\nB+ 树相比 B 树的三个优势 B 树的每个节点既存储键值，又存储数据和指针，导致单节点存储的键值数量较少。\nB 树的范围查询需要通过中序遍历逐层回溯；而 B+ 树的叶子节点通过双向链表顺序连接，范围查询只需定位起始点后顺序遍历链表即可，没有回溯开销。\nB 树的数据可能存储在任意节点，假如目标数据恰好位于根节点或上层节点，查询仅需 1-2 次 I/O；但如果数据位于底层节点，则需多次 I/O，导致查询时间波动较大。\n而 B+ 树的所有数据都存储在叶子节点，查询路径的长度是固定的，时间稳定为 O(logN)(N是数据总量)，对 MySQL 在高并发场景下的稳定性至关重要。\n联合索引介绍： 前面提到可通过覆盖索引或者联合索引来避免回表。如果一个查询只需要访问索引中已经包含的列，那么这个查询就可以被索引“覆盖”，不需要回表，这就是覆盖索引。至于联合索引就是把多个字段放在一个索引里，必须遵循 \u0026ldquo;最左前缀\u0026rdquo; 原则。\n联合索引属于非聚簇索引。与单列索引不同的是，联合索引的每个节点会存储所有索引列的值，而不仅仅是第一列的值。\n关于最左前缀涉及到的一些问题：\n范围查询只能应用于最左前缀的最后一列。范围查询之后的列无法使用索引。\nSQL -- 索引(a,b,c) SELECT * FROM table WHERE a = 1 AND b \u0026gt; 2 AND c = 3; -- 只能使用a和b，c无法使用索引 联合索引在 B+ 树中是按照最左字段优先排序构建的，如果跳过最左字段，MySQL 无法判断查找范围从哪里开始，自然也就无法使用索引。\n如果查询模式是后缀通配符 LIKE 'prefix%'，且该字段有索引，优化器通常会使用索引。否则即便是遵循最左前缀匹配，LIKE 字段也无法命中索引。\n如果排序(order by)或分组(group by)的列是最左前缀的一部分，索引还可以加速操作。\n索引下推： 索引下推是指：MySQL 把 WHERE 条件尽可能“下推”到索引扫描阶段，在存储引擎层提前过滤掉不符合条件的记录。\n在传统的查询处理方式中，存储引擎首先根据索引读取数据并将其加载到内存中，然后在内存中应用 WHERE 子句中的过滤条件，筛选出符合条件的数据行。这种方式可能导致大量的数据传输，尤其是当数据量较大时。\n而使用索引下推优化后，存储引擎在存储层就应用 WHERE 子句中的过滤条件，只有符合条件的数据才会被加载到内存中进一步处理。这可以减少数据传输量，从而提高查询效率。\n其他问题 哪些情况下索引会失效： 对索引列使用函数或表达式会导致索引失效。 LIKE 模糊查询以通配符开头会导致索引失效。 联合索引违反了最左前缀原则，索引会失效。 使用 OR 连接非索引列条件，会导致索引失效。 使用 != 或 \u0026lt;\u0026gt; 不等值查询会导致索引失效。 （隐式类型转换也会导致索引失效） 创建索引有哪些注意点： 选择合适的字段。比如说频繁出现在 WHERE、JOIN、ORDER BY、GROUP BY 中的字段。优先选择区分度高的字段，比如用户 ID、手机号等唯一值多的，而不是性别、状态等区分度极低的字段，如果真的需要，可以考虑联合索引。 要控制索引的数量，避免过度索引，每个索引都要占用存储空间，单表的索引数量不建议超过 5 个。 联合索引的时候要遵循最左前缀原则。区分度高的字段放在左侧，等值查询的字段优先于范围查询的字段。例如 WHERE A=1 AND B\u0026gt;10 AND C=2，优先 (A, C, B)。 全篇整理自二哥博客。\n","permalink":"https://blog.withdong02.top/posts/mysql%E7%B4%A2%E5%BC%95%E5%85%AB%E8%82%A1%E6%95%B4%E7%90%86/","summary":"\u003cp\u003e整理一下\u003ccode\u003eMySQL\u003c/code\u003e索引的八股。\u003c/p\u003e\n\u003ch2 id=\"索引介绍\"\u003e索引介绍\u003c/h2\u003e\n\u003cp\u003e索引是数据库中用于加速查询的数据结构，通过对表中某列或多列的值进行排序，可以快速定位所要查找的数据。就像小时候在汉语字典里查字时，会先根据偏旁笔画数找到偏旁，然后根据剩余笔画找到目标字，这比在整个字典中一个一个查找是快很多的。\u003c/p\u003e","title":"MySQL索引八股整理"},{"content":"泛型定义 先看这样一个例子：\npublic static void main(String[] args) { List list = new ArrayList(); list.add(\u0026#34;aaa\u0026#34;); list.add(\u0026#34;bbb\u0026#34;); list.add(\u0026#34;ccc\u0026#34;); for (int i = 0; i \u0026lt; list.size(); i++) { System.out.println((String)list.get(i)); } } ArrayList集合中可以加入任何类型的对象，我本意是用这个集合来存储字符串（因为ArrayList默认存储的是Object类型的对象，所以输出时需要强转 ），但我粗心的写错了list.add(123)，在我运行后发现报错java.lang.ClassCastException，显然报错原因就是输出时Integer类型并不能强转为String类型。那么如何避免这种情况呢，答案就是泛型。\n借用一下维基百科的定义：\n泛型程序设计是程序设计语言的一种风格或范型，泛型允许程序员在强类型程序设计语言中 编写代码时 使用一些 以后才指定的类型，在实例化时作为参数指明这些类型。\n说一下我的理解，泛字让我联想到了一个词语：泛泛而谈，指那些浮浅平淡，不深入的谈话。这里也可以这样理解，定义时我模糊的说明一下参数，不指明参数具体是哪种类型，等我使用时再说明。\n有这样一种说法：泛型的本质就是“参数化类型”。想象一下，定义一个方法需要形参，待你调用时，又需要传递实参。泛型亦是如此，定义时形式上意思意思，真正使用时再说明。其实都一个意思。\n下面我将从泛型类、泛型方法、泛型接口、通配符、类型擦除五个方面来对Java泛型进行详细说明。\n泛型类 来看一下ArrayList这个类的定义\npublic class ArrayList\u0026lt;E\u0026gt; extends AbstractList\u0026lt;E\u0026gt; implements List\u0026lt;E\u0026gt;, RandomAccess, Cloneable, java.io.Serializable {...} 相比其他普通类，这个类的类名称后面多了个\u0026lt;E\u0026gt;，这就是泛型类的核心标识，其中 E 为类型参数，理论上可以为任何字母，但有一些约定俗成的习惯\nT：代表一般的任何类 E：element 元素的意思 K：代表 key 的意思 V：代表 value 的意思，经常和 K 搭配作为键值对 关于类型参数怎么用，来看个例子。\npublic class Vehicle\u0026lt;T\u0026gt; { private T brand;//属性 public T getBrand() {//方法返回值 return brand; } public void setBrand(T newBrand) {//方法形参 this.brand = newBrand; } } 一共有三种使用方法，代码中已做标注。注意：静态属性和静态方法中不能使用泛型类声明的类型参数。否则编译报错：'org.example.myblog.generics.Vehicle.this' cannot be referenced from a static context\n原因如下：静态成员是属于类本身的，不属于任何实例。当类加载时，泛型参数 T 还没有被具体类型替换（因为此时还没有任何实例被创建），编译器无法确定 T 的具体类型，自然也就无法为静态成员使用 T 分配内存或验证类型。有一个小例外，静态方法自身可以是泛型方法，详情见下文（坑1挖）。\n如果把 T 视为一个真实存在的类型，其实它的使用方法与其他类型并无区别。类型参数也可以是多个，用,分隔。\n泛型接口 泛型接口和泛型类差不多，下面是List接口的定义。\npublic interface List\u0026lt;E\u0026gt; extends SequencedCollection\u0026lt;E\u0026gt; {...} 说几点与泛型类不同的地方\n因为接口中的属性的修饰符默认是public static final，所以属性不能使用类型参数声明。除静态方法外其他方法可以使用类型参数。\n在接口A继承接口B时，如果接口B是泛型接口，那么接口A可以选择确定父接口的类型参数（成为非泛型接口），也可以选择保留泛型参数并传递给父接口（自身仍是泛型接口，如List接口），示例如下\n// 父接口：泛型接口，T 是它的类型参数 interface IUsb\u0026lt;T\u0026gt; { void connect(T device); // 连接设备（设备类型为 T） } // 子接口：保留泛型参数 E，并传递给父接口 IUsb 的 T interface IA\u0026lt;E\u0026gt; extends IUsb\u0026lt;E\u0026gt; { // 此时父接口 IUsb 的 T 被“替换”为子接口的 E // 所以 IA 继承的方法实际是：void connect(E device) } 当一个类实现接口时，可以选择将自身泛型参数传递给接口，成为泛型实现类，也可以指定接口的泛型参数，成为一个普通的实现类。当接口有多个泛型参数时，实现类不允许部分指定（接口继承接口也不允许部分指定）。\n泛型类与泛型接口的类型推断：\n类型参数的确定时机：类型参数在创建实例（泛型类）或声明子类型（泛型接口）时确定。\n不指定类型参数时的类型推断：会退化为“原始类型”，将所有泛型参数视为Object（坑2挖），不会进行类型检查，不推荐这样使用。\n指定类型参数时的类型匹配：所有使用该类型参数的成员需遵循“参数化类型”的约束：\n传入的实参必须是 “指定类型” 或其子类型。\n返回值会被自动视为 “指定类型”（无需强制转换）。\nList\u0026lt;Number\u0026gt; list = new ArrayList\u0026lt;\u0026gt;(); list.add(123); //Integer类型 list.add(1.2);//Double类型 如果有多个类型参数，不允许部分指定（上文提到过）。\n泛型方法 在方法的返回值前加个类型参数就是泛型方法。\n下面这个方法是泛型方法吗？\npublic T getBrand() { return brand; } 当然不是，它仅使用了泛型类定义的类型参数，并没有在方法签名中声明\u0026lt;T\u0026gt;，不能简单的认为泛型类里的方法就是泛型方法。下面的代码则是泛型方法，泛型方法里也可以同时声明多个类型参数。\npublic \u0026lt;T\u0026gt; T test(T t){ return t; } class Test\u0026lt;T\u0026gt; { public T method(T t) { return t; } public \u0026lt;E\u0026gt; T genericsMethod(T t, E e) { return t; } } 看上面的例子，Test是一个泛型类，泛型参数是 T ；method是泛型类里的一个方法，用到了泛型类的泛型参数 T ；genericsMethod是泛型方法，泛型参数是 E，该方法同时用到了两个泛型参数。需要注意的是，这两个泛型参数相互独立，作用域不同，泛型类的泛型参数 T 的作用域是整个类，而泛型方法的泛型参数 E 仅限于这个方法。如果泛型方法里需要同时用到该方法的泛型参数和泛型类的泛型参数，推荐分开命名，提高可读性。（这个例子不太好，方法体里并未使用到泛型参数 E）\n前面提到过，在泛型类里，不允许静态方法使用泛型类声明的类型参数。但可以将静态方法声明为泛型方法。代码如下：（坑1填）\nclass Test\u0026lt;T\u0026gt; { // 泛型类定义的类型参数 T 不能在静态方法中使用 // 但可以将静态方法声明为泛型方法，方法中便可以使用其声明的类型参数了 public static \u0026lt;E\u0026gt; E show(E one) { return null; } } 泛型方法的类型推断：\n确定时机：调用该方法时确定类型参数。\n不指定类型参数时的类型推断：优先通过实参推断；结合返回值类型调整；多类型实参时取共同父类（最小上界）\n// 泛型方法：返回两个参数中较大的一个（假设T是可比较的） public static \u0026lt;T extends Comparable\u0026lt;T\u0026gt;\u0026gt; T max(T a, T b) { return a.compareTo(b) \u0026gt;= 0 ? a : b; } // 泛型方法：创建包含一个元素的列表 public static \u0026lt;T\u0026gt; List\u0026lt;T\u0026gt; createList(T element) { List\u0026lt;T\u0026gt; list = new ArrayList\u0026lt;\u0026gt;(); list.add(element); return list; } // 泛型方法：打印两个参数的类型 public static \u0026lt;T\u0026gt; void printTypes(T a, T b) { System.out.println(a.getClass().getSimpleName()); System.out.println(b.getClass().getSimpleName()); } //********************************************************************** Integer result1 = max(3, 5); //根据实参推断 T = Integer String result2 = max(\u0026#34;apple\u0026#34;, \u0026#34;banana\u0026#34;); //根据实参推断 T = String // 接收变量是 List\u0026lt;Number\u0026gt;，实参是 Integer（Number 的子类） // 推断 T = Number（而非 Integer），因为返回值需要匹配 List\u0026lt;Number\u0026gt; //如果只有createList(123); 那么编译器仅根据实参推断 T = Integer List\u0026lt;Number\u0026gt; numList = createList(123); //实参为Integer和String,最小上界为Object，推断 T = Object printTypes(123, \u0026#34;123\u0026#34;); 指定类型参数时的类型推断：\n// 情况1：推断可能歧义时显式指定 List\u0026lt;Number\u0026gt; list = createList\u0026lt;\u0026gt;(123); // 等价于 createList\u0026lt;Number\u0026gt;(123) // 情况2：实参类型与预期不符时强制指定 List\u0026lt;Object\u0026gt; objList = createList\u0026lt;Object\u0026gt;(\u0026#34;hello\u0026#34;); // 显式指定 T = Object，即使实参是 String（String 是 Object 的子类，合法） 如果泛型方法没有参数或参数不涉及泛型参数，编译器无法通过实参推断，此时需要显式指定类型参数，或通过返回值接收类型推断。\n// 泛型方法：创建一个空列表 public static \u0026lt;T\u0026gt; List\u0026lt;T\u0026gt; createEmptyList() { return new ArrayList\u0026lt;\u0026gt;(); } // 方式1：通过接收变量类型推断 T = String List\u0026lt;String\u0026gt; strList = createEmptyList(); // 方式2：显式指定 T = Integer List\u0026lt;Integer\u0026gt; intList = createEmptyList\u0026lt;Integer\u0026gt;(); 类型擦除 在编译期，编译器会将泛型代码中的泛型参数\u0026lt;T\u0026gt;,\u0026lt;E\u0026gt;等等替换为边界类型（涉及到通配符的使用，下文有），如果没有边界统一替换为Object类型（坑2填）。在运行期，JVM眼中不存在泛型类或泛型方法。\n泛型类/接口的擦除\n//无边界泛型 class Box\u0026lt;T\u0026gt; { private T value; } // 擦除后 → class Box { private Object value; } //有边界泛型 class NumberBox\u0026lt;T extends Number\u0026gt; { private T value; } // 擦除后 → class NumberBox { private Number value; } 泛型方法的擦除\n//有边界和无边界的擦除规则同上 public static \u0026lt;T\u0026gt; T getFirst(List\u0026lt;T\u0026gt; list) { return list.get(0); } // 擦除后 → public static Object getFirst(List list) { return list.get(0); } // 调用时编译器自动补全转换： List\u0026lt;String\u0026gt; list = new ArrayList\u0026lt;\u0026gt;(); String s = (String) getFirst(list); // 手动写代码时无需加，编译器隐式添加 泛型类被继承时，类型擦除可能导致子类方法与父类方法签名不匹配，编译器会自动生成 “桥接方法” 保证多态性，看下面的例子\nclass Parent\u0026lt;T\u0026gt; { public void set(T t) {} } class Child extends Parent\u0026lt;String\u0026gt; { @Override public void set(String s) {} // 子类方法 } // 父类擦除后方法为：public void set(Object t) {},此时父类和子类的该方法形参不一致，不构成重写，但实际上有一个桥接方法 // 编译器为 Child 生成桥接方法： public void set(Object t) { set((String) t); } // 调用子类的 set(String) 泛型是 JDK5 引入的特性。类型擦除的设计是为了兼容 JDK5 之前的非泛型代码。但是类型擦除也带来了一定的影响，比如运行期无法获取泛型参数类型，泛型参数不能是基本类型等\n通配符使用 泛型是类型严格的，虽然String是Object的子类，但List\u0026lt;String\u0026gt;和List\u0026lt;Object\u0026gt;之间没有继承关系。通配符可以在保证类型安全的前提下，允许泛型类型之间的“有限兼容”。\n无界通配符\u0026lt;?\u0026gt;（表示任意类型） 特性：可读。适用于 “只需要读取泛型容器中的元素，不需要关心具体类型” 的场景\npublic static void printList(List\u0026lt;?\u0026gt; list) { for (Object obj : list) { // 只能以 Object 类型读取 System.out.println(obj); } // list.add(\u0026#34;abc\u0026#34;); // 编译报错：无法确定添加的类型是否匹配未知类型 list.add(null); // 唯一例外：null 可以添加，但无意义 } // 调用：可接收任何 List 类型 printList(new ArrayList\u0026lt;String\u0026gt;()); printList(new ArrayList\u0026lt;Integer\u0026gt;()); 上界通配符\u0026lt;? extends T\u0026gt;（表示 T 及其子类） 特性：可读（读取的元素可视为 T 类型，即向上转型）。适用于 “需要读取泛型容器中的元素，且元素类型是 T 的子类型” 的场景\n// 求和 public static double sum(List\u0026lt;? extends Number\u0026gt; numbers) { double total = 0; for (Number num : numbers) { // 读取为 Number 类型（安全） total += num.doubleValue(); } // numbers.add(10); // 编译报错：无法确定添加的 Integer 是否匹配未知子类型（如 Double），即只知道是子类，不确定添加哪个子类 return total; } // 调用：可接收 List\u0026lt;Integer\u0026gt;、List\u0026lt;Double\u0026gt; 等 sum(new ArrayList\u0026lt;Integer\u0026gt;(Arrays.asList(1, 2, 3))); // 6.0 sum(new ArrayList\u0026lt;Double\u0026gt;(Arrays.asList(1.5, 2.5))); // 4.0 下界通配符\u0026lt;? super T\u0026gt;（表示 T 及其父类） 特性：可写（写入 T 及其子类的元素是安全的），但读取受限（读取的元素只能视为 Object 类型）。适用于 “需要向泛型容器中写入元素，且元素类型是 T 的子类型” 的场景（如添加元素到容器）。\n// 向列表中添加整数（列表类型可以是 Integer 的父类：Number、Object 等） public static void addIntegers(List\u0026lt;? super Integer\u0026gt; list) { list.add(10); // 写入 Integer 及其子类（安全，因为父类容器可接收子类对象） list.add(20); // Integer num = list.get(0); // 编译报错：读取的元素只能视为 Object，因为不清楚是哪个父类，而Object是顶级父类，所以可以用Object接受 } // 调用：可接收 List\u0026lt;Integer\u0026gt;、List\u0026lt;Number\u0026gt;、List\u0026lt;Object\u0026gt; addIntegers(new ArrayList\u0026lt;Integer\u0026gt;()); addIntegers(new ArrayList\u0026lt;Number\u0026gt;()); addIntegers(new ArrayList\u0026lt;Object\u0026gt;()); PECS原则 Producer Extends：如果泛型容器是 “生产者”（主要用于读取元素），使用 \u0026lt;? extends T\u0026gt;（如 List\u0026lt;? extends Number\u0026gt; 生产 Number 类型元素）。 Consumer Super：如果泛型容器是 “消费者”（主要用于写入元素），使用 \u0026lt;? super T\u0026gt;（如 List\u0026lt;? super Integer\u0026gt; 消费 Integer 类型元素）。 总结 泛型在集合框架（List，Map，Set）和主流框架（Spring，Mybatis）中的频繁出现证明其重要性，所以学会使用泛型还是很重要的。\n这篇博客先写到这里，感谢豆包。欢迎评论区指正，我也会继续更新，欢迎收藏我的网站。我的眼皮要闭上了😑\n2026年3月7日修改：\n泛型练习请看本站博客：手写IoC容器。\n参考文章\nJava 中的泛型（两万字超全详解）_java 泛型-CSDN博客\n泛型编程 - Wikiwand\nJava 泛型 | 菜鸟教程\n","permalink":"https://blog.withdong02.top/posts/java%E6%B3%9B%E5%9E%8B/","summary":"\u003ch2 id=\"泛型定义\"\u003e泛型定义\u003c/h2\u003e\n\u003cp\u003e先看这样一个例子：\u003c/p\u003e\n\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-Java\" data-lang=\"Java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003emain\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eargs\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\t\u003c/span\u003e\u003cspan class=\"n\"\u003eList\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003elist\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eArrayList\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\t\u003c/span\u003e\u003cspan class=\"n\"\u003elist\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eadd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;aaa\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\t\u003c/span\u003e\u003cspan class=\"n\"\u003elist\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eadd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;bbb\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\t\u003c/span\u003e\u003cspan class=\"n\"\u003elist\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eadd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;ccc\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\t\u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003elist\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esize\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\t\t\u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e((\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"n\"\u003elist\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e));\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\t\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003cp\u003e\u003ccode\u003eArrayList\u003c/code\u003e集合中可以加入任何类型的对象，我本意是用这个集合来存储字符串（因为\u003ccode\u003eArrayList\u003c/code\u003e默认存储的是\u003ccode\u003eObject\u003c/code\u003e类型的对象，所以输出时需要强转 ），但我粗心的写错了\u003ccode\u003elist.add(123)\u003c/code\u003e，在我运行后发现报错\u003ccode\u003ejava.lang.ClassCastException\u003c/code\u003e，显然报错原因就是输出时\u003ccode\u003eInteger\u003c/code\u003e类型并不能强转为\u003ccode\u003eString\u003c/code\u003e类型。那么如何避免这种情况呢，答案就是\u003cstrong\u003e泛型\u003c/strong\u003e。\u003c/p\u003e","title":"Java泛型"},{"content":"介绍 正则表达式（regular expression），常简写为regex，用简单字符串来描述、匹配文中全部匹配指定格式的字符串。人话讲就是根据一些规则制定一个字符串，然后你可以用这个字符串来筛选满足规则的字符串。许多程序设计语言都支持用正则表达式操作字符串，这里主要介绍正则表达式在Java中的运用。\n不同编程语言的正则表达式引擎有所不同，这里提供一个链接，里面详细介绍了不同语言对各种特性的支持程度。\n快速使用 先说明一下\\的使用。\n在Java普通字符串中，反斜杠\\本身就是转义字符，比如\\n被转义为\u0026quot;换行符\u0026quot;，又比如\\\\被转义为\\。而正则表达式也有自己的语法，它也使用反斜杠作为转义字符，比如\\d表示“匹配一个数字”。\n那么二者结合起来呢🧐。以\u0026quot;\\\\d\u0026quot;为例。编译器看到字符串\u0026quot;\\\\d\u0026quot;会根据字符串规则将其转换为两个字符，一个\\，一个d。接下来正则表达式引擎会对其进行解析，最终生效的正则模式就是\\d。可以这样理解：正则表达式需要 \\d 来匹配数字。但在Java字符串里，一个 \\ 需要写成 \\\\。所以，要把正则的 \\d 放到Java字符串里，就变成了 \\\\d。\n到底需不需要两个\\\\，idea会给你答案。\njava.util.regex包是Java标准库中用于支持正则表达式操作的包，主要涉及到Pattern和Matcher这两个类的操作。这里有个简单的例子：\nString pattern = \u0026#34;java\\\\d\u0026#34;; String text1 = \u0026#34;java1\u0026#34;; String text2 = \u0026#34;javaBad\u0026#34;; Pattern p = Pattern.compile(pattern); Matcher m = p.matcher(text1); System.out.println(m.matches());//true m = p.matcher(text2); System.out.println(m.matches());//false 先调用Pattern类的静态方法compile（参数为正则表达式）生成一个实例对象，通过调用该对象的matcher方法（参数为待匹配文本）生成一个Matcher实例。接下来就有很多方法供你选择，这里我调用的是matches方法来输出布尔值，在例子中体现为字符串Java后面能否匹配上数字。Matcher类里还有个find方法也很常见，下文会提到。\n匹配规则详解 简单匹配 为方便演示，接下来的示例代码使用String类的matches方法，该方法底层原理仍然是Pattern和Matcher这两个类的使用，后面有详细说明。下面示例参考廖雪峰和菜鸟教程。\n匹配任意字符：.可以匹配除\\r\\n之外的任何单个字符。如a.c可以匹配abc但不能匹配abbc和ac\n匹配数字：\\d匹配 0~9 的数字，同样只匹配一个字符。匹配非数字：\\D匹配非数字。\n匹配常用字符：\\w可以匹配一个字母、数字或下划线\n匹配空格字符：\\s可以匹配任何空白字符，包括空格、制表符、换页符等。与[\\f\\n\\r\\t\\v]等效。\\W和\\S和\\D同样是反着来的。\n重复匹配：\n*可以匹配任意个字符，包括0个字符。\n+可以匹配至少一个字符。比如A\\d+可以匹配A11111和A0。但不能匹配A,因为至少一个字符。\n?可以匹配0个或一个字符。\n如果想精确指定n个字符，使用{n}，比如A\\d{3}可以匹配到A123。指定匹配n~m个字符，用{n,m}， 例如A\\d{3,5}可以精确匹配A123 A1234 A12345。{n,}表示可以匹配至少n个字符。m和n为非负整数，其中n \u0026lt;= m。再举一个例子:o{2}和Bob中的一个o不匹配，而匹配food中的两个o。不同表达式可能是等效的，比如o{0,1}和o?\n来个综合点的例子：假如电话号码规则如下:34位数字表示区位，78位数字表示电话，中间用-连接。答案：\\\\d{3,4}-\\\\d{7,8}。对于连字符-，一般情况下只是一个普通字符，不需要进行转义，当然写上两个反斜杠也是对的，idea会给出提示移除多余的反斜杠。\nString pattern = \u0026#34;\\\\d{3,4}-\\\\d{7,8}\u0026#34;;//不知道需不需要写\\？idea会给你的答案 String text1 = \u0026#34;0123-123456\u0026#34;; String text2 = \u0026#34;010-1234567\u0026#34;; System.out.println(text1.matches(pattern));//false System.out.println(text2.matches(pattern));//true 复杂匹配： 匹配开头和结尾：^匹配输入字符串开始的位置，$匹配输入字符串结束的位置。他们俩的作用是将匹配过程限制在整个字符串上，避免了在子串中成功匹配的情况。其实matches()方法的行为已经隐含了^...$锚点的效果，而find()方法则没有。matches方法尝试将整个输入序列与模式匹配，而find方法会在输入序列中查找下一个与模式匹配的子序列。仔细品味这两个方法的名字，你也许会理解。\n匹配指定范围：[xyz]匹配包含的任一字符，比如[abc]匹配plain中的 a。[a-z]\n匹配 a 到 z 范围内的任何小写字母。[^a-z]匹配任何不在 a 到 z 范围内的字符，取补集的意思，和前面的\\b，\\B类似。[0-9]和[A-Z]同样理解。\n如果要匹配6位十六进制数，可以这样写：[0-9a-fA-F]{6}\n或规则匹配：AB|CD表示可以匹配 AB 或 CD。(z|f)ood匹配 zood 或 food，当然这个正则表达式也可以写成[zf]ood。\n分组匹配： 字面意思，通过()将表达式分组处理，可以配合Matcher类的group方法使用。\nPattern p = Pattern.compile(\u0026#34;(\\\\d{3,4})\\\\-(\\\\d{7,8})\u0026#34;); Matcher m = p.matcher(\u0026#34;010-12345678\u0026#34;); if (m.matches()) { String g1 = m.group(1); String g2 = m.group(2); System.out.println(g1); // 010 System.out.println(g2); // 12345678 } 非贪婪匹配：\nPattern pattern = Pattern.compile(\u0026#34;(\\\\d+)(0*)\u0026#34;); Matcher matcher = pattern.matcher(\u0026#34;1230000\u0026#34;); if (matcher.matches()) { System.out.println(\u0026#34;group1=\u0026#34; + matcher.group(1)); // \u0026#34;1230000\u0026#34; System.out.println(\u0026#34;group2=\u0026#34; + matcher.group(2)); // \u0026#34;\u0026#34; } 观察这个例子，第二个输出空字符串，输出没问题，因为\\d+可以匹配到后面的数字。正则表达式默认使用贪婪匹配，以\\d+为例，后面有多少数字就匹配多少（只要连续），这样0*就匹配到空字符串。如果想让\\d+少匹配，可以写成(\\\\d+?)(0*)，?表示非贪婪匹配，这样输出就变成了\u0026quot;123\u0026quot; \u0026quot;0000\u0026quot;。\n不能简单的把非贪婪匹配认为最少匹配，觉得输出应该是1和230000。非贪婪匹配是在保证后面表达式都能匹配上的前提下尽量少匹配。引擎保证的是整体成功优先，我认为可以是一种平衡吧，这里不做过多解释，因为更深层的原理我也不懂。\n这里的?和前面提到的?不一样。(\\d??)(9*)，\\d?表示匹配0个或1个数字，后面的?表示非贪婪匹配。如果给定字符串\u0026quot;9999\u0026quot;，匹配到的两个子串分别为\u0026quot;\u0026quot; \u0026quot;9999\u0026quot;\n实战演练 **匹配邮箱：**这里假设字符@前可以出现数字、英文字母、下划线和中划线，字符@后是域名格式，长度不限。\n先分析邮箱名称部分，只能出现数字、英文字母和下划线、中划线，那么可以这样写[0-9a-zA-Z_-]，也可以选择\\w，这里我选择第二种。又因为不止一个字符，所以加个+。变成\\w+\n然后分析域名部分，域名一般是weixin.qq.com这种类型，也就是**.**.**。可以以第一个英文句点为分界线将其拆解为两部分。一部分是**一部分是.**的复制粘贴。第一部分依然可以这样写\\w+，一个.**这样写\\.\\w+，多个.**这样写(.\\w+)+。\n经过分析，答案就是\\w+@\\w+(.\\w+)+，放到java中，需要写成\\\\w+@\\\\w+(.\\\\w+)+，还是那句话：到底需不需要写反斜杠，idea会给你答案。\n如果你在浏览器搜索“正则表达式邮箱匹配”，你可能会得到很多答案，当你不确定时，多去尝试。\n拓展 Matcher类除了提到的matches方法和find方法，还有start``replaceAll等，可以点击链接了解更多。这里我想介绍一下Pattern和Matcher这两个类出现的其他地方，比如上文提到的String.matches()方法。\n为什么说String.matches()方法底层原理仍然是Pattern和Matcher这两个类的使用。ctrl+鼠标左键点开方法源码即可发现：\npublic boolean matches(String regex) { return Pattern.matches(regex, this); } 发现调用的是Pattern类的静态方法matches，同时传进去两个参数，一个正则表达式，一个待匹配字符串。接着点进去matches方法\npublic static boolean matches(String regex, CharSequence input) { Pattern p = Pattern.compile(regex); Matcher m = p.matcher(input); return m.matches(); } 然后就会发现这跟之前的示例代码没啥两样，只是compile和matcher这两个方法的参数不再是字符串常量，而是传进来的参数regex和input。\n再说一个例子，就是String.split()方法。简单说一下：这个方法最终调用的是String类的private String[] split(String regex, int limit, boolean withDelimiters) {...}这个方法，可以看到传进去一个正则表达式作为参数，这个方法的最后几行代码是这样的\nPattern pattern = Pattern.compile(regex); return withDelimiters ? pattern.splitWithDelimiters(this, limit) : pattern.split(this, limit); 根据参数withDelimiters的布尔值来确定调用对象pattern的哪个方法，其实这两个方法最终调用的还是一个方法，兜兜转转还是回到了Pattern类和Matcher类。\n这篇博客就到这里，错误不可避免，欢迎指正。我会持续更新，欢迎收藏我的网站。\n参考文章：\n复杂匹配规则 - Java教程 - 廖雪峰的官方网站 Java 正则表达式 | 菜鸟教程 Java 正则表达式的用法和实例-CSDN博客 ","permalink":"https://blog.withdong02.top/posts/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/","summary":"\u003ch2 id=\"介绍\"\u003e介绍\u003c/h2\u003e\n\u003cp\u003e正则表达式（regular expression），常简写为regex，用简单字符串来描述、匹配文中全部匹配指定格式的字符串。人话讲就是根据一些规则制定一个字符串，然后你可以用这个字符串来筛选满足规则的字符串。许多程序设计语言都支持用正则表达式操作字符串，这里主要介绍正则表达式在Java中的运用。\u003c/p\u003e","title":"正则表达式"},{"content":"什么是反射 在spring项目中，只需要写个@Service或者@Component，然后在别的地方用@Autowired声明一个接口变量，Spring就能返回给我们一个实现了该接口的具体对象。这是如何实现的呢？它不可能在编译时就知道加了注解的类与类之间的关系，所以只能是在程序启动运行时，Spring动态地发现了这些类，读取了他们的结构，然后创建对象。这背后的技术支撑又是什么？答案就是反射。\n反射是Java提供的一种在程序运行时\n检查/获取类、接口、字段、方法、构造器等结构信息的能力。 操作/调用对象、字段、方法的能力。 它就像一面镜子，让程序在运行时“照见”自己的结构。\n反射的基石：Class对象 编译器在编译 Java 源代码时会生成 .class 文件（字节码文件）。当 JVM 需要用到某个类时，它的类加载器会读取并解析对应的 .class 文件，在方法区（或元空间）构建该类的运行时数据结构，同时在堆内存中创建一个代表该类的 java.lang.Class 对象。每个被加载的类在 JVM 中都有且只有一个对应的 Class 对象（在同一个类加载器命名空间内）。\n这里的Class是一个类的名字，不要和class关键字搞混。\n有三种方法获取Class对象\nPerson person = new Person(\u0026#34;me\u0026#34;, 20); //法一：通过对象实例 Class\u0026lt; ? extends Person\u0026gt; clazz1 = person.getClass(); //法二：自带属性（基本数据类型也有） Class\u0026lt;Person\u0026gt; clazz2 = Person.class; //法三：Class类的静态方法 forName try { Class\u0026lt;?\u0026gt; clazz3 = Class.forName(\u0026#34;org.myblog.reflection.Person\u0026#34;); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } 解释一下这三种方法泛型的使用：法一使用Class\u0026lt;? extends Person\u0026gt; 因为Class对象是在运行时从Person实例获取的，而Person实例的具体类型只能在运行时创建和确定，编译阶段无法判断，所以使用通配符 ，又因为person可能是Person实例，也可能是Person的子类实例，所以最终写成Class\u0026lt; ? extends Person\u0026gt;；法二使用Class\u0026lt;Person\u0026gt;因为编译时已知具体类型；法三使用Class\u0026lt;?\u0026gt;因为通过字符串动态加载类，编译时无法确定具体类型，所以使用通配符。这三个 Class 对象都是同一个（上面也提到了，一个类唯一对应一个 Class 对象）。\n最常用、最灵活的是法三\n反射的核心操作 Person类具体代码如下：\npublic class Person { public String name; private int age; public static String species = \u0026#34;Human\u0026#34;; public Person() { this(\u0026#34;Unknown\u0026#34;, 0); } public Person(String name, int age) { this.name = name; this.age = age; } private Person(String name) { this.name = name; this.age = 18; } public void greet(String name) { System.out.println(\u0026#34;Hello \u0026#34; + name + \u0026#34;,I\u0026#39;m \u0026#34; + this.name); } private void celebrateBirthday() { age++; System.out.println(name + \u0026#34; is now \u0026#34; + age + \u0026#34; years old!\u0026#34;); } public static void describeSpecies() { System.out.println(\u0026#34;We are all \u0026#34; + species); } } 获取类的信息 Class\u0026lt;?\u0026gt; clazz = Class.forName(\u0026#34;org.example.myblog.reflection.Person\u0026#34;); System.out.println(\u0026#34;类名：\u0026#34; + clazz.getName()); System.out.println(\u0026#34;包名：\u0026#34; + clazz.getPackage().getName()); System.out.println(\u0026#34;父类：\u0026#34; + clazz.getSuperclass()); System.out.println(\u0026#34;接口：\u0026#34; + Arrays.toString(clazz.getInterfaces())); System.out.println(\u0026#34;修饰符：\u0026#34; + Modifier.toString(clazz.getModifiers())); 类名：org.example.myblog.reflection.Person 包名：org.example.myblog.reflection 父类：class java.lang.Object 接口：[] 修饰符：public 操作字段 System.out.println(\u0026#34;操作字段+++++++++++++++++++++++++++++++++\u0026#34;); Field[] allFields = clazz.getDeclaredFields(); for (Field f : allFields) { System.out.println(\u0026#34;- \u0026#34; + Modifier.toString(f.getModifiers()) + \u0026#34; \u0026#34; + f.getType().getSimpleName() + \u0026#34; \u0026#34; + f.getName()); } Person me = new Person(\u0026#34;me\u0026#34;, 20); //访问public字段 Field nameField = clazz.getField(\u0026#34;name\u0026#34;); System.out.println(nameField.get(me)); //访问private字段 Field ageField = clazz.getDeclaredField(\u0026#34;age\u0026#34;); ageField.setAccessible(true); System.out.println(ageField.get(me)); ageField.set(me, 18); System.out.println(\u0026#34;修改后年龄: \u0026#34; + ageField.get(me)); //访问static字段 Field speciesField = clazz.getField(\u0026#34;species\u0026#34;); System.out.println(speciesField.get(null)); - public String name - private int age - public static String species me 20 修改后年龄: 18 Human 说明几点：\ngetDeclaredFields()是获取所有字段，并返回一个数组，getField则是根据参数返回指定字段，返回的是Field实例。 最后一行speciesField.get(null)传入参数null，因为静态字段是属于这个类的，当然也可以传入对象me。 ageField.setAccessible(true);这个方法传入参数true表示屏蔽Java语言的访问检查。看下面这个例子 Field[] allFields = clazz.getDeclaredFields(); for (Field f : allFields) { Object target = Modifier.isStatic(f.getModifiers()) ? null : me; System.out.println(\u0026#34; 可访问性: \u0026#34; + f.canAccess(target)); } public String name 可访问性: true private int age 可访问性: false public static String species 可访问性: true ​\t输出如上，age是不可访问的，在setAccessible(true)后可以访问并修改。否则会报错。\n如果想要访问private或其他非public字段，必须使用getDeclaredField()，注意里面有Declared，这个规律同样适用后面的Method和Constructor。 操作方法 System.out.println(\u0026#34;\\n==== 方法操作 ====\u0026#34;); Person Dong = new Person(\u0026#34;dong\u0026#34;, 18); Method[] allMethods = clazz.getDeclaredMethods(); System.out.println(\u0026#34;所有方法:\u0026#34;); for (Method m : allMethods) { System.out.println(\u0026#34;- \u0026#34; + m.getName() + \u0026#34;()\u0026#34;); } // 调用public方法 Method greetMethod = clazz.getMethod(\u0026#34;greet\u0026#34;, String.class); greetMethod.invoke(Dong, \u0026#34;李明\u0026#34;); // 调用private方法 Method birthdayMethod = clazz.getDeclaredMethod(\u0026#34;celebrateBirthday\u0026#34;); birthdayMethod.setAccessible(true); birthdayMethod.invoke(Dong); System.out.println(\u0026#34;新年龄: \u0026#34; + ageField.get(Dong)); // 验证年龄增加 // 调用static方法 Method speciesMethod = clazz.getMethod(\u0026#34;describeSpecies\u0026#34;); speciesMethod.invoke(null); 所有方法: - celebrateBirthday() - describeSpecies() - greet() Hello 李明,I\u0026#39;m dong dong is now 19 years old! 新年龄: 19 We are all Human 其实和操作字段有很多相似之处，这里说一下invoke方法\nMethod greetMethod = clazz.getMethod(\u0026#34;greet\u0026#34;, String.class); greetMethod.invoke(Dong, \u0026#34;李明\u0026#34;); getMethod方法有两个参数，一个是方法名称，一个是参数的class对象\ngreetMethod方法同样有两个参数，一个是对象实例（如果是静态方法则传入null），一个是传递的参数。这两个方法的参数个数不是固定的(其实是个数组)，这取决于目标方法的参数个数。\n操作构造器 // 获取所有构造器 Constructor\u0026lt;?\u0026gt;[] constructors = clazz.getDeclaredConstructors(); System.out.println(\u0026#34;所有构造器:\u0026#34;); for (Constructor\u0026lt;?\u0026gt; c : constructors) { System.out.println(\u0026#34;参数数量: \u0026#34; + c.getParameterCount()); } // 使用public无参构造器创建对象 Constructor\u0026lt;?\u0026gt; emptyConstructor = clazz.getConstructor(); Person unknown = (Person) emptyConstructor.newInstance(); System.out.println(\u0026#34;无参构造创建: \u0026#34; + unknown.name); // 使用public带参构造器创建对象 Constructor\u0026lt;?\u0026gt; paramConstructor = clazz.getConstructor(String.class, int.class); Person sarah = (Person) paramConstructor.newInstance(\u0026#34;Sarah\u0026#34;, 28); System.out.println(\u0026#34;带参构造创建: \u0026#34; + sarah.name + \u0026#34;, \u0026#34; + ageField.get(sarah)); // 使用private构造器创建对象 Constructor\u0026lt;?\u0026gt; privateConstructor = clazz.getDeclaredConstructor(String.class); privateConstructor.setAccessible(true); Person secret = (Person) privateConstructor.newInstance(\u0026#34;Secret\u0026#34;); System.out.println(\u0026#34;私有构造创建: \u0026#34; + secret.name + \u0026#34;, \u0026#34; + ageField.get(secret)); 所有构造器: 参数数量: 1 参数数量: 2 参数数量: 0 无参构造创建: Unknown 带参构造创建: Sarah, 28 私有构造创建: Secret, 18 经过前面叙述，这里的相关方法也是很好理解，getConstructor方法的参数取决于你拿到的构造器的参数（其实也是个数组）。\nnewInstance()方法可以根据你拿到的构造器来创建该构造器所在类的实例，参数同上理解。例子中是直接强转类型了，如果clazz是用法二得到的，也可以使用cast方法进行类型转换。一般来说这个方法创建的实例用Object来接收，因为创建的对象是在运行时动态生成的，编译阶段无法知道。建议使用反射直接调用和操作对象的方法和字段，而不是先进行类型转换，毕竟如果在编写阶段已经明确了要转换的类型，那么直接显示地调用更合适，而不必依赖于反射。反射的真正价值在于处理编译时未知的类型，从而编写更具有通用性的代码。\n示例里用Constructor\u0026lt;?\u0026gt;接收clazz.getConstructor()创建的实例是因为clazz的创建就使用的是通配符。如果在创建时指定类，那便可以指定泛型参数，相当于构造器就知道自己要构造的对象是什么类型了。\n结尾 上述内容只是反射部分的冰山一角，我已经尽我所能把这“一角”讲述清楚。\n反射很强，它能提供运行时动态操作类和对象的能力，是很多框架的基石，但同时它也带来了很多问题，性能开销大、代码可读性差等等。在日常开发中，程序员还是会优先选择直接调用、接口、设计模式等更清晰、高效的方式。\n有机会我也会更新一个与反射有关的实战案例，欢迎收藏我的网站。\n2026年3月7日修改：\n反射练习请看本站博客：手写IoC容器。\n参考文章：\n大白话说Java反射：入门、使用、原理 - 陈树义 - 博客园 Java基础之—反射（非常重要）-CSDN博客 Java中的反射 Reflection in Java_哔哩哔哩_bilibili ","permalink":"https://blog.withdong02.top/posts/java%E5%8F%8D%E5%B0%84/","summary":"\u003ch2 id=\"什么是反射\"\u003e什么是反射\u003c/h2\u003e\n\u003cp\u003e在\u003ccode\u003espring\u003c/code\u003e项目中，只需要写个\u003ccode\u003e@Service\u003c/code\u003e或者\u003ccode\u003e@Component\u003c/code\u003e，然后在别的地方用\u003ccode\u003e@Autowired\u003c/code\u003e声明一个接口变量，\u003ccode\u003eSpring\u003c/code\u003e就能返回给我们一个实现了该接口的具体对象。这是如何实现的呢？它不可能在编译时就知道加了注解的类与类之间的关系，所以只能是在程序启动\u003cstrong\u003e运行时\u003c/strong\u003e，Spring动态地发现了这些类，读取了他们的结构，然后创建对象。这背后的技术支撑又是什么？答案就是反射。\u003c/p\u003e","title":"Java反射"},{"content":"此篇博客记录我学习过程中遇到的奇葩BUG\nSpringBoot项目启动失败 如上图，SpringBoot项目启动失败。\n虽然说是端口占用，但我执行相关命令后没有任何输出，修改启动端口不行，尝试了很多办法都未成功，那只好使出我的终极大招：重启idea。还是不行。幸好我还有终极终极大招：重启电脑。嘿，你猜怎么着，成了！🤣\n接口测试报错getaddrinfo ENOTFOUND https 一次简单的接口测试，出现报错getaddrinfo ENOTFOUND https，搜索后得到答案：这通常意味着应用程序无法解析主机名或域名。这可能是由于DNS配置问题、网络连接问题或拼写错误等原因导致的。前两个果断排除，开始以为参数错，但一想那不应该试着错误，后来想可能域名错，但也没找到错误。最后发现是https:后面多了个空格。😑\nmysql容器连接报错 情况描述：本机为windows系统，装有wsl。在配置一个项目时使用docker-compose命令构建项目所需容器。构建好容器后，我测试mysql是否连接成功，结果出现报错2013 - Lost connection to server at 'handshake: reading initial communication packet', system error: 0.\n于是到网上搜索解决方案，有注释掉bind-address = 127.0.0.1的，有添加配置skip-name-resolve的，还有换系统的，但都无法解决我的问题。直到看见这篇博客CSDN博客。于是我直接修改docker-compose.yaml文件，添加network_mode: host，同时从豆包处得知：使用 host 网络模式后，容器会直接使用宿主机的网络栈，因此 ports 配置会被忽略。如果需要 MySQL 监听 3307 端口，需在 MySQL 配置文件（/etc/my.cnf 目录下的配置）中添加 port=3307，照做后问题解决。\n还要记得修改application.yml中的端口设置。\n","permalink":"https://blog.withdong02.top/posts/%E9%81%87%E5%88%B0%E7%9A%84%E5%A5%87%E8%91%A9bug/","summary":"\u003cp\u003e此篇博客记录我学习过程中遇到的奇葩BUG\u003c/p\u003e\n\u003ch2 id=\"springboot项目启动失败\"\u003e\u003ccode\u003eSpringBoot\u003c/code\u003e项目启动失败\u003c/h2\u003e\n\u003cp\u003e\u003cimg alt=\"image-20250801212248493\" decoding=\"async\" height=\"788\" loading=\"lazy\" src=\"https://dongimagehost-1356670526.cos.ap-nanjing.myqcloud.com/2025/07/image-20250801212248493.png\" width=\"2112\"\u003e\u003c/p\u003e\n\u003cp\u003e如上图，\u003ccode\u003eSpringBoot\u003c/code\u003e项目启动失败。\u003c/p\u003e\n\u003cp\u003e虽然说是端口占用，但我执行相关命令后没有任何输出，修改启动端口不行，尝试了很多办法都未成功，那只好使出我的终极大招：重启idea。还是不行。幸好我还有终极终极大招：重启电脑。嘿，你猜怎么着，成了！🤣\u003c/p\u003e","title":"遇到的奇葩BUG"},{"content":"什么是序列化和反序列化 序列化，人话讲就是将对象转换为字节序列（也可以是JSON、XML等文本格式），反序列化就是把这个过程倒置。\n下面是维基百科关于序列化的介绍\n序列化（serialization）在计算机科学的资料处理中，是指将数据结构或对象状态转换成可取用格式（例如存成文件，存于缓冲，或经由网络中发送），以留待后续在相同或另一台计算机环境中，能恢复原先状态的过程。依照序列化格式重新获取字节的结果时，可以利用它来产生与原始对象相同语义的副本。对于许多对象，像是使用大量引用的复杂对象，这种序列化重建的过程并不容易。面向对象中的对象序列化，并不概括之前原始对象所关系的函数。这种过程也称为对象编组（marshalling）。从一系列字节提取数据结构的反向操作，是反序列化（也称为解编组、deserialization、unmarshalling）。\n对于Java这种面向对象的编程语言来说，是对实例化后的对象进行序列化，而对于C++这种半面向对象的编程语言来说，序列化的目标不仅有对象（class）还有数据结构（struct）\n序列化的使用场景 数据存储：比如序列化可以将存储在 JVM 堆区中的对象转换成字节序列，从而实现持久化。\n网络通信：将对象转换为字节序列方便其在网络中进行传递和接收。\n使用Java实现序列化 以 JDK 自带序列化方法为例，实现java.io.Serializable接口\n@Data public class Cat implements Serializable { private int age; private String name; private Date birth; } 序列化演示：\npublic class serializeTest { public static void main(String[] args) { Cat cat = new Cat(); cat.setName(\u0026#34;tom\u0026#34;); cat.setAge(2); cat.setBirth(new Date()); //使用ObjectOutputStream将cat对象序列化并存入test1.txt文件中 try (FileOutputStream fileOutputStream = new FileOutputStream(\u0026#34;test1.txt\u0026#34;); ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream)) { objectOutputStream.writeObject(cat); } catch (IOException e) { e.printStackTrace(); } } } 反序列化演示：\npublic class deserializeTest { public static void main(String[] args) { //使用ObjectInputStream对test.txt文件读取并反序列化 try (FileInputStream fileInputStream = new FileInputStream(\u0026#34;test1.txt\u0026#34;); ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream)) { Cat cat = (Cat) objectInputStream.readObject(); System.out.println(cat); } catch (Exception e) { e.printStackTrace(); } } } 反序列化结果如下:\nCat(age=2, name=tom, birth=Fri Jul 04 21:37:13 CST 2035) 接下来让我们走进Serializable的源代码\npublic interface Serializable { } 你没看错，Serializable接口中没有任何方法和字段，它的作用仅仅是告诉JVM：实现该接口的类可以被序列化，\n其实除了实现Serializable接口外，还有一个选择就是实现Externalizable接口（该接口是Serializable的子接口），不过需要重写两个方法，示例如下：\n@Data public class Cat implements Externalizable { private int age; private String name; private Date birth; @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(name); out.writeObject(birth); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { name = (String) in.readObject(); birth = (Date) in.readObject(); } } 序列化和反序列化代码一样，最终结果如下：\nCat(age=0, name=tom, birth=Fri Jul 04 21:43:23 CST 2025) writeExternal和readExternal可以自定义哪些字段需要序列化，我这个例子中就没有对age进行操作，在反序列化后它被赋初值0。需要注意的是\nThe readExternal method must read the values in the same sequence and with the same types as were written by writeExternal.\n翻译过来意思就是这两个方法应该用相同的顺序和相同的类型对字段进行读写，相同的顺序容易理解，相同的类型意思就是在readExternal中强转的目标类型必须和writeExternal原类型保持一致。在实现Externalizable接口时，还有一点需要注意的是序列化对象中必须提供无参构造，以Cat类为例,当我加入一个有参构造覆盖默认无参构造后，序列化正常运行，反序列化出现报错\n关于序列化有几点需要注意：\n只有实现该接口的类才可以被序列化。\n可序列化类的所有子类是可以被序列化的。不可序列化类的子类型可以被序列化。\n如果不想对某个变量序列化，可以用transient关键字修饰，它只能修饰变量，不能修饰类和方法。transient 修饰的变量，在反序列化后变量值将会被置成类型的默认值。例如，如果是修饰 int 类型，那么反序列后结果就是 0。\n序列化运行时会为每个可序列化类分配一个版本号serialVersionUID该版本号在序列化机制中用于验证类的版本是否一致，如果反序列化时的UID和原来序列化的UID不同，则会抛出InvalidClassException。\n这个字段可以手动显示声明，也可以自动生成。如果显示声明，这个字段必须是static final且类型为long的。如果该字段未被显示声明，JVM会根据该类的结构自动生成一个serialVersionUID，枚举类的serialVersionUID会被定义为0L。\n官方强烈建议除了枚举类型以外的所有可序列化类显示声明serialVersionUID值，因为生成算法对类的细节非常敏感（如方法、字段等的变化都会导致serialVersionUID改变），从而导致兼容性问题。显式设置 serialVersionUID 可以确保在类发生非兼容更改时由开发者主动决定是否更新 UID，而不是因为类结构微小变化而导致反序列化失败。同时官方建议使用private来声明serialVersionUID，因为该字段不会被子类继承使用，因此没必要设置为public或protected。下面是一个完整的示例\n@Serial//Java14后建议添加 private static final long serialVersionUID = 1L; 这时小明举手说：老师老师，如果serialVersionUID被static修饰，那他就属于这个类，而不属于实例化后的对象了，那怎么将这个字段序列化呢？老师说：好问题，我也不知道。 序列化保存的是对象的状态，也就是实例变量的值，然而serialVersionUID是一个特例，它本身确实不作为对象状态被序列化，但它的值被序列化机制特殊处理了。屏幕前的你明白了吗，反正我还是有点糊涂，我觉得把它视为一个特例就好了，要真正弄懂怕是要搞明白JVM，那要很久以后了。\nJava反序列化漏洞 这里我推荐观看5i1encee的相关博客，关于反序列化漏洞我简要介绍一下。\n当攻击者通过构造恶意输入，让反序列化产生非预期的的对象，那么在反序列化这个过程中就可能执行恶意代码。已经有多个版本的库或框架被发现存在反序列化漏洞，如Apache Commons Collections 。借用一位大哥的例子来简单说明\npublic class VulnerabilityTest{ public static void main(String args[]) throws Exception{ MyObject myObj = new MyObject(); myObj.name = \u0026#34;hi\u0026#34;; FileOutputStream fos = new FileOutputStream(\u0026#34;object\u0026#34;); ObjectOutputStream os = new ObjectOutputStream(fos); os.writeObject(myObj); os.close(); FileInputStream fis = new FileInputStream(\u0026#34;object\u0026#34;); ObjectInputStream ois = new ObjectInputStream(fis); MyObject objectFromDisk = (MyObject)ois.readObject(); System.out.println(objectFromDisk.name); ois.close(); } } class MyObject implements Serializable { public String name; private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{ in.defaultReadObject(); Runtime.getRuntime().exec(\u0026#34;calc\u0026#34;); } } 在MyObject这个类中，自定义了readObject这个方法，相当于可以自己决定对哪些字段反序列化。在readObject这个方法里通过调用defaultReadObject来实现默认的反序列化机制。之后执行了名为calc的系统命令，这是Windows下启动计算器的命令。当我进行反序列化操作时，电脑上的计算器也随之启动。实际上，Java 反序列化漏洞产生的原因大多数是因为反序列化时没有进行校验，或者有些校验使用黑名单方式又被绕过，最终使得包含恶意代码的序列化对象在服务器端被反序列化执行。\n其他序列化协议 上述有关代码的举例均是 JDK 自带的序列化协议，但它在开发中很少被使用，主要原因如下：\n严重的安全风险：正如例子中提到的readObject方法，攻击者可构造恶意序列化数据触发任意代码执行 跨语言兼容性差：该协议序列化后的二进制数据只能被 Java 程序识别，无法与其他语言交互，多语言协作困难。 性能低下：序列化后的二进制数据体积远大于 JSON 或其他二进制协议。同时因为需要反射和递归处理对象图，导致速度较慢。 常用的序列化协议有Hessian、Kryo、 Protobuf、 ProtoStuff 等，这些都是基于二进制的序列化协议。SpringBoot 项目中也可以集成 Jackson、Fastjson 等将对象序列化为JSON格式的库。\n第一篇博客先写到这里，个人理解难免有偏差，欢迎评论区指正。我以后也会继续更新，欢迎收藏我的网站。\n参考文章：\nJava 序列化详解 | JavaGuide (6 封私信 / 70 条消息) Java反序列化安全漏洞怎么回事? - 知乎 ","permalink":"https://blog.withdong02.top/posts/java%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%8E%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/","summary":"\u003ch2 id=\"什么是序列化和反序列化\"\u003e什么是序列化和反序列化\u003c/h2\u003e\n\u003cp\u003e序列化，人话讲就是将对象转换为字节序列（也可以是JSON、XML等文本格式），反序列化就是把这个过程倒置。\u003c/p\u003e\n\u003cp\u003e下面是维基百科关于序列化的介绍\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003e序列化\u003c/strong\u003e（serialization）在\u003ca href=\"https://zh.wikipedia.org/wiki/%E8%A8%88%E7%AE%97%E6%A9%9F%E7%A7%91%E5%AD%B8\"\u003e计算机科学\u003c/a\u003e的资料处理中，是指将\u003ca href=\"https://zh.wikipedia.org/wiki/%E8%B3%87%E6%96%99%E7%B5%90%E6%A7%8B\"\u003e数据结构\u003c/a\u003e或\u003ca href=\"https://zh.wikipedia.org/wiki/%E7%89%A9%E4%BB%B6_%28%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%B8%29\"\u003e对象\u003c/a\u003e状态转换成可取用格式（例如存成文件，存于缓冲，或经由网络中发送），以留待后续在相同或另一台计算机环境中，能恢复原先状态的过程。依照序列化格式重新获取\u003ca href=\"https://zh.wikipedia.org/wiki/%E4%BD%8D%E5%85%83%E7%BB%84\"\u003e字节\u003c/a\u003e的结果时，可以利用它来产生与原始对象相同语义的副本。对于许多对象，像是使用大量\u003ca href=\"https://zh.wikipedia.org/wiki/%E5%8F%83%E7%85%A7\"\u003e引用\u003c/a\u003e的复杂对象，这种序列化重建的过程并不容易。面向对象中的对象序列化，并不概括之前原始对象所关系的函数。这种过程也称为对象编组（marshalling）。从一系列字节提取数据结构的反向操作，是\u003cstrong\u003e反序列化\u003c/strong\u003e（也称为解编组、deserialization、unmarshalling）。\u003c/p\u003e","title":"Java序列化与反序列化"},{"content":"","permalink":"https://blog.withdong02.top/taxonomies/","summary":"","title":"分类与标签"},{"content":"一名大四学生，来自杭州电子科技大学，正在学习后端开发，也在学习使用 AI 工具，欢迎通过首页联系方式与我交流。\n友链 友情链接 5i1encee英俊潇洒寝室长 z221x香香软软东北大哥 推荐学习网站 沉默王二Java 学习网站 JavaGuideJava 学习网站 ","permalink":"https://blog.withdong02.top/about/","summary":"\u003cp\u003e一名大四学生，来自杭州电子科技大学，正在学习后端开发，也在学习使用 AI 工具，欢迎通过首页联系方式与我交流。\u003c/p\u003e\n\u003ch2 id=\"友链\"\u003e友链\u003c/h2\u003e\n\u003ch3 id=\"友情链接\"\u003e友情链接\u003c/h3\u003e\n\u003cdiv class=\"friend-grid\"\u003e\n  \u003ca class=\"friend-card\" href=\"https://5i1encee.top/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e\n    \u003cimg src=\"/assets/5i1encee.jpg\" alt=\"5i1encee 头像\" loading=\"lazy\"\u003e\n    \u003cspan\u003e\u003cstrong\u003e5i1encee\u003c/strong\u003e\u003csmall\u003e英俊潇洒寝室长\u003c/small\u003e\u003c/span\u003e\n  \u003c/a\u003e\n  \u003ca class=\"friend-card\" href=\"https://z221x.website/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e\n    \u003cimg src=\"/assets/z221x.jpg\" alt=\"z221x 头像\" loading=\"lazy\"\u003e\n    \u003cspan\u003e\u003cstrong\u003ez221x\u003c/strong\u003e\u003csmall\u003e香香软软东北大哥\u003c/small\u003e\u003c/span\u003e\n  \u003c/a\u003e\n\u003c/div\u003e\n\u003ch3 id=\"推荐学习网站\"\u003e推荐学习网站\u003c/h3\u003e\n\u003cdiv class=\"friend-grid\"\u003e\n  \u003ca class=\"friend-card\" href=\"https://javabetter.cn/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e\n    \u003cimg src=\"/assets/erge.png\" alt=\"沉默王二头像\" loading=\"lazy\"\u003e\n    \u003cspan\u003e\u003cstrong\u003e沉默王二\u003c/strong\u003e\u003csmall\u003eJava 学习网站\u003c/small\u003e\u003c/span\u003e\n  \u003c/a\u003e\n  \u003ca class=\"friend-card\" href=\"https://javaguide.cn/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e\n    \u003cimg src=\"/assets/JavaGuide.png\" alt=\"JavaGuide 头像\" loading=\"lazy\"\u003e\n    \u003cspan\u003e\u003cstrong\u003eJavaGuide\u003c/strong\u003e\u003csmall\u003eJava 学习网站\u003c/small\u003e\u003c/span\u003e\n  \u003c/a\u003e\n\u003c/div\u003e","title":"关于"}]