有了BIO为啥还需要NIO

news2024/9/23 19:16:10

写在前面

注意:这里的NIO指的是Java nio技术。

源码 。
本文看下NIO相关内容。NIO太重要了,netty,tomcat,jetty等底层使用的都是Java nio,所以很有必要好好了解一下咯,涨薪不涨薪的咱不知道,至少在这个行业寒冬里,为自己不被淘汰增加一些筹码吧(典型的悲观主义者)!!!

1:BIO就那么多一无是处?

想要知道BIO是否真的是一无是处,我们就需要先来了解BIO的工作方式,如下是一段典型的基于BIO的代码:

class ConnectionPerThreadWithPool implements Runnable {
    public void run() {
        //线程池
        //注意,生产环境不能这么用,具体请参考《java高并发核心编程卷2》
        ExecutorService executor = Executors.newFixedThreadPool(100);
        try {
            //服务器监听socket
            ServerSocket serverSocket =
                    new ServerSocket(NioDemoConfig.SOCKET_SERVER_PORT);
            //主线程死循环, 等待新连接到来
            while (!Thread.interrupted()) {
                Socket socket = serverSocket.accept();
                //接收一个连接后,为socket连接,新建一个专属的处理器对象
                Handler handler = new Handler(socket);
                //创建新线程来handle
                //或者,使用线程池来处理
                new Thread(handler).start();
            }

        } catch (IOException ex) { /* 处理异常 */ }
    }

    static class Handler implements Runnable {
        final Socket socket;

        Handler(Socket s) {
            socket = s;
        }

        public void run() {
            //死循环处理读写事件
            boolean ioCompleted = false;
            while (!ioCompleted) {
                try {
                    byte[] input = new byte[NioDemoConfig.SERVER_BUFFER_SIZE];
                    /* 读取数据 */
                    socket.getInputStream().read(input);
                    byte[] output = null;
                    /* 写入结果 */
                    socket.getOutputStream().write(output);
                } catch (IOException ex) { /*处理异常*/ }
            }
        }
    }
}

ServerSocket阻塞在accept中等待新连接的到来,有一个新连接则创建一个线程负责该连接的数据读写操作。其实,BIO主要的问题也就是在这里了,每个连接都需要一个线程,即Connection Per Thread模式,每连接一个线程模式。这种线程模式下,如果是连接数不是特别多的情况下问题还是不大的,这个数的理论值一般在1000左右,但是现代应用连接数动辄几万,大的还有几百万,所以BIO肯定是无能为力的。
归根结底,BIO问题还是在过多的连接需要过多的线程上。那么为什么过多的线程就会有问题呢,原因主要有如下的几个方面:

1:线程的创建是一个很重的操作,需要通过系统调用完成(所以如果是小应用考虑使用BIO,也要进行池化)
2:过多的线程会导致频繁的线程上下文切换,各个线程的运行时数据都要进行复制保存等,这个操作可以通过观察top 的sy占用值,如果是达到20%,则说明CPU过多的时间片用在了线程的切换工作上
3:操作系统本身对一个进程可以创建的线程个数也是有限制,不同的操作系统这个值一般都是3000~5000之间的一个值。这个来自操作系统的硬性限制就有些无解了,就算是你想"硬用BIO"恐怕也是行不通了。
4:对于Java来说,每个线程都要分配一定的内存(-Xss参数),一般设置的值都在512byte~1M之间。即一个线程就要占用这么多的内存,比如设置-Xss1M则1000个线程就要占用JVM 1G的内存,所以过多的线程,内存也是无法满足要求的。这也是一个在过多的连接时无法使用BIO的硬性指标。也是无解。

所以,就很有必要隆重的请我们的Java NIO闪亮登场了。Java NIO通过使用IO多路复用模型来满足使用少量的线程来满足大量连接读写请求的需求。

2:Java NIO的核心组件

3个,分别是channel,selector,buffer。

2.1:channel

在BIO中对于读取操作定义了inputstream,对于写操作定义了outpustream,而NIO为了简化read,write的操作,对二者进行了整合,抽象定义了java.nio.channels.Channel接口。
针对网络操作,文件操作等分别定义了如下的Channel对象:

1.FileChannel 用于文件IO操作
2.DatagramChannel 用于UDP的IO操作
3.SocketChannel 用于TCP的传输操作(客户端类)
4.ServerSocketChannel 用于TCP连接监听操作(服务端类)

2.2:selector

前面已经说过了,JavaNIO底层使用的是IO多路复用的线程模型,而IO多路复用的线程模型是通过一组套接字的状态来判断读写状态的,而selector就是负责对操作系统提供的IO多路复用线程模型实现(select,poll,epoll)进行封装的组件。而套接字对应到Java nio中是channel组件,所以,channel组件需要注册selector进行读写操作:
在这里插入图片描述
如图,使用一个线程就使用selector来监听一组channel的数据读写了(核心所在)

2.3:buffer

为了能够更好的配合channel进行数据的读写操作,Java NIO定义了buffer,即一个缓存类,负责从channel中读写数据:
在这里插入图片描述
这里要注意buffer类是一个抽象类不是接口:

public abstract class Buffer {
}

3:Buffer

3.1:读写模式

Buffer支持两种模式,读模式,写模式,在读模式下可以进行数据读取操作,在写模式下可以进行数据写入操作。当Buffer被首次创建出来时默认处于写模式。

3.2:主要的buffer子类

ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer、MappedByteBuffer。可以方便操作对应的数据类型。

3.3:主要属性

  • capacity
    缓存区的总容量。
  • position
    在读写模式下,分别表示当前可以进行读写的位置。每次读一个数据,或者是写一个数据则position加一。
  • limit
    在默写模式下,分别表示可读可写的最大位置。在写模式下就是capasity的值,在读模式下就是写模式下的position值,即写模式下写的最后一个位置。
  • mark
    用来临时记录position的位置,通过mark()方法完成该操作:
public final Buffer mark() {
    mark = position;
    return this;
}

以上描述可能看起来有些云里雾里,不要着急,后续结合buffer的主要方法来看就会很清晰了。

3.4:主要方法

3.4.1:allocate

该方法用来申请缓存区,程序:

@Test
public void allocateTest() {
    IntBuffer intBuffer = IntBuffer.allocate(20);
    System.out.println("capacity: " + intBuffer.capacity());
    System.out.println("position: " + intBuffer.position());
    System.out.println("limit: " + intBuffer.limit());
}

输出:

capacity: 20
position: 0
limit: 20

Process finished with exit code 0

在这里插入图片描述

3.4.2:put

写数据到缓冲区中,程序:

@Test
public void putTest() {
    IntBuffer intBuffer = IntBuffer.allocate(20);

    intBuffer.put(1);
    intBuffer.put(2);

    System.out.println("capacity: " + intBuffer.capacity());
    System.out.println("position: " + intBuffer.position());
    System.out.println("limit: " + intBuffer.limit());
}

输出:

capacity: 20
position: 2
limit: 20

Process finished with exit code 0

在这里插入图片描述
此时position为2,代表下一个可put数据的位置。

3.4.3:flip

转换写模式为读模式,主要是对position,limit的值做一些修改,直接看源码吧:

public final Buffer flip() {
    limit = position;
    position = 0;
    mark = -1;
    return this;
}

测试程序:

@Test
public void flipTest() {
    IntBuffer intBuffer = IntBuffer.allocate(20);

    intBuffer.put(1);
    intBuffer.put(2);

    System.out.println("capacity: " + intBuffer.capacity());
    System.out.println("position: " + intBuffer.position());
    System.out.println("limit: " + intBuffer.limit());

    System.out.println("flip后");
    intBuffer.flip();
    System.out.println("capacity: " + intBuffer.capacity());
    System.out.println("position: " + intBuffer.position());
    System.out.println("limit: " + intBuffer.limit());
}

输出:

capacity: 20
position: 2
limit: 20
flip后
capacity: 20
position: 0
limit: 2

Process finished with exit code 0

在这里插入图片描述

3.4.4:get

读模式下,从缓冲区中读取数据,读取一次position+1,超过limit时读取则会抛出BufferUnderflowException异常:

@Test
public void getTest() {
    IntBuffer intBuffer = IntBuffer.allocate(20);

    intBuffer.put(1);
    intBuffer.put(2);

    System.out.println("capacity: " + intBuffer.capacity());
    System.out.println("position: " + intBuffer.position());
    System.out.println("limit: " + intBuffer.limit());

    System.out.println("flip后");
    intBuffer.flip();
    System.out.println("capacity: " + intBuffer.capacity());
    System.out.println("position: " + intBuffer.position());
    System.out.println("limit: " + intBuffer.limit());


    System.out.println("get一次后");
    System.out.println(intBuffer.get());

    System.out.println("capacity: " + intBuffer.capacity());
    System.out.println("position: " + intBuffer.position());
    System.out.println("limit: " + intBuffer.limit());

}

在这里插入图片描述

3.4.5:rewind

重放方法,可以从头重复读取数据,所以主要就是修改position的值为0,jdk源码如下:

@Test
public void rewindTest() {
    IntBuffer intBuffer = IntBuffer.allocate(20);

    intBuffer.put(1);
    intBuffer.put(2);

//        System.out.println("capacity: " + intBuffer.capacity());
//        System.out.println("position: " + intBuffer.position());
//        System.out.println("limit: " + intBuffer.limit());

//        System.out.println("flip后");
    intBuffer.flip();
//        System.out.println("capacity: " + intBuffer.capacity());
//        System.out.println("position: " + intBuffer.position());
//        System.out.println("limit: " + intBuffer.limit());


//        System.out.println("get一次后");
    System.out.println(intBuffer.get());

    System.out.println("capacity: " + intBuffer.capacity());
    System.out.println("position: " + intBuffer.position());
    System.out.println("limit: " + intBuffer.limit());

    System.out.println("rewind后");
    intBuffer.rewind();
    System.out.println("capacity: " + intBuffer.capacity());
    System.out.println("position: " + intBuffer.position());
    System.out.println("limit: " + intBuffer.limit());
    System.out.println(intBuffer.get());

}

输出:

1
capacity: 20
position: 1
limit: 2
rewind后
capacity: 20
position: 0
limit: 2
1

Process finished with exit code 0

在这里插入图片描述

3.4.6:mark()和reset()

mark()方法临时保存position值到mark中,reset恢复临时保存的值到position中:

public final Buffer mark() {
    mark = position;
    return this;
}

public final Buffer reset() {
    int m = mark;
    if (m < 0)
        throw new InvalidMarkException();
    position = m;
    return this;
}

测试:

@Test
public void markAndResetTest() {
    IntBuffer intBuffer = IntBuffer.allocate(20);

    intBuffer.put(1);
    intBuffer.put(2);

    System.out.println("记录position位置2到mark");
    // 记录position位置2到mark
    intBuffer.mark();
    System.out.println("position变为3");
    // position变为3
    intBuffer.put(3);
    System.out.println("position: " + intBuffer.position());

    System.out.println("reset mark恢复position为2");
    // reset mark恢复position为2
    intBuffer.reset();
    System.out.println("position: " + intBuffer.position());
}

运行:

记录position位置2到mark
position变为3
position: 3
reset mark恢复position为2
position: 2

Process finished with exit code 0

3.4.7:clear

从读取模式转换为写入模式,做如下的事情:

(1)会将position清零;
(2)limit设置为capacity最大容量值,可以一直写入,直到缓冲区写满。

jdk源码:

public final Buffer clear() {
    position = 0;
    limit = capacity;
    mark = -1;
    return this;
}

3.4.8:compact

该方法也是将buffer从读取模式转换为写入模式的方法之一,但不同于clear,该方法不会无脑清空所有数据,而是会将用户还没有读取的数据拷贝到底层数组的开始位置,已经读取过的会删除掉,之后将position设置为下一个可写入数据的位置,jdk源码:

public IntBuffer compact() {

    System.arraycopy(hb, ix(position()), hb, ix(0), remaining());
    position(remaining());
    limit(capacity());
    discardMark();
    return this;



}

看个示例代码:

@Test
public void compactTest() {
    IntBuffer intBuffer = IntBuffer.allocate(20);

    intBuffer.put(1);
    intBuffer.put(2);
    System.out.println("写俩数据后:");
    System.out.println("capacity: " + intBuffer.capacity());
    System.out.println("position: " + intBuffer.position());
    System.out.println("limit: " + intBuffer.limit());

    intBuffer.flip(); // 转到读取模式
    System.out.println("flip后:");
    System.out.println("capacity: " + intBuffer.capacity());
    System.out.println("position: " + intBuffer.position());
    System.out.println("limit: " + intBuffer.limit());
//        System.out.println(intBuffer.get());
    // 读一个 1
    System.out.println(intBuffer.get());

    intBuffer.compact();
    System.out.println("compact后:");
    System.out.println("capacity: " + intBuffer.capacity());
    System.out.println("position: " + intBuffer.position());
    System.out.println("limit: " + intBuffer.limit());

    //
    intBuffer.flip();
    System.out.println(intBuffer.get());
}

运行:

写俩数据后:
capacity: 20
position: 2
limit: 20
flip后:
capacity: 20
position: 0
limit: 2
1
compact后:
capacity: 20
position: 1
limit: 20
2

Process finished with exit code 0

对应的变化过程如下图:
在这里插入图片描述

3.4.8:buffer使用各个方法的核心步骤

(1)使用创建子类实例对象的allocate( )方法,创建一个Buffer类的实例对象。
(2)调用put( )方法,将数据写入到缓冲区中。
(3)写入完成后,在开始读取数据前,调用Buffer.flip()方法,将缓冲区转换为读模式。
(4)调用get( )方法,可以从缓冲区中读取数据。
(5)读取完成后,调用Buffer.clear()方法或Buffer.compact()方法,将缓冲区转换为写入模式,可以继续写入。

4:channel

主要有4种Channel(通道)实现:FileChannel、SocketChannel、ServerSocketChannel、DatagramChannel。

(1)FileChannel文件通道,用于文件的数据读写;
(2)SocketChannel套接字通道,用于Socket套接字TCP连接的数据读写;
(3)ServerSocketChannel服务器套接字通道(或服务器监听通道),允许我们监听TCP连接请求,为每个监听到的请求,创建一个SocketChannel套接字通道;
(4)DatagramChannel数据报通道,用于UDP协议的数据读写。

4.1:FileChannel

支持文件的读取,写入。仅支持阻塞模式,不支持非阻塞模式。

4.1.1:获取filechannel

程序如下:

@Test
public void createFileChannel() throws Exception {
    String srcFile = "d:\\test\\Snipaste_2024-09-02_14-29-46.png";
    String destFile = "d:\\test\\Snipaste_2024-09-02_14-29-46.png";
    //创建一个文件输入流
    FileInputStream fis = new FileInputStream(srcFile);
    //获取文件流的通道
    FileChannel inChannel = fis.getChannel();
    //创建一个文件输出流
    FileOutputStream fos = new FileOutputStream(destFile);
    //获取文件流的通道
    FileChannel outchannel = fos.getChannel();
    //也可以通过RandomAccessFile文件随机访问类,获取FileChannel文件通道实例,代码如下:
    // 创建RandomAccessFile随机访问对象
    RandomAccessFile rFile = new RandomAccessFile("filename.txt", "rw");
    //获取文件流的通道(可读可写)
    FileChannel channel = rFile.getChannel();
}

4.1.2:读取filechannel

bio中内容一般是读取到byte数组中,而Java nio则是读取到bytebuffer中,并且会返回读取到的数据量。
程序:

@Test
public void readFileChannelTest() throws Exception {
    // 文件编码是utf8,需要用utf8解码
    Charset charset = Charset.forName("utf-8");
    CharsetDecoder decoder = charset.newDecoder();

    // 创建RandomAccessFile随机访问对象
    RandomAccessFile rFile = new RandomAccessFile("d:\\test\\aaaa.txt", "rw");
    //获取文件流的通道(可读可写)
    FileChannel fChannel = rFile.getChannel();
    ByteBuffer bBuf = ByteBuffer.allocate(32); // 缓存大小设置为32个字节。仅仅是测试用。
    CharBuffer cBuf = CharBuffer.allocate(32);

    int bytesRead = fChannel.read(bBuf); // 从文件通道读取字节到buffer.
    char[] tmp = null; // 临时存放转码后的字符
    byte[] remainByte = null;// 存放decode操作后未处理完的字节。decode仅仅转码尽可能多的字节,此次转码不了的字节需要缓存,下次再转
    int leftNum = 0; // 未转码的字节数
    while (bytesRead != -1) {

        bBuf.flip(); // 切换buffer从写模式到读模式
        decoder.decode(bBuf, cBuf, true); // 以utf8编码转换ByteBuffer到CharBuffer
        cBuf.flip(); // 切换buffer从写模式到读模式
        remainByte = null;
        leftNum = bBuf.limit() - bBuf.position();
        if (leftNum > 0) { // 记录未转换完的字节
            remainByte = new byte[leftNum];
            bBuf.get(remainByte, 0, leftNum);
        }

        // 输出已转换的字符
        tmp = new char[cBuf.length()];
        while (cBuf.hasRemaining()) {
            cBuf.get(tmp);
            System.out.print(new String(tmp));
        }

        bBuf.clear(); // 切换buffer从读模式到写模式
        cBuf.clear(); // 切换buffer从读模式到写模式
        if (remainByte != null) {
            bBuf.put(remainByte); // 将未转换完的字节写入bBuf,与下次读取的byte一起转换
        }
        bytesRead = fChannel.read(bBuf);
    }
    rFile.close();
}

测试文件:

-Xthejrepath D:\programs\javas\java1.8/jre -Xthetargetclazz D:\test\itstack-demo-jvm-master\tryy-too-simulate-invokexxx-and-xreturn\target\test-classes\org\itstack\demo\test\HelloWorld
-Xthejrepath D:\programs\javas\java1.8/jre -Xthetargetclazz D:\test\itstack-demo-jvm-master\tryy-too-simulate-array-operation\target\test-classes\org\itstack\demo\test\HelloWorld

sync_al为sync_alt

10100000 想要以负数显示,必须转换成补码形式
符号位外全部无脑取反 11011111
+1 11100000

64+32 96 -96



10100001 想要以负数显示,必须转换成补码形式
符号位外全部无脑取反 11011110
+1 11011111 = 64+16+8+4+2+1=95 加上符号就是-95


00000000 00000000 00000000 10100001 = 128+32+1 = 161
-Xthejrepath D:\programs\javas\java1.8/jre -Xthetargetclazz D:\test\itstack-demo-jvm-master\tryy-too-simulate-array-operation\target\test-classes\org\itstack\demo\test\HelloWorld

测试输出:
在这里插入图片描述

4.1.3:写入filechannel

@Test
public void writeFileChannelTest() throws Exception {
    File file = new File("d:\\test\\jj.txt");
    if (!file.exists()) file.createNewFile();

    RandomAccessFile randomAccessFile = new RandomAccessFile("d:\\test\\jj.txt", "rw");
    FileChannel channel = randomAccessFile.getChannel(); // 获取一个可读写文件通道

    ByteBuffer buf = ByteBuffer.allocate(5);
    byte[] data = "Hello, Java NIO.".getBytes();
    for (int i = 0; i < data.length; ) {
        buf.put(data, i, Math.min(data.length - i, buf.limit() - buf.position()));
        buf.flip();
        // 返回写入数据的数量
        i += channel.write(buf);
//            buf.compact();
        buf.clear(); // 写入的都读完了,所以调用compact和调用clear效果一样
    }
    channel.force(false);
    channel.close();
}

结果:
在这里插入图片描述

4.1.4:关闭以及强刷filechannel

//关闭通道
channel.close( );
//强制刷新到磁盘
channel.force(true);

4.2:SocketChannel和ServerSocketChannel

4.4:DatagramChannel

4:selector

选择器,封装操作系统底层监听各个套接字的功能。在Java nio中套接字对应的就是通道,因此通道需要注册到选择中,并指定自己感兴趣的io事件,才能被监听。一般使用一个线程负责使用选择器完成其管理的所有通道的监听的工作:
在这里插入图片描述
通道注册到选择器的方法:

// java.nio.channels.SelectableChannel#register(java.nio.channels.Selector, int, java.lang.Object)
public final SelectionKey register(Selector sel, int ops)
    throws ClosedChannelException
{
    return register(sel, ops, null);
}

其中第一个参数很好理解,就是选择器,第二参数是感兴趣的io事件,在SelectionKey抽象类中定义:

public abstract class SelectionKey {
    // -- Operation bits and bit-testing convenience methods --
    
    public static final int OP_READ = 1 << 0;

    public static final int OP_WRITE = 1 << 2;

    public static final int OP_CONNECT = 1 << 3;

    public static final int OP_ACCEPT = 1 << 4;
}

如果是多个的话则可以通过|或运算来设置,比如感兴趣OP_READ和OP_ACCEPT则就是OP_READ | OP_ACCEPT
一般使用过程如下:

1:获取选择器(SPI方式)
    Selector selector = Selector.open();
2:将通道注册到选择器中
    ServerSocketChannelserverSocketChannel = ServerSocketChannel.open();
    serverSocketChannel.configureBlocking(false); // 必须处于非阻塞模式下,否则将抛出IllegalBlockingModeException异常
    serverSocketChannel.bind(new InetSocketAddress(18899));
    serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT); // 通道并非支持所有IO事件,可以在注册之前,可以通过通道的validOps()方法,来获取该通道所有支持的IO事件集合。
3:处理就绪事件(SelectionKey:封装了触发的事件和对应通道,这样方便我们处理事件了)
    while (selector.select() > 0) {
        Set selectedKeys = selector.selectedKeys();
        Iterator keyIterator = selectedKeys.iterator();
        while(keyIterator.hasNext()) {
            SelectionKey key = keyIterator.next();
            //根据具体的IO事件类型,执行对应的业务操作
            if(key.isAcceptable()) {// IO事件:ServerSocketChannel服务器监听通道有新连接
            } else if (key.isConnectable()) {// IO事件:传输通道连接成功
            } else if (key.isReadable()) { // IO事件:传输通道可读
            } else if (key.isWritable()) { // IO事件:传输通道可写
            } 
            //处理完成后,移除选择键
            keyIterator.remove(); 
        }
}

selector.select()负责调用操作系统底层轮询channel的状态,一旦返回值大于0,则说明通道有感兴趣的事件发生了,会将发生的事件以及对应的通道封装为SelectionKey,这样我们就可以通过SelectionKey获取对应的事件,以及对应的通道来进行对应的业务逻辑处理了,如下图:
在这里插入图片描述

5:实战之实现一个discard server

just go。

写在后面

参考文章列表

Java NIO (图解+秒懂+史上最全) 。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【网络安全】网络基础第一阶段——第二节:网络协议基础---- 路由和ARP协议

本篇文章我们来介绍IP路由的基本概念&#xff0c;包括路由的原理、静态路由和动态路由的配置与特点。 目录 一、路由 1.1 IP路由原理、静态路由及动态路由区分 1.1.1 什么是路由 1.1.2 路由的原理 1.1.2 路由表 1.1.3 静态路由与动态路由 1.2 路由原理详解 1.2.1 路由的…

Python增强办公效率的11个实用代码段

如果你正在学习Python&#xff0c;那么你需要的话可以&#xff0c;点击这里&#x1f449;Python重磅福利&#xff1a;入门&进阶全套学习资料、电子书、软件包、项目源码等等免费分享&#xff01; 引言 在日常工作中&#xff0c;许多任务可以通过编程自动化来提高效率。本…

QT6.7创建Non-Qt Project工程

QT6.7创建Non-Qt Project工程

数据结构——“二叉搜索树”

二叉搜索树是一个很重要的数据结构&#xff0c;它的特殊结构可以在很短的时间复杂度找到我们想要的数据。最坏情况下的时间复杂度是O(n)&#xff0c;最好是O(logn)。接下来看一看它的接口函数的实现。 为了使用方便&#xff0c;这里采用模版的方式&#xff1a; 一、节点 temp…

TaskRes: Task Residual for Tuning Vision-Language Models

文章汇总 当前VLMs微调中存在的问题 提示微调的问题 在提示调优中缺乏对先验知识保存的保证(me&#xff1a;即提示微调有可能会丢失预训练模型中的通用知识)。虽然预先训练的文本分支模块(如文本编码器和投影)的权重在提示调优范式中被冻结&#xff0c;但原始的良好学习的分类…

图文深入理解SQL语句的执行过程

List item 本文将深入介绍SQL语句的执行过程。 一.在RDBMS&#xff08;关系型DB&#xff09;中&#xff0c;看似很简单的一条已写入DB内存的SQL语句执行过程却非常复杂&#xff0c;也就是说&#xff0c;你执行了一条诸如select count(*) where id 001 from table_name的非常简…

【Transformers基础入门篇4】基础组件之Model

文章目录 一、Model简介1.1 Transformer1.2 注意力机制1.3 模型类型 二、Model Head2.1 什么是 Model Head2.2 Transformers中的Model Head 三、Model基本使用方法3.0 模型下载-浏览器下载3.1 模型加载与保存3.2 配置加载参数3.3 加载config文件3.2 模型调用3.2.1 带ModelHead的…

【PAM】Linux登录认证限制

PAM&#xff08;Pluggable Authentication Modules&#xff0c;可插拔认证模块&#xff09;是一种灵活的认证框架&#xff0c;用于在 Linux 和其他类 Unix 系统上管理用户的身份验证。PAM 允许系统管理员通过配置不同的认证模块来定制应用程序和服务的认证方式&#xff0c;而不…

软件设计师:01计算机组成与结构

文章目录 一、校验码1.奇偶校验码2.海明码3.循环冗余检验码 二、原码反码补码移码三、浮点数表示法1.浮点数相加时 四、寻址方式五、CPU1.访问速度2.cpu的组成 六、RISC和CISC&#xff08;<font color red>只用记住不同就可以&#xff09;七、冗余技术1.结构冗余2.信息冗…

HyperWorks的实体几何创建与六面体网格剖分

创建和编辑实体几何 在 HyperMesh 有限元前处理环境中&#xff0c;有许多操作是针对“实体几何”的&#xff0c;例如创建六面体网格。在创建实体网格的工作中&#xff0c;我们既可以使用闭合曲面创建实体网格&#xff0c;也可以使用完整的实体几何创建实体网格。与闭合曲面相比…

【rabbitmq-server】安装使用介绍

在 1050a 系统下安装 rabbitmq-server 服务以及基本配置;【注】:改方案用于A版统信服务器操作系统 文章目录 功能概述功能介绍一、安装软件包二、启动服务三、验证四、基本配置功能概述 RabbitMQ 是AMQP的实现,高性能的企业消息的新标准。RabbitMQ服务器是一个强大和可扩展…

截取递增数-第15届蓝桥省赛Scratch中级组真题第6题

[导读]&#xff1a;超平老师的《Scratch蓝桥杯真题解析100讲》已经全部完成&#xff0c;后续会不定期解读蓝桥杯真题&#xff0c;这是Scratch蓝桥杯真题解析第191讲。 如果想持续关注Scratch蓝桥真题解读&#xff0c;可以点击《Scratch蓝桥杯历年真题》并订阅合集&#xff0c;…

【c数据结构】OJ练习篇 帮你更深层次理解链表!(相交链表、相交链表、环形链表、环形链表之寻找环形入口点、判断链表是否是回文结构、 随机链表的复制)

目录 一. 相交链表 二. 环形链表 三. 环形链表之寻找环形入口点 四. 判断链表是否是回文结构 五. 随机链表的复制 一. 相交链表 最简单粗暴的思路&#xff0c;遍历两个链表&#xff0c;分别寻找是否有相同的对应的结点。 我们对两个链表的每个对应的节点进行判断比较&…

力扣 209.长度最小的子数组

一、长度最小的子数组 二、解题思路 采用滑动窗口的思路&#xff0c;详细见代码。 三、代码 class Solution {public int minSubArrayLen(int target, int[] nums) {int n nums.length, left 0, right 0, sum 0;int ans n 1; for (right 0; right < n; right ) { …

数通。。。

通信&#xff1a;需要介质才能通信电话离信号塔&#xff08;基站&#xff09;越远&#xff0c;信号越弱。信号在基站之间传递。你离路由器越远&#xff0c;信号越差。一个意思 比如想传一张图片&#xff0c;这张图片就是数据载荷 网关&#xff0c;分割两个网络。路由器可以是网…

Chat2VIS: Generating Data Visualizations via Natural Language

Chat2VIS:通过使用ChatGPT, Codex和GPT-3大型语言模型的自然语言生成数据可视化 梅西大学数学与计算科学学院&#xff0c;新西兰奥克兰 IEEE Access 1 Abstract 数据可视化领域一直致力于设计直接从自然语言文本生成可视化的解决方案。自然语言接口 (NLI) 的研究为这些技术的…

巴黎嫩事件对数据信息安全的影响及必要措施

2024年9月17日&#xff0c;黎巴嫩首都贝鲁特发生了多起小型无线电通信设备爆炸事件&#xff0c;导致伊朗驻黎巴嫩大使受轻伤。这一事件不仅引发了对安全的广泛关注&#xff0c;也对数据信息安全提出了新的挑战。 王工 18913263502 对数据信息安全的影响&#xff1a; 数据泄露风…

MySQL慢查询优化指南

​ 博客主页: 南来_北往 系列专栏&#xff1a;Spring Boot实战 前言 当遇到慢查询问题时&#xff0c;不仅影响服务效率&#xff0c;还可能成为系统瓶颈。作为一位软件工程师&#xff0c;掌握MySQL慢查询优化技巧至关重要。今天&#xff0c;我们就来一场“数据库加速之旅…

Thinkphp(TP)

1.远程命令执行 /index.php?sindex/think\app/invokefunction&functioncall_user_func_array&vars[0]system&vars[1][]whoami 2.远程代码执行 /index.php?sindex/think\app/invokefunction&functioncall_user_func_array&vars[0]phpinfo&vars[1][]…

Java面向对象——内部类(成员内部类、静态内部类、局部内部类、匿名内部类,完整详解附有代码+案例)

文章目录 内部类17.1概述17.2成员内部类17.2.1 获取成员内部类对象17.2.2 成员内部类内存图 17.3静态内部类17.4局部内部类17.5匿名内部类17.5.1概述 内部类 17.1概述 写在一个类里面的类叫内部类,即 在一个类的里面再定义一个类。 如&#xff0c;A类的里面的定义B类&#x…