NIO基础

一、NIO基础

Java New IO是从Java1.4版本开始引入的一个新的IO api,可以替代以往的标准IO,NIO相比原来的IO有同样的作用和目的,但是使用的方式完全不一样,NIO是面向缓冲区的,基于通道的IO操作,这也让它比传统IO有着更为高效的读写。

1.1 IO和NIO的主要区别

IONIO
面向流面向缓冲
区阻塞IO非阻塞IO
选择器

1.1.1 传统IO的流

以下用图来简单理解一下,在传统IO中当App要对网络,磁盘中的文件进行读写的时候,它们必须建立一个连接,流到底是一个什么样的概念呢,我们可以先把它想象成自来水,家里要用自来水,需要有水管,让水从水管过来到家里,起到一个运输的作用。

所以当我们文件中的数据需要输入到App里面时,它们就会建立一个输入的管道。而当我们的App有数据需要写入到文件系统的时候,就会建立一个输出的管道,这两条管道就是我们的输入流和输出流。那水从来没有逆流而上的呀,所以它们都是单向管道。这么一讲,是不是就很好懂了呢😁?

1.1.2 NIO

也是同样的文件系统和App,不过此时把流换成了一个channel,现在我们可以先认为它就是一条铁道,那我们知道铁道本身是不能传递货物的呀,所以我们需要一个载具—火车(也就是缓冲区),App需要的数据就由这个名叫缓冲区的载具运输过来。那火车是可以开过来,也可以开回去的,所以NIO是双向传输的。

1.2 Buffer

NIO的核心在于,通道(channel)和缓冲区(buffer)两个。通道是打开到IO设备的连接。使用时需要获取用于连接IO设备的通道以及用于容纳数据的缓冲区,然后通过操作缓冲区对数据进行处理。(其实就是上面那张图的事儿,或者一句话就是一个负责传输,一个负责存储)。

缓冲区是Java.nio包定义好的,所有缓冲区都是Buffer抽象类的子类。Buffer根据数据类型不同,常用子类分别是基本数据类型除了Boolean外的xxxBuffer(IntBuffer,DoubleBuffer···等)。不同的Buffer类它们的管理方式都是相同的,获取对象的方法都是

// 创建一个容量为capacity的xxx类型的Buffer对象
static xxxBuffer allocate(int capacity)

而且缓冲区提供了两个核心方法:get()和put(),put方法是将数据存入到缓冲区,而get是获取缓冲区的数据。

此时我们用代码看一下

public class BufferTest {@Testpublic void testBuffer(){// 创建缓冲区对象ByteBuffer byteBuffer = ByteBuffer.allocate(1024);}
}

点进去ByteBuffer,会看到这个东西是继承了Buffer类的

public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer>

此时继续点进去Buffer类,第一眼看到的是有几个自带的属性

1.2.1 buffer的基本属性

① capacity容量
表示Buffer的最大数据容量,这个值不能为负。而且创建后是不能更改的。

② limit限制
第一个不能读取或写入的数据的索引,位于此索引后的数据不可读写。这个数值不能为负且不能超过capacity,如上图中第三个缓冲区,在下标为5之后的数据块均不能读写,那limit为5

③ position位置
下一个要读取或写入的数据的索引,这个数值不能为负且不能超过capacity,如图中第二个缓冲区,前面5块写完成,此时第6个数据块的下标为5,所以position为5

④ mark标记/reset重置
mark是一个索引,通过Buffer的mark()方法指定Buffer中一个特定的position后,可以通过reset()方法重置到这个position,这个通过代码来解释会比较好说明

1.2.2 code部分(非常简单)

1.首先我们创建一个缓冲区对象,然后把它的属性打印出来

ByteBuffer byteBuffer = ByteBuffer.allocate(10);
System.out.println(byteBuffer.position());
System.out.println(byteBuffer.capacity());
System.out.println(byteBuffer.limit());运行结果:0,10,10

2.执行一个put()方法,来把一个字符丢进去

String str = "abcde";
byteBuffer.put(str.getBytes());
System.out.println(byteBuffer.position());
System.out.println(byteBuffer.capacity());
System.out.println(byteBuffer.limit());运行结果:5,10,10
"abcde"长度为5,position已经变化,其它不变

3.使用flip()切换为读模式

byteBuffer.flip();
System.out.println(byteBuffer.position());
System.out.println(byteBuffer.capacity());
System.out.println(byteBuffer.limit());运行结果:0,10,5

此时position变成为0了,因为一开始的5,是因为这时候要写的是下标为5的数据块,而转换成读模式后,第一个读的明显是下标为0的数据块呀。limit的数值也变成了5,因为当前能读到的数据从下标为5开始就木有了,所以limit为5

4.简单获取一下buffer中的数据

byte[] array = new byte[byteBuffer.limit()];
byteBuffer.get(array);
System.out.println(new String(array,0,array.length));运行结果:abcde

5.mark() & reset()

byte[] array = new byte[byteBuffer.limit()];
byteBuffer.get(array,0,2);
System.out.println(new String(array,0,2));
System.out.println(byteBuffer.position());byteBuffer.mark();
byteBuffer.get(array,2,2);
System.out.println(new String(array,2,2));
System.out.println(byteBuffer.position());byteBuffer.reset();
System.out.println(byteBuffer.position());运行结果:ab,2,cd,4,2

其实很简单,就是第一次读取的时候,只是读取了前面两个字符,然后此时position的结果为2,然后再读取后两个,position为4,可是因为我在读取前面2个的时候进行了一个mark操作,它就自动回到我mark之前的那个读取位置而已,就是这么简单

6.其他的一些方法
rewind()方法,可重复读,clear()清空缓冲区,不过这个方法的清空缓冲区,是一种被遗忘的状态,就是说,数据仍然还存于缓冲区中,可是自动忽略掉了。此时再次读取数据,是还是可以get()到的。hasRemaining()方法就是表示剩余可操作的数据量还有多少,比如刚刚的mark的那个例子中,我reset回去之后,剩余的可操作数据就是3,因为我只读了ab,还有cde这三个。

1.2.3 直接缓冲区和非直接缓冲区

非直接缓冲区:通过allocate()方法来分配缓冲区。将缓冲区建立在JVM的内存中。
直接缓冲区:通过allocateDirect()方法分配缓冲区,将缓冲区建立在物理内存中。效率更高。

① 非直接缓冲区

应用程序想要在磁盘中读取数据时,首先它发起请求,让物理磁盘先把它的数据读到内核地址空间当中,之后这个内核空间再将这个数据copy一份到用户地址空间去。然后数据才能通过read()方法将数据返回个应用程序。而应用程序需要写数据进去,也是同理,先写到用户地址空间,然后copy到内核地址空间,再写入磁盘。此时不难发现,这个copy的操作显得十分的多余,所以非直接缓冲区的效率相对来说会低一些。

② 直接缓冲区

直接缓冲区就真的顾名思义非常直接了,写入的时候,写到物理内存映射文件中,再由它写入物理磁盘,读取也是磁盘把数据读到这个文件然后再由它读取到应用程序中即可。没有了copy的中间过程。

1.3 channel

1.3.1 扯一下概念背景

由java.nio.channels包定义,表示IO源与目标打开的链接,它本身不存在直接访问数据的能力,只能和Buffer进行交互

传统的IO由cpu来全权负责,此时这个设计在有大量文件读取操作时,CPU的利用率会被拉的非常低,因为IO操作把CPU的资源都抢占了。

在这种背景下进行了一些优化,把对cpu的连接取消,转为DMA(直接内存存取)的方式。当然DMA这个操作本身也是需要CPU进行调度的。不过这个损耗自然就会比大量的IO要小的多。

此时,就出现了通道这个概念,它是一个完全独立的处理器。专门用来负责文件的IO操作。

1.3.2 常用通道

Java为Channel接口提供的主要实现类:

FileChannel:用于读取,写入,映射和操作文件的通道
DatagramChannel:通过UDP读写网络中的数据通道
SocketChannel:通过TCP读写网络中的数据通道
ServerSocketChannel:可以监听新进来的TCP连接,对每一个新进来的连接都会创建一个SocketChannel              

获取channel的一种方式是对支持通道的对象调用getChannel()方法,支持类如下

FileInputStream
FileOutputStream
RandomAccessFile
DatagramSocket
Socket
ServerSocket

获取的其他方式是使用Files类的静态方法newByteChannel()获取字节通道。再或者是通过通道的静态方法open()打开并返回指定通道。

1.3.3 常用方法和简单使用

① 使用非直接缓冲区完成文件复制

// 创建输入输出流对象
FileInputStream fileInputStream = new FileInputStream("testPic.jpg");
FileOutputStream fileOutputStream = new FileOutputStream("testPic2.jpg");// 通过流对象获取通道channel
FileChannel inChannel = fileInputStream.getChannel();
FileChannel outChannel = fileOutputStream.getChannel();// 创建指定大小的缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);// 将通道中的数据写入到缓冲区中
while (inChannel.read(byteBuffer) != -1){// 切换成读取模式byteBuffer.flip();// 将缓冲区中的数据写到输出通道outChannel.write(byteBuffer);// 清空缓冲区byteBuffer.clear();}
//回收资源(这里为了省时间直接抛出去了,反正这段不太重要)
outChannel.close();
inChannel.close();
fileInputStream.close();
fileOutputStream.close();运行结果:就自然是复制了一个testPic2出来啦

因为代码本身不难,注释已经写得比较详细,就不展开了

② 使用直接缓冲区来完成文件的复制

注意这里的StandardOpenOption是一个枚举,表示模式,很显然这里是要选择READ读取模式。

FileChannel inChannel = FileChannel.open(Paths.get("testPic.jpg",StandardOpenOption.READ));
FileChannel outChannel = FileChannel.open(Paths.get("testPic2.jpg"),StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE);
// 进行内存映射
MappedByteBuffer inMappedBuffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
MappedByteBuffer outMapBuffer = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());// 对缓冲区进行数据的读写操作
byte[] array = new byte[inMappedBuffer.limit()];
inMappedBuffer.get(array);
outMapBuffer.put(array);// 回收资源
inChannel.close();
outChannel.close();

如果需要看一下它们两个的时间差,自己用最常规的系统时间来瞧瞧就好,在这里就不再加上了。

二、NIO非阻塞式网络通信

传统的IO流都是阻塞式的,当一个线程调用read或者write时,该线程被阻塞,直到数据被读取或者写入,该线程在此期间都是不能执行其他任务的,因此,在完成网络通信进行IO操作时,线程被阻塞,所以服务器端必须为每个客户端提供一个独立线程进行处理,当服务器端需要处理大量客户端时,性能将会急剧下降。

NIO是非阻塞的,当线程从某通道进行读写数据时,若没有数据可用,该线程可以进行其他任务。线程通常将非阻塞IO的空闲时间用于在其他通道上执行IO操作,所以单独的线程可以管理多个输入和输出通道。因此NIO可以让服务器端使用一个或有限几个线程来同时处理连接到服务器端的所有客户端。

2.1 Selector

这个选择器其实就是在客户端和服务端之间引入一个通道的注册器,比如现在我的客户端要像服务端传输数据了,客户端会给选择器去发送一个channel的注册请求,注册完成后,Selector就会去监控这个channel的IO状态(读写,连接)。只有当通道中的数据完全准备就绪,Selector才会将数据分配到服务端的某个线程去处理。

这种非阻塞性的流程就可以更好地去使用CPU的资源。提高CPU的工作效率。这个可以用收快递来说明。如果你一开始就告诉我半小时后过来取快递,而我在这时候已经到目的地了,我有可能就原地不动站着等半个小时。这个期间啥地都去不了,可是你是到了之后,才打电话告诉我过来取,那我就有了更多的自由时间。

2.2 code(阻塞性IO的网络通信)

现在我们来演示一下阻塞性IO的网络通信

2.2.1 client(阻塞性IO)

这个代码大家可以尝试这删除sChannel.shutdownOutput(),此时会发现在启动好server,运行client程序的时候,程序也会阻塞,这是因为这时服务端并无法确定你是否已经发送完成数据了,所以client端也产生了阻塞,双方就一直僵持。

还有一种方法是解阻塞,之后进行阐述。

// 1.获取通道
SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("你的IP地址",9898));
// 2.创建文件通道
FileChannel inChannel = FileChannel.open(Paths.get("C:/Users/Administrator/Desktop/testPic.jpg"),StandardOpenOption.READ);
// 3.分配指定大小的缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);// 4.发送数据,需要读取文件
while (inChannel.read(byteBuffer) != -1){byteBuffer.flip();// 将buffer的数据写入到通道中sChannel.write(byteBuffer);byteBuffer.clear();
}// 主动告诉服务端,数据已经发送完毕
sChannel.shutdownOutput();while (sChannel.read(byteBuffer) != -1){byteBuffer.flip();System.out.println("接收服务端数据成功···");byteBuffer.clear();}// 5.关闭通道
inChannel.close();
sChannel.close();

2.2.2 server(阻塞性IO)

// 1.获取通道
ServerSocketChannel ssChannel = ServerSocketChannel.open();
// 创建一个输出通道,将读取到的数据写入到输出通道中,保存为testPic2
FileChannel outChannel = FileChannel.open(Paths.get("testPic2.jpg"),StandardOpenOption.WRITE,StandardOpenOption.CREATE);
// 2.绑定端口
ssChannel.bind(new InetSocketAddress(9898));
// 3.等待客户端连接,连接成功时会得到一个通道
SocketChannel sChannel = ssChannel.accept();
// 4.创建缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
// 5.接收客户端的数据存储到本地
while (sChannel.read(byteBuffer) != -1){byteBuffer.flip();outChannel.write(byteBuffer);byteBuffer.clear();
}// 发送反馈给客户端// 向缓冲区中写入应答信息byteBuffer.put("服务端接收数据成功".getBytes());byteBuffer.flip();sChannel.write(byteBuffer);// 关闭通道
sChannel.close();
outChannel.close();
byteBuffer.clear();

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

然后再当我们的客户端运行起来,就会进行copy操作

2.3 Selector完成非阻塞IO

使用NIO完成网络通信需要三个核心对象:
channel:java.nio.channels.Channel接口,SocketChannel,ServerSocketChannel,DatagramChannel
管道相关:Pipe.SinkChannel,Pine.SourceChannel
buffer:负责存储数据
Selector:其中Selector是SelectableChannel的多路复用器,主要是用于监控SelectableChannel的IO状态

2.3.1 client(非阻塞)

// 1.获取通道,默认是阻塞的
SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("192.168.80.1",9898));// 1.1 将阻塞的套接字变成非阻塞
sChannel.configureBlocking(false);// 2.创建指定大小的缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
// 3.发送数据给服务端,直接将数据存储到缓冲区
byteBuffer.put(new Date().toString().getBytes());
// 4.将缓冲区的数据写入到sChannel
byteBuffer.flip();
sChannel.write(byteBuffer);
byteBuffer.clear();// 关闭
sChannel.close();

2.3.2 server(非阻塞)

代码的注释中已经解释了整个过程的做法,这里就不一一展开了。

// 1.获取通道
ServerSocketChannel ssChannel = ServerSocketChannel.open();
// 2.将阻塞的套接字设置为非阻塞的
ssChannel.configureBlocking(false);
// 3.绑定端口号
ssChannel.bind(new InetSocketAddress(9898));// 4.创建选择器对象
Selector selector = Selector.open();// 5.将通道注册到选择器上(这里的第二个参数为selectionKey),下面有解释
// 此时选择器就开始监听这个通道的接收时间,此时接收工作准备就绪,才开始下一步的操作
ssChannel.register(selector,SelectionKey.OP_ACCEPT);// 6.通过轮询的方式获取选择器上准备就绪的事件
// 如果大于0,至少有一个SelectionKey准备就绪
while (selector.select() > 0){// 7.获取当前选择器中所有注册的selectionKey(已经准备就绪的监听事件)Iterator<SelectionKey> selectionKeyIterator = selector.selectedKeys().iterator();// 迭代获取已经准备就绪的选择键while (selectionKeyIterator.hasNext()){// 8.获取已经准备就绪的事件SelectionKey selectionKey = selectionKeyIterator.next();if (selectionKey.isAcceptable()){// 9.调用accept方法SocketChannel sChannel = ssChannel.accept();// 将sChannel设置为非阻塞// 再次强调,整个过程不能有任何一条阻塞通道sChannel.configureBlocking(false);// 进行数据接收工作,而且把sChannel也注册上选择器让选择器来监听sChannel.register(selector,SelectionKey.OP_READ);}else if (selectionKey.isReadable()){// 如果读状态已经准备就绪,就开始读取数据// 10.获取当前选择器上读状态准备就绪的通道SocketChannel sChannel = (SocketChannel) selectionKey.channel();// 11.读取客户端发送的数据,需要先创建缓冲区ByteBuffer byteBuffer = ByteBuffer.allocate(1024);// 12.读取缓冲区的数据while (sChannel.read(byteBuffer) > 0){byteBuffer.flip();// 这里sChannel.read(byteBuffer)就是这个字节数组的长度System.out.println(new String(byteBuffer.array(),0,sChannel.read(byteBuffer)));// 清空缓冲区byteBuffer.clear();}}// 当selectionKey使用完毕需要移除,否则会一直优先selectionKeyIterator.remove();}}

当调用register方法将通道注册到选择器时,选择器对通道的监听事件需要通过第二个参数ops决定

读:SelectionKey.OP_READ(1)
写:SelectionKey.OP_WRITE(4)
连接:SelectionKey.OP_CONNECT(8)
接收:SelectionKey.OP_ACCEPT(16)

若注册时不仅仅只有一个监听事件,则需要用位或操作符连接

int selectionKeySet = SelectionKey.OP_READ|SelectionKey.OP_WRITE

而关于这个selectionKey,它表示着SelectableChannel和Selectr之间的注册关系。它也有一系列对应的方法

2.3.3 客户端的改造

引入Scanner接收输入信息,不过请注意,在测试代码中输入IDEA需要进行一些设置,具体做法是在Help-Edit Custom VM Option中加入一行
-Deditable.java.test.console=true

这样就可以输入了。

// 1.获取通道,默认是阻塞的
SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("192.168.80.1",9898));// 1.1 将阻塞的套接字变成非阻塞
sChannel.configureBlocking(false);// 2.创建指定大小的缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()){String str = scanner.next();// 3.发送数据给服务端,直接将数据存储到缓冲区byteBuffer.put((new Date().toString()+str).getBytes());// 4.将缓冲区的数据写入到sChannelbyteBuffer.flip();sChannel.write(byteBuffer);byteBuffer.clear();
}
// 关闭
sChannel.close();

这样就完成了一个问答模式的网络通信。

2.4 Pipe管道

Java NIO中的管道是两个线程之间的单向数据连接,Pipe有一个source管道和一个sink管道,数据会被写到sink,从source中获取

// 1.获取管道
Pipe pipe = Pipe.open();// 2.创建缓冲区对象
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
// 3.获取sink通道
Pipe.SinkChannel sinkChannel = pipe.sink();
byteBuffer.put("通过单向管道传输数据".getBytes());// 4.将数据写入sinkChannel
byteBuffer.flip();
sinkChannel.write(byteBuffer);
// 5.读取缓冲区中的数据
Pipe.SourceChannel sourceChannel = pipe.source();
// 6.读取sourceChannel中的数据放入到缓冲区
byteBuffer.flip();
sourceChannel.read(byteBuffer);
System.out.println(new String(byteBuffer.array(),0,sourceChannel.read(byteBuffer)));sourceChannel.close();
sinkChannel.close();运行结果就是打印了我们的那串字符"通过单向管道传输数据"

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

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

相关文章

Java学习之--类和对象

&#x1f495;粗缯大布裹生涯&#xff0c;腹有诗书气自华&#x1f495; 作者&#xff1a;Mylvzi 文章主要内容&#xff1a;Java学习之--类和对象 类和对象 类的实例化&#xff1a; 1.什么叫做类的实例化 利用类创建一个具体的对象就叫做类的实例化&#xff01; 当我们创建了…

Vulnhub系列靶机---HarryPotter-Fawkes-哈利波特系列靶机-3

文章目录 信息收集主机发现端口扫描dirsearch扫描gobuster扫描 漏洞利用缓冲区溢出edb-debugger工具msf-pattern工具 docker容器内提权tcpdump流量分析容器外- sudo漏洞提权 靶机文档&#xff1a;HarryPotter: Fawkes 下载地址&#xff1a;Download (Mirror) 难易程度&#xff…

工厂除静电除尘设备--离子风枪

静电无处不在&#xff0c;区别在于静电的多少而已。特别是工业生产过程中&#xff0c;大量的静电会有很多危害。 静电的危害有几点&#xff1a;1.引起电子设备的故障或误动作&#xff0c;造成电磁干扰。2.击穿集成电路和精密的电子元件&#xff0c;或使元件老化&#xff0c;拉…

关于一个left join的易错点

很多人在学习mysql的时候应该都出现过很多问题&#xff0c;特别是连接方面的问题应该最多&#xff0c;希望这篇文章帮助到正在找bug的你 Java报错数据返回数量出现错误 遇到这种问题一定要看日志 很明显通过left join查询除了两条数据并且为空 马上思考错误的原因&#xff0c;…

【Java基础篇 | 面向对象】--- 聊聊什么是多态(上篇)

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【JavaSE_primary】 本专栏旨在分享学习JavaSE的一点学习心得&#xff0c;欢迎大家在评论区讨论&#x1f48c; 目录 一、什么是多态二、多…

数据结构基础7:二叉树【链式结构】实现和递归思想。

二叉树的链式结构实现 一.二叉树链式结构的实现&#xff1a;1.前置说明&#xff1a;1.创建二叉树&#xff1a;2.二叉树的结构&#xff1a; 2.二叉树的遍历&#xff1a;1.二叉树的前中后序遍历&#xff1a;2.内容拓展&#xff1a; 二.二叉树链式(题目)题目一&#xff1a;计算节点…

小程序源码:多功能口袋工具箱微信小程序源码-带流量主|云开发(更新)

这里主要分享多功能口袋工具箱微信小程序源码&#xff0c;有带流量主&#xff0c;而且超多功能工具箱组合的微信小程序源码。无需服务器即可搭建&#xff0c;可以设置流量主赚取收益。 源码链接&#xff1a; 网盘源码 密码&#xff1a;hma8 工具箱的应用一览&#xff1a; 1…

【计算机网络】传输层协议——TCP(上)

文章目录 TCPTCP协议段格式报头和有效载荷如何分离&#xff1f;4位首部长度 TCP可靠性确认应答机制的提出序号和确认序号为什么序号和确认序号在不同的字段&#xff1f; 16位窗口大小 6个标志位标志位本质具体标志位PSHRSTURG 超时重传机制 文章目录 TCPTCP协议段格式报头和有效…

Qt Designer UI设计布局小结

目录 前言1 居中布局2 左右布局3 上下布局4 复杂页面布局总结 前言 本文总结了在开发Qt应用程序时使用 Designer 进行UI布局的一些心得体会。Qt Designer是Qt提供的一个可视化界面设计工具&#xff0c;旨在帮助开发人员快速创建和布局用户界面。它提供了丰富的布局管理器和控件…

我的个人网站——宏夏Coding上线啦

网站地址&#xff1a;宏夏Coding Github地址&#xff1a;&#x1f525;&#x1f525;宏夏coding网站&#xff0c;致力于为编程学习者、互联网求职者提供最需要的内容&#xff01;网站内容包括求职秘籍&#xff0c;葵花宝典&#xff08;学习笔记&#xff09;&#xff0c;资源推…

线性代数的学习和整理20,关于向量/矩阵和正交相关,相似矩阵等(草稿)

目录 1 什么是正交 1.1 正交相关名词 1.2 正交的定义 1.3 正交向量 1.4 正交基 1.5 正交矩阵的特点 1.6 正交矩阵的用处 1 什么是正交 1.1 正交相关名词 orthogonal set 正交向量组正交变换orthogonal matrix 正交矩阵orthogonal basis 正交基orthogonal decompositio…

访问局域网内共享文件时报错0x80070043,找不到网络名

我是菜鸡 此篇只为分享一个我遇到的很简单的但是排查了好久的小问题。 我的网络环境是在校园网内&#xff0c; 自己的办公电脑设置了固定IP&#xff1a;10.11.128.236&#xff0c;同事电脑IP为&#xff1a;10.11.128.255 本人需要访问同事在局域网内分享的文件&#xff0c;…

关于vscode的GitLens插件里的FILE HISTORY理解

最近在用vscode的GitLens插件开发项目遇到这个疑问&#xff0c;先看图&#xff1a; 每当我点击FILE HISTORY 一个commit时&#xff0c;正常来说显示器会自动将点击的提交版本和它上一个提交版本进行比较&#xff0c;如果单纯这么理解的话就错了&#xff0c;因为GitLens的File …

富斯I6刷10通道固件

使用USB转串口模块刷写10通道固件 一、下载固件 1. 十通道英文固件 下载地址: https://github.com/benb0jangles/FlySky-i6-Mod-/tree/master 选择 FlySky-i6-Mod–master \ 10ch Mod i6 Updater \ 10ch_MOD_i6_Programmer_V1 路径下的文件,亲测可用。 2. 原版六通道中…

Android性能优化之应用瘦身(APK瘦身)

关于作者&#xff1a;CSDN内容合伙人、技术专家&#xff0c; 从零开始做日活千万级APP。 专注于分享各领域原创系列文章 &#xff0c;擅长java后端、移动开发、人工智能等&#xff0c;希望大家多多支持。 目录 一、导读二、概览2.1 apk组成 三、优化方向3.1 源代码3.1.1 代码混…

网站搭建从零开始(0)--域名的选择与解析

目录 确定用途 购买域名 使用可靠的注册商购买域名 想好域名关键词 检查域名是否可用 添加域名到购物车并完成购买 域名的解析 登录注册商账户 选择要配置的域名 进入DNS解析设置 添加DNS记录 保存配置 检查解析是否生效 提示 确定用途 在购买域名之前&#xf…

【MFC】Button控件美化(自绘)

在MFC中Button控件不能通过OnCtlColor&#xff08;&#xff09;函数对外观做太多的改变。 HBRUSH C按钮控件自绘Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) {HBRUSH hbr CDialogEx::OnCtlColor(pDC, pWnd, nCtlColor);switch (pWnd->GetDlgCtrlID()){case ID…

Dinky上路之旅

1、部署flink集群 1.1、flink-conf.yaml cat > flink-conf.yaml << EOF jobmanager.rpc.address: boshi-146 jobmanager.rpc.port: 6123 jobmanager.bind-host: 0.0.0.0 jobmanager.memory.process.size: 1600m taskmanager.bind-host: 0.0.0.0 # 修改为本机ip tas…

芯科蓝牙BG27开发笔记1-新建示例工程

此笔记的必要性 芯科的官方资料很丰富&#xff0c;并且ssv5中能方便索引到所需文档&#xff0c;不过大而全的问题就是找不到合适的切入点&#xff0c;更不会有本地化比较好的中文的系统的教程了。往往看到一个starting guide&#xff0c;会延伸其他starting guide&#xff0c;…

自动化控制系统的设计重点是什么?

要实现对选择性激光烧结系统预热温度的控制&#xff0c;需要找到合理的控制对象模型&#xff0c;但选择性激光烧结设备的预热温度场是一个复杂的非线性系统&#xff0c;很难找到合理的控制对象模型来实现预热温度场的温度控制。模糊控制不需要具体的控制模型&#xff0c;预热温…