【对话写 Netty 代码的同学,你真的懂 Netty 了吗?(三中)】initAndRegister() 之 init() 对不起!!我柜子动了…我不学了

本文主要是介绍【对话写 Netty 代码的同学,你真的懂 Netty 了吗?(三中)】initAndRegister() 之 init() 对不起!!我柜子动了…我不学了,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录:

【对话写 Netty 代码的同学,你真的懂 Netty 了吗?(一)】Netty 初始化总览

【对话写 Netty 代码的同学,你真的懂 Netty 了吗?(二)】主线流程 new NioEventLoopGroup(nThreads) 究竟做了什么

【对话写 Netty 代码的同学,你真的懂 Netty 了吗?(三上)】initAndRegister() 之 init() 怒了!这文章为啥写成了这样??

【对话写 Netty 代码的同学,你真的懂 Netty 了吗?(三中)】initAndRegister() 之 init() 对不起!!我柜子动了…我不学了

写在前面

欢迎添加个人微信 dyinggq 一起交流学习~~

书接上文:对话写 Netty 代码的同学,你真的懂 Netty 了吗?(三之上)怒了!这文章为啥写成了这样??

在上篇中,对于 Netty 的第一大部分 initAndRegister() 方法我们拆分为三部分进行讲解

  1. channel = channelFactory.newChannel();
  2. init(channel);
  3. ChannelFuture regFuture = config().group().register(channel);

对于第二小部分的 init(channel) 有些遗漏的地方,故而本篇将从 init 继续讲起。

initAndRegister 之 init(channel)

上篇总结到:

init 其实就是在初始化 NioServerSocketChannel 的各个属性,主要是其 options 和 pipeline 的初始化。

然而在这里对于 pipeline 的初始化有些遗漏,在 ServerBootstrap 的 init 方法中为 pipeline 添加 ChannelInitializer 忽略了一个细节 ch.eventLoop().execute

        ChannelPipeline p = channel.pipeline(); // 这里的 channel 即 NioServerSocketChannelp.addLast(new ChannelInitializer<Channel>() {@Overridepublic void initChannel(final Channel ch) throws Exception {final ChannelPipeline pipeline = ch.pipeline();ChannelHandler handler = config.handler();if (handler != null) {pipeline.addLast(handler);}ch.eventLoop().execute(new Runnable() {@Overridepublic void run() {pipeline.addLast(new ServerBootstrapAcceptor(ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));}});}});

对于 ch.eventLoop().execute() 这行提交任务的代码 ch.eventLoop() 获取的是 NioEventLoop
NioEventLoop.execute() 执行的是其父类 SingleThreadEventExecutor 实现的 execute 方法

@Overridepublic void execute(Runnable task) {if (task == null) {throw new NullPointerException("task");}boolean inEventLoop = inEventLoop();addTask(task);if (!inEventLoop) {startThread();if (isShutdown()) {boolean reject = false;try {if (removeTask(task)) {reject = true;}} catch (UnsupportedOperationException e) {// The task queue does not support removal so the best thing we can do is to just move on and// hope we will be able to pick-up the task before its completely terminated.// In worst case we will log on termination.}if (reject) {reject();}}}if (!addTaskWakesUp && wakesUpForTask(task)) {wakeup(inEventLoop);}}

这里核心的两个方法:

addTask(task);
startThread();
1.先看 addTask(task);
    protected void addTask(Runnable task) {if (task == null) {throw new NullPointerException("task");}if (!offerTask(task)) {reject(task);}}final boolean offerTask(Runnable task) {if (isShutdown()) {reject();}return taskQueue.offer(task);}

在这里插入图片描述
在这里插入图片描述
通过断点跟踪的方式可知 public void initChannel(final Channel ch) 就是 initAndRegister() 方法中,一开始通过 channel = channelFactory.newChannel(); 初始化的 NioServerSocketChannel

2.再看 startThread(); 方法
    private void startThread() {if (state == ST_NOT_STARTED) {if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {try {doStartThread();} catch (Throwable cause) {STATE_UPDATER.set(this, ST_NOT_STARTED);PlatformDependent.throwException(cause);}}}}

直接看其核心逻辑 doStartThread()

 private void doStartThread() {assert thread == null;executor.execute(new Runnable() {@Overridepublic void run() {thread = Thread.currentThread();if (interrupted) {thread.interrupt();}boolean success = false;updateLastExecutionTime();try {SingleThreadEventExecutor.this.run();success = true;} catch (Throwable t) {logger.warn("Unexpected exception from an event executor: ", t);} finally {for (;;) {int oldState = state;if (oldState >= ST_SHUTTING_DOWN || STATE_UPDATER.compareAndSet(SingleThreadEventExecutor.this, oldState, ST_SHUTTING_DOWN)) {break;}}// Check if confirmShutdown() was called at the end of the loop.if (success && gracefulShutdownStartTime == 0) {if (logger.isErrorEnabled()) {logger.error("Buggy " + EventExecutor.class.getSimpleName() + " implementation; " +SingleThreadEventExecutor.class.getSimpleName() + ".confirmShutdown() must " +"be called before run() implementation terminates.");}}try {// Run all remaining tasks and shutdown hooks.for (;;) {if (confirmShutdown()) {break;}}} finally {try {cleanup();} finally {// Lets remove all FastThreadLocals for the Thread as we are about to terminate and notify// the future. The user may block on the future and once it unblocks the JVM may terminate// and start unloading classes.// See https://github.com/netty/netty/issues/6596.FastThreadLocal.removeAll();STATE_UPDATER.set(SingleThreadEventExecutor.this, ST_TERMINATED);threadLock.release();if (!taskQueue.isEmpty()) {if (logger.isWarnEnabled()) {logger.warn("An event executor terminated with " +"non-empty task queue (" + taskQueue.size() + ')');}}terminationFuture.setSuccess(null);}}}}});}

这里最核心的逻辑为:SingleThreadEventExecutor.this.run(); 跟进到实现方法中,很显然这里是 NioEventLoop

在这里插入图片描述
NioEventLoop run() 源码如下

@Overrideprotected void run() {for (;;) {try {try {switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {case SelectStrategy.CONTINUE:continue;case SelectStrategy.BUSY_WAIT:// fall-through to SELECT since the busy-wait is not supported with NIOcase SelectStrategy.SELECT:select(wakenUp.getAndSet(false));// 'wakenUp.compareAndSet(false, true)' is always evaluated// before calling 'selector.wakeup()' to reduce the wake-up// overhead. (Selector.wakeup() is an expensive operation.)//// However, there is a race condition in this approach.// The race condition is triggered when 'wakenUp' is set to// true too early.//// 'wakenUp' is set to true too early if:// 1) Selector is waken up between 'wakenUp.set(false)' and//    'selector.select(...)'. (BAD)// 2) Selector is waken up between 'selector.select(...)' and//    'if (wakenUp.get()) { ... }'. (OK)//// In the first case, 'wakenUp' is set to true and the// following 'selector.select(...)' will wake up immediately.// Until 'wakenUp' is set to false again in the next round,// 'wakenUp.compareAndSet(false, true)' will fail, and therefore// any attempt to wake up the Selector will fail, too, causing// the following 'selector.select(...)' call to block// unnecessarily.//// To fix this problem, we wake up the selector again if wakenUp// is true immediately after selector.select(...).// It is inefficient in that it wakes up the selector for both// the first case (BAD - wake-up required) and the second case// (OK - no wake-up required).if (wakenUp.get()) {selector.wakeup();}// fall throughdefault:}} catch (IOException e) {// If we receive an IOException here its because the Selector is messed up. Let's rebuild// the selector and retry. https://github.com/netty/netty/issues/8566rebuildSelector0();handleLoopException(e);continue;}cancelledKeys = 0;needsToSelectAgain = false;final int ioRatio = this.ioRatio;if (ioRatio == 100) {try {processSelectedKeys();} finally {// Ensure we always run tasks.runAllTasks();}} else {final long ioStartTime = System.nanoTime();try {processSelectedKeys();} finally {// Ensure we always run tasks.final long ioTime = System.nanoTime() - ioStartTime;runAllTasks(ioTime * (100 - ioRatio) / ioRatio);}}} catch (Throwable t) {handleLoopException(t);}// Always handle shutdown even if the loop processing threw an exception.try {if (isShuttingDown()) {closeAll();if (confirmShutdown()) {return;}}} catch (Throwable t) {handleLoopException(t);}}}

这里搞了个死循环来执行逻辑
在这里插入图片描述
我们这里重点关注 select(wakenUp.getAndSet(false));

case SelectStrategy.SELECT:select(wakenUp.getAndSet(false));
 private void select(boolean oldWakenUp) throws IOException {Selector selector = this.selector;try {int selectCnt = 0;long currentTimeNanos = System.nanoTime();long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);for (;;) {long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;if (timeoutMillis <= 0) {if (selectCnt == 0) {selector.selectNow();selectCnt = 1;}break;}// If a task was submitted when wakenUp value was true, the task didn't get a chance to call// Selector#wakeup. So we need to check task queue again before executing select operation.// If we don't, the task might be pended until select operation was timed out.// It might be pended until idle timeout if IdleStateHandler existed in pipeline.if (hasTasks() && wakenUp.compareAndSet(false, true)) {selector.selectNow();selectCnt = 1;break;}int selectedKeys = selector.select(timeoutMillis);selectCnt ++;if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {// - Selected something,// - waken up by user, or// - the task queue has a pending task.// - a scheduled task is ready for processingbreak;}if (Thread.interrupted()) {// Thread was interrupted so reset selected keys and break so we not run into a busy loop.// As this is most likely a bug in the handler of the user or it's client library we will// also log it.//// See https://github.com/netty/netty/issues/2426if (logger.isDebugEnabled()) {logger.debug("Selector.select() returned prematurely because " +"Thread.currentThread().interrupt() was called. Use " +"NioEventLoop.shutdownGracefully() to shutdown the NioEventLoop.");}selectCnt = 1;break;}long time = System.nanoTime();if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {// timeoutMillis elapsed without anything selected.selectCnt = 1;} else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {// The code exists in an extra method to ensure the method is not too big to inline as this// branch is not very likely to get hit very frequently.selector = selectRebuildSelector(selectCnt);selectCnt = 1;break;}currentTimeNanos = time;}if (selectCnt > MIN_PREMATURE_SELECTOR_RETURNS) {if (logger.isDebugEnabled()) {logger.debug("Selector.select() returned prematurely {} times in a row for Selector {}.",selectCnt - 1, selector);}}} catch (CancelledKeyException e) {if (logger.isDebugEnabled()) {logger.debug(CancelledKeyException.class.getSimpleName() + " raised by a Selector {} - JDK bug?",selector, e);}// Harmless exception - log anyway}}

在该 select() 方法中同样使用了 for (;;) 死循环进行逻辑执行。不同于 run() 方法的是循环没有跳出而 select() 是有 break 跳出条件的。

同样的我们看关键逻辑:

  int selectedKeys = selector.select(timeoutMillis);

这里就是 Nio 的代码,进行了一个超时阻塞操作,获取 selectedKeys 或者直到超时。

如果获取到了事件则跳出该循环

       if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {// - Selected something,// - waken up by user, or// - the task queue has a pending task.// - a scheduled task is ready for processingbreak;}

简单串一下目前的逻辑
1.在 run() 方法中 死循环 for(;;) 执行 首先进行了 switch select()

     case SelectStrategy.SELECT:select(wakenUp.getAndSet(false));

2.在 switch select() 同样进行了 for(;;) 循环,正常流程会 超时阻塞等待 select 事件 selector.select(timeoutMillis),如果获取到事件则跳出循环 。一般情况该循环会等待事件获取,或者直到超时才进行跳出,返回 run() 方法的主循环逻辑进行后面方法的执行。
目前为止,这里是大循环套了一个小循环,进行事件的监听选择。

跳出 switch select() 循环后会执行到后面的核心逻辑

if (ioRatio == 100) {try {processSelectedKeys();} finally {// Ensure we always run tasks.runAllTasks();}
} else {final long ioStartTime = System.nanoTime();try {processSelectedKeys();} finally {// Ensure we always run tasks.final long ioTime = System.nanoTime() - ioStartTime;runAllTasks(ioTime * (100 - ioRatio) / ioRatio);}
}

可以看到无论如何都会走到两个主逻辑中,这里我们重点看下这两个核心逻辑

processSelectedKeys();
runAllTasks();

首先处理被选择 keys 方法 processSelectedKeys()

private void processSelectedKeys() {if (selectedKeys != null) {processSelectedKeysOptimized();} else {processSelectedKeysPlain(selector.selectedKeys());}
}

通过 debug 断点可以看到在启动 NettyServer 的时候 selectedKeys 不为空 但 size = 0
所以会进入方法 processSelectedKeysOptimized()

我们两个方法都过一下:

private void processSelectedKeysOptimized() {for (int i = 0; i < selectedKeys.size; ++i) {final SelectionKey k = selectedKeys.keys[i];// null out entry in the array to allow to have it GC'ed once the Channel close// See https://github.com/netty/netty/issues/2363selectedKeys.keys[i] = null;final Object a = k.attachment();if (a instanceof AbstractNioChannel) {processSelectedKey(k, (AbstractNioChannel) a);} else {@SuppressWarnings("unchecked")NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;processSelectedKey(k, task);}if (needsToSelectAgain) {// null out entries in the array to allow to have it GC'ed once the Channel close// See https://github.com/netty/netty/issues/2363selectedKeys.reset(i + 1);selectAgain();i = -1;}}
}
private void processSelectedKeysPlain(Set<SelectionKey> selectedKeys) {// check if the set is empty and if so just return to not create garbage by// creating a new Iterator every time even if there is nothing to process.// See https://github.com/netty/netty/issues/597if (selectedKeys.isEmpty()) {return;}Iterator<SelectionKey> i = selectedKeys.iterator();for (;;) {final SelectionKey k = i.next();final Object a = k.attachment();i.remove();if (a instanceof AbstractNioChannel) {processSelectedKey(k, (AbstractNioChannel) a);} else {@SuppressWarnings("unchecked")NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;processSelectedKey(k, task);}if (!i.hasNext()) {break;}if (needsToSelectAgain) {selectAgain();selectedKeys = selector.selectedKeys();// Create the iterator again to avoid ConcurrentModificationExceptionif (selectedKeys.isEmpty()) {break;} else {i = selectedKeys.iterator();}}}
}

可以看到其实大同小异,最终都会走到 processSelectedKey() 这样一个方法中,方法有两个实现 :

  1. processSelectedKey(SelectionKey k, AbstractNioChannel ch)
  2. processSelectedKey(SelectionKey k, NioTask<SelectableChannel> task)
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();if (!k.isValid()) {final EventLoop eventLoop;try {eventLoop = ch.eventLoop();} catch (Throwable ignored) {// If the channel implementation throws an exception because there is no event loop, we ignore this// because we are only trying to determine if ch is registered to this event loop and thus has authority// to close ch.return;}// Only close ch if ch is still registered to this EventLoop. ch could have deregistered from the event loop// and thus the SelectionKey could be cancelled as part of the deregistration process, but the channel is// still healthy and should not be closed.// See https://github.com/netty/netty/issues/5125if (eventLoop != this || eventLoop == null) {return;}// close the channel if the key is not valid anymoreunsafe.close(unsafe.voidPromise());return;}try {int readyOps = k.readyOps();// We first need to call finishConnect() before try to trigger a read(...) or write(...) as otherwise// the NIO JDK channel implementation may throw a NotYetConnectedException.if ((readyOps & SelectionKey.OP_CONNECT) != 0) {// remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking// See https://github.com/netty/netty/issues/924int ops = k.interestOps();ops &= ~SelectionKey.OP_CONNECT;k.interestOps(ops);unsafe.finishConnect();}// Process OP_WRITE first as we may be able to write some queued buffers and so free memory.if ((readyOps & SelectionKey.OP_WRITE) != 0) {// Call forceFlush which will also take care of clear the OP_WRITE once there is nothing left to writech.unsafe().forceFlush();}// Also check for readOps of 0 to workaround possible JDK bug which may otherwise lead// to a spin loopif ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {unsafe.read();}} catch (CancelledKeyException ignored) {unsafe.close(unsafe.voidPromise());}
}
private static void processSelectedKey(SelectionKey k, NioTask<SelectableChannel> task) {int state = 0;try {task.channelReady(k.channel(), k);state = 1;} catch (Exception e) {k.cancel();invokeChannelUnregistered(task, k, e);state = 2;} finally {switch (state) {case 0:k.cancel();invokeChannelUnregistered(task, k, null);break;case 1:if (!k.isValid()) { // Cancelled by channelReady()invokeChannelUnregistered(task, k, null);}break;}}
}

这里我们主要看方法 1 。可以看到该方法主要逻辑是通过位与操作来判断是否是对应的事件从而进行相应的操作

if ((readyOps & SelectionKey.OP_CONNECT) != 0)
if ((readyOps & SelectionKey.OP_WRITE) != 0)
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0)

最终调用 unsafe 的几个方法 final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();

	unsafe.finishConnect();ch.unsafe().forceFlush();unsafe.read();public interface NioUnsafe extends Unsafe {/*** Return underlying {@link SelectableChannel}*/SelectableChannel ch();/*** Finish connect*/void finishConnect();/*** Read from underlying {@link SelectableChannel}*/void read();void forceFlush();}

这块首先要记住几个 unsafe 内部类

protected abstract class AbstractNioUnsafe extends AbstractUnsafe implements NioUnsafe
protected class NioByteUnsafe extends AbstractNioUnsafe
private final class NioMessageUnsafe extends AbstractNioUnsafe

AbstractNioUnsafe 为 AbstractChannel 内部类
NioByteUnsafe 为 AbstractNioMessageChannel 内部类
NioMessageUnsafe 为 AbstractNioMessageChannel 内部类

这几个方法还是比较重要的,这里先有个印象,暂时按下不表,我们继续走下面的流程

到这里 processSelectedKeys(); 的流程就走完了,这里就形成了下图循环的前两步
在这里插入图片描述
NioEventLoop 为 Netty 提供了这样的 run 方法循环,该死循环核心三步为:

  1. select(wakenUp.getAndSet(false));
  2. processSelectedKeys();
  3. runAllTasks();

第三步 runAllTasks();

从名字上看,就可以理解到 是执行任务了。

  protected boolean runAllTasks() {assert inEventLoop();boolean fetchedAll;boolean ranAtLeastOne = false;do {fetchedAll = fetchFromScheduledTaskQueue();if (runAllTasksFrom(taskQueue)) {ranAtLeastOne = true;}} while (!fetchedAll); // keep on processing until we fetched all scheduled tasks.if (ranAtLeastOne) {lastExecutionTime = ScheduledFutureTask.nanoTime();}afterRunningAllTasks();return ranAtLeastOne;}

这里我们主要看 runAllTasksFrom(taskQueue)

   protected final boolean runAllTasksFrom(Queue<Runnable> taskQueue) {Runnable task = pollTaskFrom(taskQueue);if (task == null) {return false;}for (;;) {safeExecute(task);task = pollTaskFrom(taskQueue);if (task == null) {return true;}}}

这里从之前 NioEventLoop 中 的 taskQueue 取出任务执行

   protected static Runnable pollTaskFrom(Queue<Runnable> taskQueue) {for (;;) {Runnable task = taskQueue.poll();if (task == WAKEUP_TASK) {continue;}return task;}}
    protected static void safeExecute(Runnable task) {try {task.run();} catch (Throwable t) {logger.warn("A task raised an exception. Task: {}", task, t);}}

这个时候 taskQueue 是没有任务的,何时有任务我们放到后面的文章进行讲解。

到这里 startThread(); 的主线逻辑就过完了。

总结

本篇作为中篇吧,行文实在有点长了,简单做个总结。
本文主要介绍了 Netty 自定义的 Executor 中的执行逻辑,也是 Netty 处理任务的关键逻辑。

核心类为:SingleThreadEventExecutor
核心方法 :

// 重写了提交任务方法
public void execute(Runnable task)
protected void addTask(Runnable task)
private void startThread()
private void doStartThread()
protected abstract void run();
protected boolean runAllTasks()

以及 NioEventLoop 中对 SingleThreadEventExecutor run() 抽象方法的实现

protected void run()
private void processSelectedKeys()
private void select(boolean oldWakenUp)

其中最为重要的是 doStartThread() 方法中调用的 NioEventLoop 实现的 run() 方法

SingleThreadEventExecutor.this.run();

run() 方法中进行了 循环三部曲

  1. select 超时阻塞事件选择
  2. processSelectedKeys unsafe 事件处理
  3. runAllTasks 执行 taskQueue 中所有任务

在这里插入图片描述
最后的最后,其实还有很重要的一点不知道大家注意到了没有,这里我在放上源码:

        p.addLast(new ChannelInitializer<Channel>() {@Overridepublic void initChannel(final Channel ch) throws Exception {final ChannelPipeline pipeline = ch.pipeline();ChannelHandler handler = config.handler();if (handler != null) {pipeline.addLast(handler);}ch.eventLoop().execute(new Runnable() {@Overridepublic void run() {pipeline.addLast(new ServerBootstrapAcceptor(ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));}});}});

其实在 init 这里 ch.eventLoop().execute 并没有真正的执行,这里只是进行了重写声明而已。而在后文的 register 中会有直接的执行。

经过上文的介绍相信大家对上图也有了具体的理解。下篇文章将开启 register 之旅。感谢可以看到这里,希望本文对你有所帮助。

我是 dying 搁浅 ,我始终期待与你的相遇。无论你是否期待,潮涨潮落,我仅且就在这里…

我们下期再见~
在这里插入图片描述

这篇关于【对话写 Netty 代码的同学,你真的懂 Netty 了吗?(三中)】initAndRegister() 之 init() 对不起!!我柜子动了…我不学了的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/329719

相关文章

活用c4d官方开发文档查询代码

当你问AI助手比如豆包,如何用python禁止掉xpresso标签时候,它会提示到 这时候要用到两个东西。https://developers.maxon.net/论坛搜索和开发文档 比如这里我就在官方找到正确的id描述 然后我就把参数标签换过来

poj 1258 Agri-Net(最小生成树模板代码)

感觉用这题来当模板更适合。 题意就是给你邻接矩阵求最小生成树啦。~ prim代码:效率很高。172k...0ms。 #include<stdio.h>#include<algorithm>using namespace std;const int MaxN = 101;const int INF = 0x3f3f3f3f;int g[MaxN][MaxN];int n

计算机毕业设计 大学志愿填报系统 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

🍊作者:计算机编程-吉哥 🍊简介:专业从事JavaWeb程序开发,微信小程序开发,定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事,生活就是快乐的。 🍊心愿:点赞 👍 收藏 ⭐评论 📝 🍅 文末获取源码联系 👇🏻 精彩专栏推荐订阅 👇🏻 不然下次找不到哟~Java毕业设计项目~热门选题推荐《1000套》 目录 1.技术选型 2.开发工具 3.功能

代码随想录冲冲冲 Day39 动态规划Part7

198. 打家劫舍 dp数组的意义是在第i位的时候偷的最大钱数是多少 如果nums的size为0 总价值当然就是0 如果nums的size为1 总价值是nums[0] 遍历顺序就是从小到大遍历 之后是递推公式 对于dp[i]的最大价值来说有两种可能 1.偷第i个 那么最大价值就是dp[i-2]+nums[i] 2.不偷第i个 那么价值就是dp[i-1] 之后取这两个的最大值就是d

pip-tools:打造可重复、可控的 Python 开发环境,解决依赖关系,让代码更稳定

在 Python 开发中,管理依赖关系是一项繁琐且容易出错的任务。手动更新依赖版本、处理冲突、确保一致性等等,都可能让开发者感到头疼。而 pip-tools 为开发者提供了一套稳定可靠的解决方案。 什么是 pip-tools? pip-tools 是一组命令行工具,旨在简化 Python 依赖关系的管理,确保项目环境的稳定性和可重复性。它主要包含两个核心工具:pip-compile 和 pip

D4代码AC集

贪心问题解决的步骤: (局部贪心能导致全局贪心)    1.确定贪心策略    2.验证贪心策略是否正确 排队接水 #include<bits/stdc++.h>using namespace std;int main(){int w,n,a[32000];cin>>w>>n;for(int i=1;i<=n;i++){cin>>a[i];}sort(a+1,a+n+1);int i=1

html css jquery选项卡 代码练习小项目

在学习 html 和 css jquery 结合使用的时候 做好是能尝试做一些简单的小功能,来提高自己的 逻辑能力,熟悉代码的编写语法 下面分享一段代码 使用html css jquery选项卡 代码练习 <div class="box"><dl class="tab"><dd class="active">手机</dd><dd>家电</dd><dd>服装</dd><dd>数码</dd><dd

为什么现在很多人愿意选择做债务重组?债重组真的就这么好吗?

债务重组,起初作为面向优质企业客户的定制化大额融资策略,以其高效周期著称,一个月便显成效。然而,随着时代的车轮滚滚向前,它已悄然转变为负债累累、深陷网贷泥潭者的救赎之道。在此路径下,个人可先借助专业机构暂代月供,经一段时间养护征信之后,转向银行获取低成本贷款,用以替换高昂网贷,实现利息减负与成本优化的双重目标。 尽管债务重组的代价不菲,远超传统贷款成本,但其吸引力依旧强劲,背后逻辑深刻。其一

生信代码入门:从零开始掌握生物信息学编程技能

少走弯路,高效分析;了解生信云,访问 【生信圆桌x生信专用云服务器】 : www.tebteb.cc 介绍 生物信息学是一个高度跨学科的领域,结合了生物学、计算机科学和统计学。随着高通量测序技术的发展,海量的生物数据需要通过编程来进行处理和分析。因此,掌握生信编程技能,成为每一个生物信息学研究者的必备能力。 生信代码入门,旨在帮助初学者从零开始学习生物信息学中的编程基础。通过学习常用

husky 工具配置代码检查工作流:提交代码至仓库前做代码检查

提示:这篇博客以我前两篇博客作为先修知识,请大家先去看看我前两篇博客 博客指路:前端 ESlint 代码规范及修复代码规范错误-CSDN博客前端 Vue3 项目开发—— ESLint & prettier 配置代码风格-CSDN博客 husky 工具配置代码检查工作流的作用 在工作中,我们经常需要将写好的代码提交至代码仓库 但是由于程序员疏忽而将不规范的代码提交至仓库,显然是不合理的 所