(children.length);
Collections.addAll(childrenSet, children);
readonlyChildren = Collections.unmodifiableSet(childrenSet);
}
这边将代码主要分为三个步骤:
1.创建线程执行器
这里有个new DefaultThreadFactory()创建一个DefaultThreadFactory对象, 这个对象作为参数传入ThreadPerTaskExecutor的构造函数, DefaultThreadFactory顾名思义, 是一个线程工厂, 用于创建线程的, 简单看下这个类的继承关系:
public class DefaultThreadFactory implements ThreadFactory{//类体}
这里继承了jdk底层ThreadFactory类, 用于创建线程
我们继续跟进ThreadPerTaskExecutor的类中:
public final class ThreadPerTaskExecutor implements Executor {
private final ThreadFactory threadFactory;
public ThreadPerTaskExecutor(ThreadFactory threadFactory) {
if (threadFactory == null) {
throw new NullPointerException("threadFactory");
}
this.threadFactory = threadFactory;
}
@Override
public void execute(Runnable command) {
//起一个线程
threadFactory.newThread(command).start();
}
}
这个类非常简单, 继承了jdk的Executor类, 从继承关系中我就能猜想到, 而这个类就是用于开启线程的线程执行器,再看重写的 execute(Runnable command) 方法, 传入一个任务, 然后由threadFactory对象创建一个线程执行该任务。这个execute(Runnable command)方法, 其实就是用开开启NioEventLoop线程用的。
这样, 通过 executor = new ThreadPerTaskExecutor(newDefaultThreadFactory()) 这种方式就返回了一个线程执行器Executor, 用于开启NioEventLoop线程
2.创建EventLoop,并存储到EventExecutor类型的数组中
这里通过 children = new EventExecutor[nThreads] 初始化了children属性, 看下这个属性的定义:
private final EventExecutor[] children
这里的children是EventExecutor类型的数组, 其实就是NioEventLoop的集合, 因为NioEventLoop也是EventExecutor的子类
所以这里初始化了children数组, 大小为参数nThreads传入的线程数量, 默认为cpu核数的两倍
后面就是通过for循环来创建NioEventLoop线程,在循环体里通过 newChild() 创建NioEventLoop, 我们跟newChild(executor, args)方法
protected EventLoop newChild(Executor executor, Object... args) throws Exception {
return new NioEventLoop(this, executor, (SelectorProvider) args[0], ((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2]);
}
this是NioEventLoopGroup自身, executor就是上一小节讲到的线程执行器
跟一下NioEventLoop的构造方法,跟到父类SingleThreadEventExecutor构造方法:
protected SingleThreadEventExecutor(EventExecutorGroup parent, Executor executor,
boolean addTaskWakesUp, int maxPendingTasks,
RejectedExecutionHandler rejectedHandler) {
super(parent);
this.addTaskWakesUp = addTaskWakesUp;
this.maxPendingTasks = Math.max(16, maxPendingTasks);
//初始化了线程执行器
this.executor = ObjectUtil.checkNotNull(executor, "executor");
//创建一个任务队列, 这个任务队列可以将不属于NioEventLoop线程的任务放到这个任务队列中, 通过NioEventLoop线程执行
taskQueue = newTaskQueue(this.maxPendingTasks);
rejectedExecutionHandler = ObjectUtil.checkNotNull(rejectedHandler, "rejectedHandler");
}
3.创建线程选择器
chooser = chooserFactory.newChooser(children);
NioEventLoopGroup都绑定一个chooser对象, 作为线程选择器, 通过这个线程选择器, 为每一个channel分配不同的线程
我们看到newChooser(children)传入了NioEventLoop数组
我们跟到DefaultEventExecutorChooserFactory类中的newChooser方法:
public EventExecutorChooser newChooser(EventExecutor[] executors) {
if (isPowerOfTwo(executors.length)) {
return new PowerOfTowEventExecutorChooser(executors);
} else {
return new GenericEventExecutorChooser(executors);
}
}
这里通过 isPowerOfTwo(executors.length) 判断NioEventLoop数组的长度是不是2的倍数, 然后根据判断结果返回两种选择器对象, 这里使用到java设计模式的策略模式
根据这两个类的名字不难看出, 如果是2的倍数, 使用的是一种高性能的方式选择线程, 如果不是2的倍数, 则使用一种比较普通的线程选择方式。
private static final class PowerOfTowEventExecutorChooser implements EventExecutorChooser {
private final AtomicInteger idx = new AtomicInteger();
private final EventExecutor[] executors;
PowerOfTowEventExecutorChooser(EventExecutor[] executors) {
this.executors = executors;
}
@Override
public EventExecutor next() {
return executors[idx.getAndIncrement() & executors.length - 1];
}
}
这个类实现了线程选择器的接口EventExecutorChooser, 构造方法中初始化了NioEventLoop线程数组
重点关注下next()方法, next()方法就是选择下一个线程的方法, 如果线程数是2的倍数, 这里通过按位与进行计算, 所以效率极高
private static final class GenericEventExecutorChooser implements EventExecutorChooser {
private final AtomicInteger idx = new AtomicInteger();
private final EventExecutor[] executors;
GenericEventExecutorChooser(EventExecutor[] executors) {
this.executors = executors;
}
@Override
public EventExecutor next() {
return executors[Math.abs(idx.getAndIncrement() % executors.length)];
}
}
这个类同样实现了线程选择器的接口EventExecutorChooser, 并在造方法中初始化了NioEventLoop线程数组
再看这个类的next()方法, 如果线程数不是2的倍数, 则用绝对值和取模的这种效率一般的方式进行线程选择
2、NioEventLoop
2.1 类图
![Netty源码分析——NioEventLoopGroup & NioEventLoop_第3张图片](http://img.e-com-net.com/image/info8/b69465c1098641e3ae13f05c708ffc44.jpg)
NioEventLoop 是只有单个线程的线程池,但并不是一个纯粹的I/O线程,它除了负责I/O的读写之外,还兼顾处理以下两类任务:
-
系统Task:通过调用NioEventLoop的execute(Runnable task)方法实现,Netty有很多系统Task,创建它们的主要原因是:当I/O线程和用户线程同时操作网络资源时,为了防止并发操作导致的锁竞争,将用户线程的操作封装成Task放入消息队列中,由I/O线程负责执行,这样就实现了局部无锁化。
-
定时任务:通过调用NioEventLoop的schedule(Runnable command, long delay, TimeUnit unit)方法实现。
2.2 selector
作为NIO框架的Reactor线程,NioEventLoop需要处理网络I/O读写事件,因此它必须聚合一个多路复用器对象。Selector的初始化非常简单,直接调用Selector.open()方法就能创建并打开一个新的Selector。Netty对Selector的selectedKeys进行了优化,用户可以通过io.netty.noKeySetOptimization开关决定是否启用该优化项。默认不打开selectedKeys优化功能。
Selector selector;
private SelectedSelectionKeySet selectedKeys;
private final SelectorProvider provider;
NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider) {
super(parent, executor, false);
if (selectorProvider == null) {
throw new NullPointerException("selectorProvider");
}
provider = selectorProvider;
//初始化selector
selector = openSelector();
}
private Selector openSelector() {
final Selector selector;
try {
////调用jdk底层的api,通过provider.openSelector()创建并打开多路复用器
selector = provider.openSelector();
} catch (IOException e) {
throw new ChannelException("failed to open a new selector", e);
}
//如果没有开启selectedKeys优化开关,就立即返回。
if (DISABLE_KEYSET_OPTIMIZATION) {
return selector;
}
//如果开启了优化开关,需要通过反射的方式从Selector实例中获取selectedKeys和publicSelectedKeys,
//将上述两个成员变量设置为可写,通过反射的方式使用Netty构造的selectedKeys包装类selectedKeySet将原JDK的selectedKeys替换掉。
try {
//用这个数据结构替换原生的SelectionKeySet
SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet();
//通过反射拿到sun.nio.ch.SelectorImpl这个类的class对象
Class selectorImplClass = Class.forName("sun.nio.ch.SelectorImpl", false, ClassLoader.getSystemClassLoader());
//判断拿到的是不是class对象并且是不是Selector的实现类,如果不是他的实现, 就直接返回原生select
if (!selectorImplClass.isAssignableFrom(selector.getClass())) {
return selector;
}
//通过反射拿到selectedKeys和publicSelectedKeys两个属性, 默认这两个属性底层都是hashSet方式实现的
Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys");
Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys");
//设置成可修改的
selectedKeysField.setAccessible(true);
publicSelectedKeysField.setAccessible(true);
//将selector的这两个属性替换成Netty的selectedKeySet
selectedKeysField.set(selector, selectedKeySet);
publicSelectedKeysField.set(selector, selectedKeySet);
selectedKeys = selectedKeySet;
logger.trace("Instrumented an optimized java.util.Set into: {}", selector);
} catch (Throwable t) {
selectedKeys = null;
logger.trace("Failed to instrument an optimized java.util.Set into: {}", selector, t);
}
return selector;
}
selector在select()操作时候, 会通过selector.selectedKeys()操作返回一个Set, 这个是Set类型。
netty对这个set进行了处理, 使用SelectedSelectionKeySet的数据结构进行替换, 当在select()操作时将key存入一个SelectedSelectionKeySet的数据结构中。
简单跟一下SelectedSelectionKeySet这个类的构造方法:
SelectedSelectionKeySet() {
keys = new SelectionKey[1024];
}
说明这类其实底层是通过数组实现的, 通过操作数组下标会有更高的效率。看下其中重要的方法,add表示添加一个SelectionKey。remove()方法, contains()方法都返回了false, 说明其不支持删除方法和包含方法。
@Override
public boolean add(SelectionKey o) {
if (o == null) {
return false;
}
keys[size++] = o;
if (size == keys.length) {
increaseCapacity();
}
return true;
}
@Override
public boolean remove(Object o) {
return false;
}
@Override
public boolean contains(Object o) {
return false;
}
2.3 run
NioEventLoop 的核心方法是 run 方法
protected void run() {
for (;;) {
try {
switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
case SelectStrategy.CONTINUE:
continue;
case SelectStrategy.BUSY_WAIT:
case SelectStrategy.SELECT:
//轮询io事件(1)
select(wakenUp.getAndSet(false));
if (wakenUp.get()) {
selector.wakeup();
}
// fall through
default:
}
cancelledKeys = 0;
needsToSelectAgain = false;
//ioRatio主要是用来控制processSelectedKeys()方法执行时间和任务队列执行时间的比例, 其中ioRatio默认是50, 所以会走到下一步else
final int ioRatio = this.ioRatio;
if (ioRatio == 100) {
try {
processSelectedKeys();
} finally {
// Ensure we always run tasks.
runAllTasks();
}
} else {
//记录下开始时间
final long ioStartTime = System.nanoTime();
try {
//处理轮询到的key(2)
processSelectedKeys();
} finally {
//计算耗时
final long ioTime = System.nanoTime() - ioStartTime;
//执行task(3)
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);
}
}
}
分为以下3步
1. 轮询io事件
2. 处理轮询到的key
3. 执行任务队列中的task
1.轮询io事件
首先switch块中默认会走到SelectStrategy.SELECT中, 执行select(wakenUp.getAndSet(false))方法
参数wakenUp.getAndSet(false)代表当前select操作是未唤醒状态
进入到select(wakenUp.getAndSet(false))方法中:
private void select(boolean oldWakenUp) throws IOException {
Selector selector = this.selector;
try {
int selectCnt = 0;
//当前系统的纳秒数
long currentTimeNanos = System.nanoTime();
//截止时间=当前时间+队列第一个任务剩余执行时间,因为超过之后定时任务不能严格按照预定时间执行,
//delayNanos(currentTimeNanos)代表距定时任务中第一个任务剩余多长时间,
//其中定时任务队列是已经按照执行时间有小到大排列好的队列, 所以第一个任务则是最近需要执行的任务, selectDeadLineNanos就代表了当前操作不能超过的时间。
long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);
for (;;) {
//阻塞时间(毫秒)=(截止时间-当前时间+0.5毫秒),除以1000000表示将计算的时间转化为毫秒数
//最后算出的时间就是selector操作的阻塞时间, 并赋值到局部变量的timeoutMillis中
long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;
if (timeoutMillis <= 0) {
if (selectCnt == 0) {
//时间已经超过了最后截止时间+0.5毫秒, selectCnt == 0 代表没有进行select操作, 满足这两个条件, 则执行selectNow()之后, 将selectCnt赋值为1之后跳出循环
selector.selectNow();
selectCnt = 1;
}
break;
}
//没超过截止时间
if (hasTasks() && wakenUp.compareAndSet(false, true)) {
//hasTasks()这里是判断当前NioEventLoop所绑定的taskQueue是否有任务,
//如果有任务, sa则执行selectNow()之后, 将selectCnt赋值为1之后跳出循环(跳出循环之后去执行任务队列中的任务)
selector.selectNow();
selectCnt = 1;
break;
}
//如果没有满足上述条件, 就会执行如下,进行阻塞式轮询, 并且自增轮询次数
int selectedKeys = selector.select(timeoutMillis);
//轮询次数
selectCnt ++;
//如果轮询到一个事件(selectedKeys != 0), 或者当前select操作需要唤醒(oldWakenUp),
//或者在执行select操作时已经被外部线程唤醒(wakenUp.get()),
//或者任务队列已经有任务(hasTask), 或者定时任务队列中有任务(hasScheduledTasks())
//如果满足以上,就跳出循环
if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {
break;
}
//省略
//记录下当前时间
long time = System.nanoTime();
//当前时间-开始时间>=超时时间(条件成立, 说明已经完整的执行完成了一个阻塞的select()操作, 条件不成立, 有可能发生空轮询)
if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {
//代表已经进行了一次阻塞式select操作, 操作次数重置为1
selectCnt = 1;
} else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 && selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
//省略日志代码
//如果空轮询的次数大于一个阈值(512), 解决空轮询的bug
rebuildSelector();
selector = this.selector;
selector.selectNow();
selectCnt = 1;
break;
}
currentTimeNanos = time;
}
//代码省略
} catch (CancelledKeyException e) {
//省略
}
}
如果此条件不成立, 说明没有完整执行select()操作, 可能触发了一次空轮询, 根据前一个selectCnt++这步我们知道, 每触发一次空轮询selectCnt都会自增
之后会进入第二个判断 SELECTOR_AUTO_REBUILD_THRESHOLD > 0 && selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD
其中SELECTOR_AUTO_REBUILD_THRESHOLD默认是512, 这个判断意思就是空轮询的次数如果超过512次, 则会认为是发生了epoll bug, 这样会通过rebuildSelector()方法重新构建selector, 然后将重新构建的selector赋值到局部变量selector, 执行一次selectNow(), 将selectCnt初始化1, 跳出循环
2.处理轮询到的key
ioRatio主要是用来控制processSelectedKeys()方法执行时间和任务队列执行时间的比例, 其中ioRatio默认是50, 所以会走到下一步else,首先通过 final long ioStartTime = System.nanoTime() 记录下开始时间, 再通过processSelectedKeys()方法处理轮询到的key。
我们跟到processSelectedKeys()方法中:
private void processSelectedKeys() {
if (selectedKeys != null) {
//如果selectedKeys被netty优化过,走这里
processSelectedKeysOptimized();
} else {
//如果selectedKeys没有被优化过,到这里
processSelectedKeysPlain(selector.selectedKeys());
}
}
private void processSelectedKeysOptimized() {
//通过for循环遍历数组
for (int i = 0; i < selectedKeys.size; ++i) {
//拿到当前的selectionKey
final SelectionKey k = selectedKeys.keys[i];
//将当前引用设置为null
selectedKeys.keys[i] = null;
//获取channel(NioSeverSocketChannel)
final Object a = k.attachment();
//判断channel是不是AbstractNioChannel, 通常情况都是AbstractNioChannel。
//如果是AbstractNioChannel, 则调用processSelectedKey()方法处理io事件
if (a instanceof AbstractNioChannel) {
processSelectedKey(k, (AbstractNioChannel) a);
} else {
@SuppressWarnings("unchecked")
NioTask task = (NioTask) 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/2363
selectedKeys.reset(i + 1);
selectAgain();
i = -1;
}
}
}
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
//获取到channel中的unsafe
final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
//验证key是否合法的
if (!k.isValid()) {
//如果这个key不是合法的, 说明这个channel可能有问题
final EventLoop eventLoop;
try {
eventLoop = ch.eventLoop();
} catch (Throwable ignored) {
return;
}
if (eventLoop != this || eventLoop == null) {
return;
}
unsafe.close(unsafe.voidPromise());
return;
}
try {
//如果是合法的, 拿到key的io事件
int readyOps = k.readyOps();
//链接事件
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
int ops = k.interestOps();
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops);
unsafe.finishConnect();
}
//写事件
if ((readyOps & SelectionKey.OP_WRITE) != 0) {
ch.unsafe().forceFlush();
}
//读事件和接受链接事件
//如果当前NioEventLoop是work线程的话, 这里就是op_read事件
//如果是当前NioEventLoop是boss线程的话, 这里就是op_accept事件
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
//然后会通过channel绑定的unsafe对象执行read()方法用于处理链接或者读写事件
unsafe.read();
}
} catch (CancelledKeyException ignored) {
unsafe.close(unsafe.voidPromise());
}
}
3.执行任务队列中的task
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
protected boolean runAllTasks(long timeoutNanos) {
//从定时任务队列中聚合任务,也就是将定时任务中找到可以执行的任务添加到taskQueue中
fetchFromScheduledTaskQueue();
//从普通taskQ里面拿一个任务
Runnable task = pollTask();
//task为空, 则直接返回
if (task == null) {
//跑完所有的任务执行收尾的操作
afterRunningAllTasks();
return false;
}
//如果队列不为空
//首先算一个截止时间(+50毫秒, 因为执行任务, 不要超过这个时间)
final long deadline = ScheduledFutureTask.nanoTime() + timeoutNanos;
long runTasks = 0;
long lastExecutionTime;
//执行每一个任务
for (;;) {
safeExecute(task);
//标记当前跑完的任务
runTasks ++;
//当跑完64个任务的时候, 会计算一下当前时间
if ((runTasks & 0x3F) == 0) {
//定时任务初始化到当前的时间
lastExecutionTime = ScheduledFutureTask.nanoTime();
//如果超过截止时间则不执行(nanoTime()是耗时的)
if (lastExecutionTime >= deadline) {
break;
}
}
//如果没有超过这个时间, 则继续从普通任务队列拿任务
task = pollTask();
//直到没有任务执行
if (task == null) {
//记录下最后执行时间
lastExecutionTime = ScheduledFutureTask.nanoTime();
break;
}
}
//收尾工作
afterRunningAllTasks();
this.lastExecutionTime = lastExecutionTime;
return true;
}
private boolean fetchFromScheduledTaskQueue() {
long nanoTime = AbstractScheduledEventExecutor.nanoTime();
//从定时任务队列中抓取第一个定时任务
//寻找截止时间为nanoTime的任务
Runnable scheduledTask = pollScheduledTask(nanoTime);
//如果该定时任务队列不为空, 则塞到普通任务队列里面
while (scheduledTask != null) {
//如果添加到普通任务队列过程中失败
if (!taskQueue.offer(scheduledTask)) {
//则重新添加到定时任务队列中
scheduledTaskQueue().add((ScheduledFutureTask) scheduledTask);
return false;
}
//继续从定时任务队列中拉取任务
//方法执行完成之后, 所有符合运行条件的定时任务队列, 都添加到了普通任务队列中
scheduledTask = pollScheduledTask(nanoTime);
}
return true;
}
参考文献:
https://www.cnblogs.com/xiangnan6122/p/10203169.html
你可能感兴趣的:(netty,java)
- ✨JMH(Java Microbenchmark Harness)✨
魔镜前的帅比
java开发语言
JMH(JavaMicrobenchmarkHarness)是一个专门用于编写、运行和分析Java微基准测试的工具。它由OpenJDK团队开发,旨在提供精确的基准测试结果,避免常见的基准测试陷阱,如JVM的优化、即时编译(JIT)等影响。核心知识点基准测试的目的基准测试用于测量代码的性能,通常是在微秒或纳秒级别。它可以帮助开发者识别性能瓶颈,优化代码。JMH的特点精确性:JMH通过多次迭代和预热来
- java后端开发面试常问
躲在没风的地方
java面试题java面试spring
面试常问问题1spring相关(1)@Transactional失效的场景@Transactional注解默认只会回滚运行时异常(RuntimeException),如果方法中抛出了其他异常,则事务不会回滚(数据库数据仍然插入成功了)。@Transactional(rollbackFor=Exception.class)如果方法中有trycatch语句,并且抛出的异常的代码被try捕获,那么方法上
- Amaze UI web mobile 前端框架
iteye_20240
ui前端框架移动开发
目录结构index.html-空白HTML模板;blog.html-博客页面模板(预览);landing.html-LandingPage模板(预览);login.html-登录界面模板(预览);sidebar.html-带边栏的文章模板(预览);在app.css中编写CSS;在app.js中编写JavaScript;移动端首选Zepto,桌面端选jQuery,这应该是大多数开发者的共识。那对于跨
- Java中Long类型处理精度丢失问题
原冰洋
javabug
Long类型处理精度丢失问题描述后端给前端传一个long类型的数据,如果数据过大,前端js接收就会精度丢失,如果再传过来给后端,后端接收到的数据也是精度丢失的数据解决方案只需要将后端从传给前端的long类型的数据变成string类型就好了。1.提供对象转换器jacksonObjectMapper,基于jackson进行java对象到json数据的转换2.在webMvcConfig配置类中扩展Spr
- 后端Long类型19位返回前端丢失精度问题
19940719
JAVA
后端Long类型19位返回前端丢失精度问题问题原因:由于Long类型最大19位而JavaScript最大接收数字为16位,固存在精度丢失问题解决方案1.导入Maven依赖com.fasterxml.jackson.corejackson
- 【设计模式】【行为型模式】访问者模式(Visitor)
FLZJ_KL
设计模式设计模式访问者模式java
hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD2025本人正在沉淀中…博客更新速度++欢迎点赞、收藏、关注,跟上我的更新节奏当你的天空突然下了大雨,那是我在为你炸乌云文章目录一、入门什么是访问者模式?为什么需要访问者模式?怎么实现访问模式?二、访问者模式在源码中的运用ASM框架(Java字节码操作)三、总结访问者模式的优点访问者模式的缺点访问者模式的适用场景一、
- 【JavaScript】Day01
boy快快长大
前端javascript开发语言ecmascript
1.介绍1.1JS是什么一种运行在客户端(浏览器)的编程语言,实现人机交互效果。1.2作用网页特效(监听用户的一些行为让网页做出对应的反馈)表单验证(针对表单数据的合法性做出判断)数据交互(获取后台的数据,渲染到前端)服务端编程(node.js)1.3JS的组成ECMAScript:规定了JS基础的语法知识WebAPIs:DOM与BOMDOM:操作文档,比如对页面元素进行移动、大小、添加删除等操作
- DeepSeek接入Python,一般电脑也能飞速跑,确实可以封神了!
算法channel
python开发语言
你好,我是郭震今天这篇教程介绍:DeepSeep最新推理模型R1接入Python编程,在本地电脑从零搭建方法。1这样做有哪些好处?1)大模型在本地搭建,除了能够方便个人知识库管理,详见上一篇介绍,还能提效编程学习,比如Python,Java等,学编程就像学做事的思路和逻辑,挺重要也很有意思。2)DeepSeek最近开源了推理模型R1,开源免费,性能强劲,本文接入的正是DeepSeek的R1;Pyt
- 华为OD最新机试真题-最小的调整次数-C++-OD统一考试(E卷)
ai因思坦
华为odc++算法华为开发语言面试
最新华为OD机试考点合集:华为OD机试2024年真题题库(E卷+D卷+C卷)_华为od机试题库-CSDN博客每一题都含有详细的解题思路和代码注释,精选c++、JAVA、Python三种语言解法。帮助每一位考生轻松、高效刷题。订阅后永久可看,发现新题及时跟新。题目描述:有一个特异性的双端队列,该队列可以从头部或尾部添加数据,但是只能从头部移出数据。小A依次执行2n个指令往队列中添加数据和移出数据。其
- Maven 引入外部依赖
lsx202406
开发语言
Maven引入外部依赖引言Maven是一个强大的项目管理和构建自动化工具,广泛应用于Java项目的开发中。在Maven的项目中,引入外部依赖是构建项目不可或缺的一环。本文将详细介绍如何在Maven中引入外部依赖,包括依赖的类型、配置方式以及常见问题解决。一、依赖的类型在Maven中,依赖可以分为以下几类:Compile依赖:编译依赖,用于项目的编译过程。这类依赖通常包含了项目的编译时需要用到的类库
- golang之基础库
丑阳璐
go语言golangjson
从这小节开始,就变得尤为重要,希望小伙伴们打起精神包管理工具(gomodule)使用gomodule来管理0包之间的依赖关系和Java类似,类似于文件夹。当我们需要使用某个包时,导入某个包即可导包时,必须从GOPATH最初的包路径开始//查看环境变量PSD:\gowork\goProiect\Go01Test>goenvsetGO111MODULE=on//模块化setGOARCH=amd64se
- 高德地图JS API地址解析
天马3798
JavaScriptjavascriptjava数据库
- Java内存回收(GC)新贵的抉择,G1与ZGC!
颯沓如流星
javajvm架构
前言Java的垃圾回收(GarbageCollection,GC)是确保内存管理高效和稳定的关键机制。随着JDK的不断演进,各种垃圾回收器也应运而生,其中G1和ZGC作为两大明星产品,各自拥有独特的特性和适用场景。本文将详细解析G1和ZGC的特性和回收方式。关键概念解析Region在G1和ZGC中,Region是堆内存的一个逻辑分区。G1将堆划分为多个大小相同的Region,用于存放对象。而ZGC
- 【Java】已解决:java.util.concurrent.ExecutionException
屿小夏
java开发语言android
个人简介:某不知名博主,致力于全栈领域的优质博客分享|用最优质的内容带来最舒适的阅读体验!文末获取免费IT学习资料!文末获取更多信息精彩专栏推荐订阅收藏专栏系列直达链接相关介绍书籍分享点我跳转书籍作为获取知识的重要途径,对于IT从业者来说更是不可或缺的资源。不定期更新IT图书,并在评论区抽取随机粉丝,书籍免费包邮到家AI前沿点我跳转探讨人工智能技术领域的最新发展和创新,涵盖机器学习、深度学习、自然
- 静态类型和动态类型
老赵骑摩托
go语言
静态类型和动态类型定义编程语言按照类型检查可以分为两大类:静态类型(StaticTyping)和动态类型(DynamicTyping)。在现在比较流行的这些语言里,静态类型的代表人物有Java、C/C++、Golang等,动态类型有Python、Ruby等。静态类型语言中,变量具有类型,而且在编译期确定,具有某一类型的变量只能持有相同类型的数据。动态类型语言中,变量没有类型,只有数据有类型,变量可
- 走进Java:JDK和JRE分别是什么?它们有什么区别?
老鼠只爱大米
javaJava面试Java面试攻略Java高频面试题JREJDK
目录引言一、概述二、JRE(JavaRuntimeEnvironment)三、JDK(JavaDevelopmentKit)四、安装和配置五、区别和联系六、总结引言 Java是一门非常流行的编程语言,被广泛应用于各个领域的软件开发。在Java开发中,JRE和JDK是两个重要的概念,它们有着不同的作用和功能。本篇技术博客将会介绍JRE和JDK的概念、组成部分、安装和配置、以及它们之间的区别和联系。
- javascript的成熟分类:“成熟分类”之路
lizi88888
javascript开发语言ecmascript
JavaScript是一门广泛应用于Web前端开发的高级编程语言,具有交互性强、界面效果丰富等优点。其在Web开发中的广泛应用越来越受到业界的关注,从最初的“套壳”式编程到现在的成熟分类,JavaScript已经发生了巨大的变化。ES5ES5标准于2009年发布,是JavaScript发展的一个重要里程碑。这个版本添加了一些新的语言特性,例如:严格模式、Object.create()方法、Func
- Http connect timed out
不像程序员的程序媛
http网络网络协议
客户向云端服务请求时,连接云端域名显示连接超时,为什么呢,偶尔会有。java.net.SocketTimeoutException:connecttimedoutatjava.net.PlainSocketImpl.socketConnect(NativeMethod)atjava.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketI
- String的基础知识
GordonH1991
java
前提:androidsdk和jdk版本对照中可以看到从android5.0后就需要使用jdk7以上的版本,这里讨论的默认字符串常量池放置在虚拟机的堆内存中,不再过多讨论永久代(PermanentGeneration,PermGen)的方法区,事实上从java8后,JVM就已经没有永久代了,取而代之的是元空间(MetaSpace)1.String.java的开头注释TheStringclassrep
- 总结:使用JDK原生HttpsURLConnection,封装HttpsUtil工具类,加载自定义证书验证,忽略ssl证书验证
ideal-cs
总结javasslhttps自签证书验证ssl验证https忽略证书验证https工具类
总结:使用JDK原生HttpsURLConnection,封装HttpsUtil工具类,加载自定义证书验证,忽略ssl证书验证一·HttpsUtil工具类二·SSLUtil工具类一·HttpsUtil工具类packagecom.example.util;importjavax.net.ssl.HttpsURLConnection;importjavax.net.ssl.SSLContext;imp
- new的过程发生了什么,手写实现new
零合一
前端基础知识点原型模式javascript开发语言
new的过程当使用new关键字调用函数时,该函数将被用作构造函数。new将执行以下操作:创建空对象:创建一个空的简单JavaScript对象。为方便起见,我们称之为newInstance。绑定原型:如果构造函数的prototype属性是一个对象,则将newInstance的[[Prototype]]指向构造函数的prototype属性,否则newInstance将保持为一个普通对象,其[[Prot
- new操作中发生了什么?
木头没有瓜
js
比较直观的感觉,当我们new一个构造函数,得到的实例继承了构造器的构造属性(this.name这些)以及原型上的属性。在《JavaScript模式》这本书中,new的过程说的比较直白,当我们new一个构造器,主要有三步:•创建一个空对象,将它的引用赋给this,继承函数的原型。•通过this将属性和方法添加至这个对象•最后返回this指向的新对象,也就是实例(如果没有手动返回其他的对象)例子,大概
- 详解 Java 基础的多态机制
红烧白开水。
后端JAVA多态java开发语言后端java基础多态
一、什么是多态?多态(Polymorphism)是面向对象编程(OOP)的三大核心特性之一,指同一操作作用于不同对象时,可以产生不同的行为。在Java中,多态通过以下两种形式体现:编译时多态:方法重载(Overload),根据参数列表在编译时确定调用哪个方法。运行时多态:方法重写(Override),通过继承和接口实现,在运行时动态绑定具体方法(本文重点)。二、多态的核心实现机制1.三个必要条件继
- 混合开发框架整理:使用Crosswalk + WebViewJavascriptBridge进行混合开发
hwj3747
Android混合开发androidhtml5javascriptjqueryhtml
作者:hwj3747转载请注明Crosswalk介绍目前APP的开发模式大多基于H5+原生壳的开发模式,这时候使用到的WebView的性能就至关重要。我们知道,Android平台上,系统的碎片化比较严重,同Android版本的WebView的H5解析能力也有较大差异,导致相应的HTML5应用一致性难以保证。所以在做混合开发的时候,对Android系统的适配是一个比较麻烦的问题。这个时候,如果能在我
- 跟我学C++中级篇——C++编码的几点建议
fpcc
C++c++软件工程
一、C++编码C++语言做为一种为大多数人认为难度较大的开发语言,因其灵活多变的风格和技术导致其应用开发不易为开发者掌握。有过较长C++开发经验的程序员可能发现,在C++开发的人员中,鱼龙混杂,各色开发人员都有。它不象Js或Java等高级语言,大家的开发水平在三五年后基本都差不多。C++编码的程序员可能有的十几年如一日的编写着让人头大的代码。二、如何更好的编写C++代码为了方便描述如何更好的编写C
- jdbc连接数据库时 java.lang.RuntimeException错误怎么解决
脑袋坏掉了
数据库javamysql
Exceptioninthread"main"java.lang.RuntimeException:com.mysql.cj.jdbc.exceptions.CommunicationsException:CommunicationslinkfailureThelastpacketsentsuccessfullytotheserverwas0millisecondsago.Thedriverhas
- java进阶篇--Docker 有什么优点?使用时需要注意什么问题?
爱分享的淘金达人
Java源码剖析(30讲)Dockerdocker使用docker注意事项
Docker从2013年发展到现在,它的普及率已经可以和最常用的MySQL和Redis并驾齐驱了,从最初偶尔出现在面试中,到现在几乎成为面试中必问的问题之一。如果再不了解Docker相关的知识点,可能就会与自己心仪的职位擦肩而过。所以本课时将会带领你对Docker相关的知识做一个全面的认识。我们本课的问题是,Docker有什么优点?使用时需要注意什么问题?回答:Docker是一个开源(开放源代码)
- Go语言函数:比Java更简单优雅的函数设计
后端
Go语言函数:比Java更简单优雅的函数设计前言嘿,各位开发者!今天我们来聊一个有趣的话题:Go语言的函数。如果你是从Java阵营过来的,准备好被惊艳到了!因为Go的函数设计其实就是优雅到极致的艺术品。一、函数定义:告别繁文缛节1.简洁的语法Java:publicstaticintadd(inta,intb){returna+b;}Go:funcadd(a,bint)int{returna+b;}
- Java面试题--设计模式
m0_74823892
面试学习路线阿里巴巴java设计模式开发语言
答案以及所有23种设计模式详细介绍,请参考:https://blog.csdn.net/doymm2008/article/details/13288067设计模式的分类有哪些?工厂模式:概念:实现方法:普通工厂方法、静态工厂方法使用场景:有哪些问题单例模式:好处:单例模式的实现代码:最简单的,以及优化后的适配器模式:概念核心思想实现代码装饰模式:概念:实现代码:应用场景代理模式:概念:实现代码:
- 对股票分析时要注意哪些主要因素?
会飞的奇葩猪
股票 分析 云掌股吧
众所周知,对散户投资者来说,股票技术分析是应战股市的核心武器,想学好股票的技术分析一定要知道哪些是重点学习的,其实非常简单,我们只要记住三个要素:成交量、价格趋势、振荡指标。
一、成交量
大盘的成交量状态。成交量大说明市场的获利机会较多,成交量小说明市场的获利机会较少。当沪市的成交量超过150亿时是强市市场状态,运用技术找综合买点较准;
- 【Scala十八】视图界定与上下文界定
bit1129
scala
Context Bound,上下文界定,是Scala为隐式参数引入的一种语法糖,使得隐式转换的编码更加简洁。
隐式参数
首先引入一个泛型函数max,用于取a和b的最大值
def max[T](a: T, b: T) = {
if (a > b) a else b
}
因为T是未知类型,只有运行时才会代入真正的类型,因此调用a >
- C语言的分支——Object-C程序设计阅读有感
darkblue086
applec框架cocoa
自从1972年贝尔实验室Dennis Ritchie开发了C语言,C语言已经有了很多版本和实现,从Borland到microsoft还是GNU、Apple都提供了不同时代的多种选择,我们知道C语言是基于Thompson开发的B语言的,Object-C是以SmallTalk-80为基础的。和C++不同的是,Object C并不是C的超集,因为有很多特性与C是不同的。
Object-C程序设计这本书
- 去除浏览器对表单值的记忆
周凡杨
html记忆autocompleteform浏览
&n
- java的树形通讯录
g21121
java
最近用到企业通讯录,虽然以前也开发过,但是用的是jsf,拼成的树形,及其笨重和难维护。后来就想到直接生成json格式字符串,页面上也好展现。
// 首先取出每个部门的联系人
for (int i = 0; i < depList.size(); i++) {
List<Contacts> list = getContactList(depList.get(i
- Nginx安装部署
510888780
nginxlinux
Nginx ("engine x") 是一个高性能的 HTTP 和 反向代理 服务器,也是一个 IMAP/POP3/SMTP 代理服务器。 Nginx 是由 Igor Sysoev 为俄罗斯访问量第二的 Rambler.ru 站点开发的,第一个公开版本0.1.0发布于2004年10月4日。其将源代码以类BSD许可证的形式发布,因它的稳定性、丰富的功能集、示例配置文件和低系统资源
- java servelet异步处理请求
墙头上一根草
java异步返回servlet
servlet3.0以后支持异步处理请求,具体是使用AsyncContext ,包装httpservletRequest以及httpservletResponse具有异步的功能,
final AsyncContext ac = request.startAsync(request, response);
ac.s
- 我的spring学习笔记8-Spring中Bean的实例化
aijuans
Spring 3
在Spring中要实例化一个Bean有几种方法:
1、最常用的(普通方法)
<bean id="myBean" class="www.6e6.org.MyBean" />
使用这样方法,按Spring就会使用Bean的默认构造方法,也就是把没有参数的构造方法来建立Bean实例。
(有构造方法的下个文细说)
2、还
- 为Mysql创建最优的索引
annan211
mysql索引
索引对于良好的性能非常关键,尤其是当数据规模越来越大的时候,索引的对性能的影响越发重要。
索引经常会被误解甚至忽略,而且经常被糟糕的设计。
索引优化应该是对查询性能优化最有效的手段了,索引能够轻易将查询性能提高几个数量级,最优的索引会比
较好的索引性能要好2个数量级。
1 索引的类型
(1) B-Tree
不出意外,这里提到的索引都是指 B-
- 日期函数
百合不是茶
oraclesql日期函数查询
ORACLE日期时间函数大全
TO_DATE格式(以时间:2007-11-02 13:45:25为例)
Year:
yy two digits 两位年 显示值:07
yyy three digits 三位年 显示值:007
- 线程优先级
bijian1013
javathread多线程java多线程
多线程运行时需要定义线程运行的先后顺序。
线程优先级是用数字表示,数字越大线程优先级越高,取值在1到10,默认优先级为5。
实例:
package com.bijian.study;
/**
* 因为在代码段当中把线程B的优先级设置高于线程A,所以运行结果先执行线程B的run()方法后再执行线程A的run()方法
* 但在实际中,JAVA的优先级不准,强烈不建议用此方法来控制执
- 适配器模式和代理模式的区别
bijian1013
java设计模式
一.简介 适配器模式:适配器模式(英语:adapter pattern)有时候也称包装样式或者包装。将一个类的接口转接成用户所期待的。一个适配使得因接口不兼容而不能在一起工作的类工作在一起,做法是将类别自己的接口包裹在一个已存在的类中。 &nbs
- 【持久化框架MyBatis3三】MyBatis3 SQL映射配置文件
bit1129
Mybatis3
SQL映射配置文件一方面类似于Hibernate的映射配置文件,通过定义实体与关系表的列之间的对应关系。另一方面使用<select>,<insert>,<delete>,<update>元素定义增删改查的SQL语句,
这些元素包含三方面内容
1. 要执行的SQL语句
2. SQL语句的入参,比如查询条件
3. SQL语句的返回结果
- oracle大数据表复制备份个人经验
bitcarter
oracle大表备份大表数据复制
前提:
数据库仓库A(就拿oracle11g为例)中有两个用户user1和user2,现在有user1中有表ldm_table1,且表ldm_table1有数据5千万以上,ldm_table1中的数据是从其他库B(数据源)中抽取过来的,前期业务理解不够或者需求有变,数据有变动需要重新从B中抽取数据到A库表ldm_table1中。
- HTTP加速器varnish安装小记
ronin47
http varnish 加速
上午共享的那个varnish安装手册,个人看了下,有点不知所云,好吧~看来还是先安装玩玩!
苦逼公司服务器没法连外网,不能用什么wget或yum命令直接下载安装,每每看到别人博客贴出的在线安装代码时,总有一股羡慕嫉妒“恨”冒了出来。。。好吧,既然没法上外网,那只能麻烦点通过下载源码来编译安装了!
Varnish 3.0.4下载地址: http://repo.varnish-cache.org/
- java-73-输入一个字符串,输出该字符串中对称的子字符串的最大长度
bylijinnan
java
public class LongestSymmtricalLength {
/*
* Q75题目:输入一个字符串,输出该字符串中对称的子字符串的最大长度。
* 比如输入字符串“google”,由于该字符串里最长的对称子字符串是“goog”,因此输出4。
*/
public static void main(String[] args) {
Str
- 学习编程的一点感想
Cb123456
编程感想Gis
写点感想,总结一些,也顺便激励一些自己.现在就是复习阶段,也做做项目.
本专业是GIS专业,当初觉得本专业太水,靠这个会活不下去的,所以就报了培训班。学习的时候,进入状态很慢,而且当初进去的时候,已经上到Java高级阶段了,所以.....,呵呵,之后有点感觉了,不过,还是不好好写代码,还眼高手低的,有
- [能源与安全]美国与中国
comsci
能源
现在有一个局面:地球上的石油只剩下N桶,这些油只够让中国和美国这两个国家中的一个顺利过渡到宇宙时代,但是如果这两个国家为争夺这些石油而发生战争,其结果是两个国家都无法平稳过渡到宇宙时代。。。。而且在战争中,剩下的石油也会被快速消耗在战争中,结果是两败俱伤。。。
在这个大
- SEMI-JOIN执行计划突然变成HASH JOIN了 的原因分析
cwqcwqmax9
oracle
甲说:
A B两个表总数据量都很大,在百万以上。
idx1 idx2字段表示是索引字段
A B 两表上都有
col1字段表示普通字段
select xxx from A
where A.idx1 between mmm and nnn
and exists (select 1 from B where B.idx2 =
- SpringMVC-ajax返回值乱码解决方案
dashuaifu
AjaxspringMVCresponse中文乱码
SpringMVC-ajax返回值乱码解决方案
一:(自己总结,测试过可行)
ajax返回如果含有中文汉字,则使用:(如下例:)
@RequestMapping(value="/xxx.do") public @ResponseBody void getPunishReasonB
- Linux系统中查看日志的常用命令
dcj3sjt126com
OS
因为在日常的工作中,出问题的时候查看日志是每个管理员的习惯,作为初学者,为了以后的需要,我今天将下面这些查看命令共享给各位
cat
tail -f
日 志 文 件 说 明
/var/log/message 系统启动后的信息和错误日志,是Red Hat Linux中最常用的日志之一
/var/log/secure 与安全相关的日志信息
/var/log/maillog 与邮件相关的日志信
- [应用结构]应用
dcj3sjt126com
PHPyii2
应用主体
应用主体是管理 Yii 应用系统整体结构和生命周期的对象。 每个Yii应用系统只能包含一个应用主体,应用主体在 入口脚本中创建并能通过表达式 \Yii::$app 全局范围内访问。
补充: 当我们说"一个应用",它可能是一个应用主体对象,也可能是一个应用系统,是根据上下文来决定[译:中文为避免歧义,Application翻译为应
- assertThat用法
eksliang
JUnitassertThat
junit4.0 assertThat用法
一般匹配符1、assertThat( testedNumber, allOf( greaterThan(8), lessThan(16) ) );
注释: allOf匹配符表明如果接下来的所有条件必须都成立测试才通过,相当于“与”(&&)
2、assertThat( testedNumber, anyOf( g
- android点滴2
gundumw100
应用服务器android网络应用OSHTC
如何让Drawable绕着中心旋转?
Animation a = new RotateAnimation(0.0f, 360.0f,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,0.5f);
a.setRepeatCount(-1);
a.setDuration(1000);
如何控制Andro
- 超简洁的CSS下拉菜单
ini
htmlWeb工作html5css
效果体验:http://hovertree.com/texiao/css/3.htmHTML文件:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>简洁的HTML+CSS下拉菜单-HoverTree</title>
- kafka consumer防止数据丢失
kane_xie
kafkaoffset commit
kafka最初是被LinkedIn设计用来处理log的分布式消息系统,因此它的着眼点不在数据的安全性(log偶尔丢几条无所谓),换句话说kafka并不能完全保证数据不丢失。
尽管kafka官网声称能够保证at-least-once,但如果consumer进程数小于partition_num,这个结论不一定成立。
考虑这样一个case,partiton_num=2
- @Repository、@Service、@Controller 和 @Component
mhtbbx
DAOspringbeanprototype
@Repository、@Service、@Controller 和 @Component 将类标识为Bean
Spring 自 2.0 版本开始,陆续引入了一些注解用于简化 Spring 的开发。@Repository注解便属于最先引入的一批,它用于将数据访问层 (DAO 层 ) 的类标识为 Spring Bean。具体只需将该注解标注在 DAO类上即可。同时,为了让 Spring 能够扫描类
- java 多线程高并发读写控制 误区
qifeifei
java thread
先看一下下面的错误代码,对写加了synchronized控制,保证了写的安全,但是问题在哪里呢?
public class testTh7 {
private String data;
public String read(){
System.out.println(Thread.currentThread().getName() + "read data "
- mongodb replica set(副本集)设置步骤
tcrct
javamongodb
网上已经有一大堆的设置步骤的了,根据我遇到的问题,整理一下,如下:
首先先去下载一个mongodb最新版,目前最新版应该是2.6
cd /usr/local/bin
wget http://fastdl.mongodb.org/linux/mongodb-linux-x86_64-2.6.0.tgz
tar -zxvf mongodb-linux-x86_64-2.6.0.t
- rust学习笔记
wudixiaotie
学习笔记
1.rust里绑定变量是let,默认绑定了的变量是不可更改的,所以如果想让变量可变就要加上mut。
let x = 1; let mut y = 2;
2.match 相当于erlang中的case,但是case的每一项后都是分号,但是rust的match却是逗号。
3.match 的每一项最后都要加逗号,但是最后一项不加也不会报错,所有结尾加逗号的用法都是类似。
4.每个语句结尾都要加分