目录

NIO三大组件

一. ByteBuffer

基本用法

DirectByteBuffer与HeapByteBuffer对比

字符串转ByteBuffer

ByteBuffer.wrap(byte[] )

粘包与拆包

文件编程

零拷贝transferTo

二. 阻塞与非阻塞Channel

三. Selector

SelectionKey(重点)

SelectionKey四种类型

key.cancel()

事件类型

iter.remove()

selectKeys中的事件要么处理、要么取消

key.cancel()的应用场景

selectionKey附件

TCP编程都要考虑的:消息边界处理

ByteBuffer大小分配

写出内容过多问题-使用write事件(重点)

通过可写事件-分批多次进行大文件写出

多路复用器

selector.select()如何退出阻塞

NIO多线程优化

阻塞队列还可以放入Runnable任务

NIO概念剖析

BIO与NIO

IO模型

阻塞read()

非阻塞read()

异步IO

内存映射与零拷贝

mmap+write

sendFile

再次对比DirectByteBuffer与HeapByteBuffer

为何IO操作都需要将JVM内存数据拷贝到堆外内存?

那为什么Java不全部通过JNI操作堆外内存来写代码呢?

普通BIO

DirectByteBuffer存在的意义(重点)

没有mmap内存映射产生的DirectByteBuffer对象之前

直接内存的开辟示意图

Nio如何管理堆外内存的释放

回收流程

Netty的异步调用与异步IO模型

视频链接


NIO三大组件

NIO三大组件:Channel、Buffer、Selector

Selector适合channel连接数特别多,但是每个channel上来回的流量低的场景

一. ByteBuffer

基本用法

int count = channel.read(buffer),如果返回-1,表示channel中的数据读取完毕了

  • 在写入模式下,limit就是能最大的写入位置
  • 在读取模式下,limit就是最大的读取位置

compact()两个作用:压缩、切换到写模式

DirectByteBuffer与HeapByteBuffer对比

HeapByteBuffer因为是分配在JVM堆上的,所以如果一轮gc这个HeapByteBuffer还没有被处理完,就不能被回收,那么如果此HeapByteBuffer在新生代,就会被通过复制算法,复制到s区(产生多次数据拷贝),这就是HeapByteBuffer的使用,会受到gc影响的一种表现

DirectByteBuffer,是分配在JVM堆外的,是要调用,系统调用,直接在JVM进程堆(JVM堆外)中分配的,所以分配效率较HeapByteBuffer低一些,但是因为JVM进程堆不受gc影响,不会因为发生了GC,而导致DirectByteBuffer对应的堆外空间被到处复制转移(复制转移是会产生开销的)

字符串转ByteBuffer

第一种模式写入数据后,要先flip()切换为读模式,否则就会读到空数据

ByteBuffer.wrap(byte[] )

wrap效果等于第二种模式,包装以后,也会直接切换为读模式

粘包与拆包

发送方,肯定是想一次攒多条消息一起发送,效率更高,所以就是因为发送方想一次攒一波儿数据一起发,从而导致接收方,产生了粘包的可能

比如上面,发送方可能就是把第8、9、10行的三个数据包,一起发送给了接收方

文件编程

channel的写入能力是有上限的,如果channel对应的本地发送缓冲区满了,tcp还没来得及把堆在发送缓冲区中的数据发出去,此时channel.write()就往发送缓冲区写入数据就会一直写入0字节,所以要通过上面的hasRemaining()的方式,分多次往channel中写入

零拷贝transferTo

当要传输的文件大于2G时,要分多次传输

我觉得上面函数的第二个参数,应该写size,不应该是left

二. 阻塞与非阻塞Channel

IO的api默认是阻塞模式,如果想使用非阻塞NIO,那么ServerSocketChannel和SocketChannel都要分别设置非阻塞模式

三. Selector

SelectionKey(重点)

无论是serverSocketChannel,还是socketChannel,往selector上注册后,都会返回唯一一个魏颖的SelectionKey注册键

  • serverSocketChannel对应唯一一个SelectionKey,可以关注accept事件
  • 从每个socketChannel对应SelectionKey身上,可以取出唯一对应的那个socketChannel,和一个attachment附件
  • 一个socketChannel对应的唯一SelectionKey,可以同时关注read事件和write事件
  • 当socketChannel上无论有read事件,还是write事件时,selector.select()都会将这个socketChannel对应的唯一SelectionKey返回。但是可以通过key.readable()或者key.writeable()判断此时返回的事件究竟是何种类型

SelectionKey四种类型

sscKey关注了accept事件

那么当客户端有新的连接进来时,sscKey这个SelectionKey就会出现在selector.select()返回的SelectionKey的集合中。也就是说,此时代码B处的某一个key才可能是sscKey

可以看到,当有一个连接事件进来时,selector.select()返回的SelectionKey还是第28行对应的sscKey,只不过在第28行时,sscKey管理的serverSocketChannel上还没有accept事件达到。

而当有新的客户端连接serverSocketChannel时,就代表sscKey管理的serverSocketChannel上有accept事件达到了,此时,selector.select()返回的SelectionKey还是第28行对应的sscKey,此时,通过sscKey就能拿到这个新达到的accept事件

key.cancel()

如果有了事件不处理,上面的代码就会一直死循环,因为selector.select()一直会把sscKey给返回

如果拿到事件就是不想处理这个事件,可以cancel

此时key.cancel()实现的效果是,直接把此selectionKey从selector身上拿掉了,也就是说,以后本selector就不再管理此selectionKey对应的channel了,也就是说,以后本selector就不再监控来自这个channel上的任何事件了(这个channel就放养了,无人看管,无人关心了)

事件类型

iter.remove()

当第42行,将accept事件处理以后,就会将sscKey@59a6e353这个selectionKey上的accept事件给去掉

sscKey@59a6e353这个selectionKey此时,还在绿色框中,也就是selector.select()时,还是会将这个selectionKey给拿出来,但是这个selectionKey上此时已经没有accept事件了

第二轮while循环,此时selector.select()返回的集合中有两个selectionKey,一个sscKey和scKey,只不过sscKey上的accept事件已经不存在了,在上一轮while循环被取走了,所以此时在想通过sscKey上取accept事件就取不到了,所以上面代码第42行会返回null,导致代码第43行报了NPE

讲了这么多,我们处理完一个selector.select()返回的集合中的某个selectionKey身上挂载的事件后,就要把这个selectionKey,从绿色框的selectionKey集合中给移除

右边红色框,就是每个selector的红黑树集合,这个保存着所有注册到本selector身上的channel(包括serverSocketChannel和socketChannel)

右边绿色框,就是每次epoll_wait()返回的“有事件到达的selectionKey集合”,只不过每次用户空间把这个集合取过去后,处理完每个selectionKey当前身上挂载的事件后,需要用户空间手动调用iter.remove()把这个selectionKey,从“有事件到达的selectionKey集合”中手动删除

selectKeys中的事件要么处理、要么取消

当客户端连接强制断开时(比如客户端直接宕机了),服务端再通过客户端的channel去read()就会抛异常,会导致服务端线程挂掉,所以服务端需要catch这个IO异常

key.cancel()的应用场景

客户端连接关闭后,会引发一个Read事件,读取这个Read事件会抛出IOExcepiton

所以此时要做的就是,通过调用key.cancel(),取消此selectionKey背后对应的channel,在selector身上的注册,让selector不再管理这个已经死了的客户端channel了

不管是客户端强制断开,还是正常channel.close(),服务端都会受到一个read事件

只不过异常断开时,服务端调用channel.read()会抛出IOException。正常断开时,服务端调用channel.read()会返回-1。

但是不管是客户端连接时强制断开,还是正常channel.close()导致的断开,服务端都要通过key.cancel(),把这个channel对应的SelectionKey从selector的红黑树中删除,也就是,删除这个channel在selector身上的注册行为

selectionKey附件

SelectionKey - Channel - attachment,都是一一对应的

通过这种方式,给每个channel附带一个唯一对应的ByteBuffer

TCP编程都要考虑的:消息边界处理

通过fileSize(固定字节数) + fileBuffer(可变字节数)的形式,来处理消息边界问题。

http也是这种形式,有contentLength请求头

ByteBuffer大小分配

写出内容过多问题-使用write事件(重点)

可以看到发送能力是有限的,当发送缓冲区写满了,第38行的写入就会写不进去了,所以返回的写入字节数是0

这样的实现模式,本身是没有问题的,如果3000w字节的数据,如果没有发送完,那么左侧的epoll处理主线程就会一直卡在上图红框的while循环中,别的channel的话,epoll处理主线程此时就无暇去处理它们。这不符合我们非阻塞IO的设计思想

我们希望是,当发送缓冲区满时,epoll处理主线程就不一直卡在上图红框的while循环中(产生空转,因为只要发送缓冲区满,while循环就会一直空转耗费CPU),而是去处理别的channel的事件

(等上一个channel的发送缓冲区空了,触发一个Write事件,表示发送缓冲区空了,可以写了,此时,epoll处理主线程再对这个channel写入上一轮没有写完的数据)

此时,写事件会覆盖关注的读事件

此时,就表示我们通过scKey这一个SelectionKey,同时关注了Read和Write事件

此后,如果此scKey对应的channel对应的发送缓冲区空了以后,就会触发一个Write的可写事件,然后代码就会进入第47行的if判断中,开始把上一轮没有写完的buffer中的剩余数据,继续去写

这样,就把原来的while循环写,变成了对Write事件的多次触发

大数据buffer写完后,还需要将它从附件处删除,同时取消关注Write事件

可写事件Write的触发机制,就是当发送缓冲区空时,可以写数据了,那么就会触发可写事件,从而epoll.select()就能拿到这个可写事件

通过可写事件-分批多次进行大文件写出

可写事件,只有在要写的数据太多时,才去使用

通过可写事件,来处理channel.write()一次写不完一整个大文件的情况。

那么就可以知道,如果你每次就写个几个字节,几十上百个字节,那么直接通过channel.write(),把数据一次性写出去就好了,根本不需要可写事件Write的相关逻辑参与

可以看到,当这个channel对应的发送缓冲区写满的时候,再通过channel.write()来往channel对应的发送缓冲区中写数据,会直接返回0,也就是写入了0字节。

后续,写入线程就会多次空转,尝试往发送缓冲区中写入数据,那么我们可以通过,写入发送缓冲区写满了以后,就通过selectionKey关注这个channel的write事件,那么当这个channel对应的发送缓冲区又可以写数据时,selector.select()就会探测到一个write可写事件,这个时候,我们的写入线程,再通过channel.write()来往channel对应的发送缓冲区中写数据,就能避免写入线程因为发送换成区满而产生的多次空转问题

那么此时,如果第36行一次没有把全部大文件写完,那么就会多次进入第47行开始的可写事件,也就是相当于,把原来的while循环写,转化为了多次可写事件的处理

因为key身上挂的附件buffer,可能大小有1个G,当我们把这个附件buffer中的数据全部写完了以后,要取消附件buffer的挂载,让JVM去gc回收这篇1个G的大内存,不然一直让它占着1个G的大内存是非常不合适的

另外,我们把1个G的内容写完了以后,还要取消关注可写事件,等到后续又有新的大文件的写需求时,先尝试写一次,如果又没有写完,再让selector去关注这个channel上的可写事件

多路复用器

selector.select()如何退出阻塞

NIO多线程优化

这里实际上,就是boss线程,再往Worker的阻塞队列中,投入了一个Runnable任务,并唤醒在selector.select()处阻塞worker线程

阻塞队列还可以放入Runnable任务

当selector.select()处于阻塞状态时,直接socketChannel.register(selector)来往selector上注册事件监听时,也会被阻塞住

只有先让worker线程,从selector.select()被唤醒,然后worker线程自己从阻塞队列中去task执行

注意,当前阻塞队列中,放的是一个Runnable任务

NIO概念剖析

BIO与NIO

BIO是更加高层次的API,比如它们不会关心到发送缓冲区,接收缓冲区的逻辑。比如,前面的例子,写大量数据时,就会关心发送区慢了,本次就不发了,而是去关注write事件,等下一次write事件达到时,再去发

IO模型

阻塞read()

非阻塞read()

比如有网卡的数据真正达到网卡缓冲区,需要被从网卡缓冲区复制到内核缓冲区时,用户线程调用的read()一样还是会被阻塞住

多路复用器

BIO 同步阻塞、NIO 和 EPOLL都是同步非阻塞

异步IO

异步,一定是非阻塞的。没有异步阻塞的说法

内存映射与零拷贝

mmap+write

使用MappedByteBuffer

内存映射

内存文件映射适用于对大文件的读写。虚拟地址空间有一块区域: “Memory mapped region for shared libraries” ,这段区域就是在内存映射文件的时候将某一段的虚拟地址和文件对象的某一部分建立起映射关系,此时并没有拷贝数据到内存中去,而是当进程代码第一次引用这段代码内的虚拟地址时,触发了缺页异常,这时候OS根据映射关系直接将文件的相关部分数据拷贝到进程的用户私有空间中去

只用3次拷贝,减少了1次

rocketmq是mmap+write,kafka是sendFile

sendFile

数据,不在经过用户空间,

零拷贝,指的是不再需要把数据,拷贝到用户空间内存(JVM内存,就是用户空间内存

不再需要CPU拷贝,只需要DMA去执行拷贝动作

再次对比DirectByteBuffer与HeapByteBuffer

为何IO操作都需要将JVM内存数据拷贝到堆外内存?

  • 因为JNI操作的内存空间数据,不能随便被GC从而挪动位置,所以Java程序,不能通过JNI直接分配内存空间来进行代码逻辑的书写,因为可能上一秒通过JNI记录的是固定地址,而GC会导致固定地址内部的数据被挪走到别处
  • 但是,Java程序可以通过JNI分配堆外内存,并直接操作堆外内存,堆外内存的地址就是固定的,不会随意被GC动作给挪走

那为什么Java不全部通过JNI操作堆外内存来写代码呢?
  • 因为,Java的特色就是能自动进行垃圾回收,而只有堆内内存空间的数据,才能通过JVM垃圾收集器进行自动的垃圾回收
  • 所以,如果Java全部使用JNI分配堆外内存,那么就没有办法再使用JVM提供的垃圾收集器进行自动的垃圾回收
  • 而JNI操作的堆外内存,才是直面各底层硬件的内存,所有的硬件上的数据读写都要先经过堆外内存

JVM堆内内存,JNI是不能直接访问和操作的。所以,JVM堆内空间要想拿到磁盘中的数据,必须先通过把磁盘数据拿到内核缓冲区中来,然后Java代码再通过read()系统调用,从内核缓冲区中拿数据到JVM堆内来

不能直接通过调用JNI方法,把内核缓冲区中的数据,写入到JVM堆内。只能是磁盘数据先到内核缓冲区,然后用户通过read()系统调用,把内核缓冲区中的数据,再读取JVM堆内

DirectByteBuffer分配的堆外内存,本质是调用操作系统的malloc(),分配的JVM进程的用户堆空间的内存

因为,DirectByteBuffer对应的堆外地址,和内核缓冲区,共用同一个片物理页。所以,我们程序员通过Java代码往DirectByteBuffer对应的堆外地址put了一些数据,就相当于直接写入了内核缓冲区,而且put操作,还不会产生系统调用

普通BIO

但是,JDK还是提供了一种走捷径的方式,通过内存映射mmap,拿到文件映射的address,从而用户可以通过Java代码,直接读写这个address对应的堆外内存空间

而不用先在JVM堆内用户空间搞一个byte[],然后再把这个byte[]中的内容写入堆外内存空间,或者把堆外内存空间的数据读入byte[]中,从而减少了数据在JVM堆内的拷贝过程

DirectByteBuffer存在的意义(重点)

  • 有了mmap内存映射产生的DirectByteBuffer对象,使得Java程序,也有了能直接操作堆外内存地址空间的机会
  • 堆外内存空间才是直接面对各个底层硬件的,比如底层的磁盘,或者网卡
  • 只有堆外内存空间的数据,才是能直接去往各底层硬件读写的,JVM堆内空间数据,是不能直接往各底层硬件读写的

没有mmap内存映射产生的DirectByteBuffer对象之前

Java程序,想将数据写入到网卡,只能先在Java用户空间生成byte[]并写满数据,然后通过调用write()系统调用先把用户空间的byte[]写入到堆外内存空间,然后通过系统调用flush(),把堆外内存空间的数据,写入到网卡缓冲区

直接内存的开辟示意图

比如定义:DirectByteBuffer dbb = ByteBuffer.allocateDirect(1024);底层是:

public static ByteBuffer allocateDirect(int capacity) {return new DirectByteBuffer(capacity);
}

示意图:

Nio如何管理堆外内存的释放

回收流程

NIO中如何使用虚引用管理堆外内存原理_虚引用 堆外内存-CSDN博客

Netty的异步调用与异步IO模型

Netty的异步指的是调用方式的异步,不是指的IO模型的异步。指的是请求的发送,和响应的接收,分别是不同的IO线程在处理

Netty的IO模型还是基于多路复用器的同步非阻塞IO


 

视频链接

黑马程序员Netty全套教程, netty深入浅出Java网络编程教程_哔哩哔哩_bilibili

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/100429.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/100429.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/100429.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

知识点17:多Agent系统架构设计模式

知识点17:多Agent系统架构设计模式 核心概念 掌握系统架构思维,理解多Agent系统的设计原则和模式 架构设计核心概念 在构建多Agent系统时,良好的架构设计是系统成功的关键。本节将介绍多Agent系统架构设计中的核心概念,包括单点瓶…

数据库造神计划第五天---增删改查(CRUD)(1)

🔥个人主页:寻星探路 🎬作者简介:Java研发方向学习者 📖个人专栏:《从青铜到王者,就差这讲数据结构!!!》、 《JAVA(SE)----如此简单&a…

基于Vue3的人工智能生成内容标识服务平台前端页面设计

效果图&#xff1a;素材库&#xff1a;App.vue<template><div id"app"><!-- 头部导航 --><Header /><!-- 主要内容区域 --><main class"main-content"><div class"container"><!-- 强制性国家标准…

使用 MyCat 实现 MySQL 主从读写分离

文章目录使用 MyCat 实现 MySQL 主从读写分离完整指南一、MySQL 读写分离基础概述1.1 读写分离工作原理1.2 为什么需要读写分离1.3 读写分离的两种实现方式主流读写分离中间件对比二、MyCat 中间件简介2.1 MyCat 核心功能2.2 MyCat 适用场景三、环境准备与 MyCat 安装3.1 前提&…

物联网传感器检测实验

/*------------------------------------------------------------------------------ * @文件名 : handle * @描述 : 用户处理函数 * @作者 : 物联网项目组 * @日期 : 2023/04/01 * @版本 : V0.0.2 *****************************…

什么是dirsearch、xray、durpsuite、sqlmap?

你提到的 dirsearch、xray、durpsuite&#xff08;可能为笔误&#xff0c;推测是 ​​Burp Suite​​&#xff09;和 sqlmap 均为网络安全领域中常用的工具&#xff0c;主要用于 Web 应用的安全测试、漏洞检测或渗透测试。以下分别详细说明&#xff1a;​​1. dirsearch​​​​…

lamp脚本部署

#!/bin/bash #关闭防火墙和selinux systemctl stop firewalld systemctl disable firewalld setenforce 0 #配置yum网络源 echo “正在配置yum仓库” rm -rf /etc/yum.repos.d/* wget -O /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo &am…

Redis Hash数据类型深度解析:从命令、原理到实战场景

前言 在Redis的众多数据结构中&#xff0c;Hash&#xff08;哈希&#xff09;类型占据着至关重要的地位。Redis本身就是一个高性能的键值&#xff08;Key-Value&#xff09;数据库&#xff0c;其底层的键值对便是通过哈希方式组织的。而Hash数据类型则更进一步&#xff0c;它允…

【C++实战⑬】解锁C++文件操作:从基础到实战的进阶之路

目录一、文件操作的基本概念1.1 文件的分类与打开方式1.2 文件流的概念与相关类&#xff08;ifstream、ofstream、fstream&#xff09;1.3 文件操作的基本流程二、文本文件的读写实战2.1 文本文件的打开与关闭2.2 文本文件的写入操作&#xff08;<< 运算符、write 函数&a…

从C++开始的编程生活(9)——模板初阶

前言 本系列文章承接C语言的学习&#xff0c;需要有C语言的基础才能学会哦~ 第8篇主要讲的是有关于C的模板初阶。 C才起步&#xff0c;都很简单&#xff01;&#xff01; 目录 前言 模板初阶 基本语法 函数模板的实例化 显式实例化的作用 类模板 基本语法 模板初阶 模板…

计算机网络——传输层(25王道最新版)

传输层传输层提供的服务进程 端口号 传输层协议之间的关系socket套接字有链接 VS 无连接 | 可靠 VS 不可靠UDP数据报及检验数据报格式检验方法TCPTCP协议的三大阶段TCP报文段格式&#xff08;很重要&#xff09;建立连接&#xff08;三次握手&#xff09;&#xff08;超级超级重…

羽毛球地板:从专业运动场景到全民健身市场的技术跃迁与产业重构

在全球体育产业向“专业化大众化”双轨并行的趋势下&#xff0c;羽毛球地板作为运动场景的核心基础设施&#xff0c;正经历从单一功能型产品向“性能优化场景适配智能管理”一体化解决方案的转型。据QYResearch统计&#xff0c;2031年全球羽毛球地板市场规模将达15.95亿元&…

R 语言查看类库源码的方法

你想查看 getGEO&#xff08;来自 R 语言 GEOquery 包&#xff09;的源码&#xff0c;这能帮你更好理解其工作原理和数据处理细节。由于 getGEO 是 R 函数&#xff0c;查看方法与 Python 有所不同。下面为你提供几种主要方法。 方法 适用场景 关键命令/操作 在 R 控制台直接查看…

SQL,posexplode 用法示例

示例1 -- 创建测试数据 WITH test_data AS (SELECT array(apple, banana, cherry) as fruits ) SELECT pos, col FROM test_data LATERAL VIEW posexplode(fruits) t AS pos, col;结果 pos | col ----|------- 0 | apple 1 | banana 2 | cherry示例2 -- 假设有一个用户表…

数据库造神计划第十天---数据库约束(1)

&#x1f525;个人主页&#xff1a;寻星探路 &#x1f3ac;作者简介&#xff1a;Java研发方向学习者 &#x1f4d6;个人专栏&#xff1a;《从青铜到王者&#xff0c;就差这讲数据结构&#xff01;&#xff01;&#xff01;》、 《JAVA&#xff08;SE&#xff09;----如此简单&a…

知微传感Dkam系列3D相机SDK例程篇:CSharp连接相机及保存数据

序言 写在前面 本人从事机器视觉细分的3D相机行业。编写此系列文章主要目的有&#xff1a; 1、便利他人应用相机&#xff0c;本系列文章包含公司所出售相机的SDK的使用例程及详细注释&#xff1b;2、促进行业发展及交流。 知微传感Dkam系列3D相机可以应用于定位分拣、焊接焊缝提…

[笔记] 系统分析师 第十二章 软件架构设计(分析师主要工作)

文章目录前言12.1 软件架构概述12.1.1 软件架构的意义12.1.2 软件架构的发展史12.2 软件架构建模12.3 软件架构风格12.3.1 软件架构风格概述12.3.2 数据流体系结构风格1.批处理体系结构风格2.管道-过滤体系结构风格12.3.3 调用/返回体系结构风格1.主程序/子程序风格2.面向对象体…

C++---存储周期,作用域,链接性

在C程序设计中&#xff0c;变量的行为不仅由其类型决定&#xff0c;还由存储周期&#xff08;变量在内存中存在的时间&#xff09;、作用域&#xff08;变量可被访问的代码范围&#xff09;和链接性&#xff08;变量在多文件程序中的可见性&#xff09;共同约束。 一、存储周期…

基于Python的商品爬取与可视化系统

本系统是基于Python的商品数据爬取与价格分析可视化系统&#xff0c;集成了数据爬取、数据存储、数据展示和可视化分析等功能。下面介绍一下系统主要功能和技术栈。一、主要功能&#xff1a;1、数据爬取功能 支持淘宝美妆商品数据爬取 可配置搜索关键词和爬取页数 实时显示爬取…

联邦学习过程中,了解清楚影响准确率的因素有哪些也很重要

影响模型准确率的因素有很多&#xff0c;下面是一些主要的因素&#xff0c;它们可以从数据、模型设计、训练策略以及超参数等多个层面来考虑。1. 学习率作用&#xff1a;学习率直接影响模型的训练速度、稳定性和最终表现。过高的学习率可能导致模型不收敛或收敛不稳定&#xff…