学习笔记:Netty网络编程框架

news2024/11/25 2:24:11

学习视频:Java网络编程教程——Netty深入浅出
参考笔记:网络编程

Netty

  • 前言
  • 一、NIO 基础
    • 1. NIO三大核心组件
      • 1.1 缓冲区 Buffer
        • 1.1.1 创建Buffer的方式
        • 1.1.2 HeapByteBuffer与DirectByteBuffer
        • 1.1.3 Buffer初体验
        • 1.1.4 Buffer三个重要参数
      • 1.2 通道 Channel
        • 1.2.1 FileChannel
        • 1.2.2 SocketChannel
      • 1.3 选择器 Selector
        • 1.3.1 服务器设计演化
        • 1.3.2 核心API
  • 二、NIO快速入门
    • 1. 阻塞与非阻塞
      • 阻塞
      • 非阻塞
      • 多路复用
    • 2. Selector
      • 创建
      • 绑定 Channel 事件
      • 监听 Channel 事件
      • 处理 accept 事件


前言

现在的互联网环境下,分布式大行其道,而分布式系统的根基在于网络编程,Netty恰恰是Java网络编程领域的王者。如果要致力于开发高性能的服务器、客户端程序,Netty是你必不可少的第一步,本笔记主要记录:

  • 使用Netty开发基本网络应用程序
  • 彻底理解阻塞、非阻塞区别,并将NIO与Netty的编码相互联系
  • 懂得多路复用在服务器开发时的优势,为什么在此基础上还要使用多线程
  • Netty 中是如何实现异步的?异步处理的优势是什么?
  • Netty 中是如何管理线程的?EventLoop如何运作?
  • Netty 中饰如何管理内存的?ByteBuffer特点与分配时机
  • 掌握看源码、调试的一些技巧,提升源码阅读能力

该章节简要介绍了NIO相关基础内容,详细介绍了ByteBuffer、FileChannel的使用方式

一、NIO 基础

NIO:java.nio全称Java Non-blocking IO,是指JDK1.4及以上版本里提供的新API(New IO) ,为所有的原始类型(boolean类型除外)提供缓存支持的数据容器,使用它可以提供非阻塞式的高伸缩性网络

为什么使用NIO?

在上面的描述中提到,是在JDK1.4以上的版本才提供NIO,那在之前使用的是什么呢?答案很简单,就是BIO(阻塞式IO),也就是我们常用的IO流。

BIO的问题其实不用多说了,因为在使用BIO时,主线程会进入阻塞状态,这就非常影响程序的性能,不能充分利用机器资源。但是这样就会有人提出疑问了,那我使用多线程不就可以了吗?

但是在高并发的情况下,会创建很多线程,线程会占用内存,线程之间的切换也会浪费资源开销。

NIO只有在连接/通道真正有读写事件发生时(事件驱动),才会进行读写,就大大地减少了系统的开销。不必为每一个连接都创建一个线程,也不必去维护多个线程。

避免了多个线程之间的上下文切换,导致资源的浪费。

1. NIO三大核心组件

NIO核心组件对应的类或接口应用作用
缓冲区Buffer文件IO/网络IO存储数据
通道Channel文件IO/网络IO传送数据
选择器Selector网络IO控制器

1.1 缓冲区 Buffer

我们先看以下这张类图,可以看到Buffer有七种类型。
在这里插入图片描述
Buffer是一个内存块。在NIO中,所有的数据都是用Buffer处理,有读写两种模式。所以NIO和传统的IO的区别就体现在这里。传统IO是面向Stream流,NIO而是面向缓冲区(Buffer)。

一般我们常用的类型是ByteBuffer,把数据转成字节进行处理。实质上是一个byte[]数组。

public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer>{
    //存储数据的数组
    final byte[] hb;
    //构造器方法
    ByteBuffer(int mark, int pos, int lim, int cap, byte[] hb, int offset) {
        super(mark, pos, lim, cap);
        //初始化数组
        this.hb = hb;
        this.offset = offset;
    }
}
1.1.1 创建Buffer的方式

主要分成两种:JVM堆内内存块Buffer堆外内存块Buffer

创建堆内内存块(非直接缓冲区)的方法是:

//创建堆内内存块HeapByteBuffer
ByteBuffer byteBuffer1 = ByteBuffer.allocate(1024);

String msg = "java技术爱好者";
//包装一个byte[]数组获得一个Buffer,实际类型是HeapByteBuffer
ByteBuffer byteBuffer2 = ByteBuffer.wrap(msg.getBytes());

创建堆外内存块(直接缓冲区)的方法:

//创建堆外内存块DirectByteBuffer
ByteBuffer byteBuffer3 = ByteBuffer.allocateDirect(1024);
1.1.2 HeapByteBuffer与DirectByteBuffer

根据类名就可以看出:

  • HeapByteBuffer字节缓冲区在JVM堆中,即JVM内部所维护的字节数组,读写效率低,会受到GC影响。
  • DirectByteBuffer是直接操作操作系统本地代码创建的内存缓冲数组,读写效率高(不需要再把文件内容copy到物理内存中),不会受GC影响,但该内存分配效率低。

DirectByteBuffer的使用场景:

  1. Java程序与本地磁盘、socket传输数据
  2. 存储大文件对象。不会受到堆内存大小的限制。
  3. 不需要频繁创建,生命周期较长的情况,能重复使用的情况。

HeapByteBuffer的使用场景:

除了以上的场景外,其他情况还是建议使用HeapByteBuffer,没有达到一定的量级,实际上使用DirectByteBuffer是体现不出优势的。

1.1.3 Buffer初体验

接下来,使用ByteBuffer做一个小例子,熟悉一下:

public static void main(String[] args) throws Exception {
    String msg = "java技术爱好者,起飞!";
    //创建一个固定大小的buffer(返回的是HeapByteBuffer)
    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
    byte[] bytes = msg.getBytes();
    //写入数据到Buffer中
    byteBuffer.put(bytes);
    //切换成读模式,关键一步
    byteBuffer.flip();
    //创建一个临时数组,用于存储获取到的数据
    byte[] tempByte = new byte[bytes.length];
    int i = 0;
    //如果还有数据,就循环。循环判断条件
    while (byteBuffer.hasRemaining()) {
        //获取byteBuffer中的数据
        byte b = byteBuffer.get();
        //放到临时数组中
        tempByte[i] = b;
        i++;
    }
    //打印结果
    System.out.println(new String(tempByte));//java技术爱好者,起飞!
}

这上面有一个flip()方法是很重要的。意思是切换到读模式。上面已经提到缓存区是双向的,既可以往缓冲区写入数据,也可以从缓冲区读取数据。但是不能同时进行,需要切换。那么这个切换模式的本质是什么呢?

1.1.4 Buffer三个重要参数

三个重要参数

//位置,默认是从第一个开始
private int position = 0;
//限制,不能读取或者写入的位置索引
private int limit;
//容量,缓冲区所包含的元素的数量
private int capacity;

那么我们以上面的例子,一句一句代码进行分析:

String msg = "java技术爱好者,起飞!";
//创建一个固定大小的buffer(返回的是HeapByteBuffer)
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

当创建一个缓冲区时,参数的值是这样的:

在这里插入图片描述

当执行到byteBuffer.put(bytes),当put()进入多少数据,position就会增加多少,参数就会发生变化:

在这里插入图片描述

接下来关键一步byteBuffer.flip(),会发生如下变化:

在这里插入图片描述

flip()方法的源码如下:

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

为什么要这样赋值呢?因为下面有一句循环条件判断:

byteBuffer.hasRemaining();
public final boolean hasRemaining() {
    //判断position的索引是否小于limit。
    //所以可以看出limit的作用就是记录写入数据的位置,那么当读取数据时,就知道读到哪个位置
    return position < limit;
}

接下来就是在while循环中get()读取数据,读取完之后:
在这里插入图片描述

最后当position等于limit时,循环判断条件不成立,就跳出循环,读取完毕。

所以可以看出实质上capacity容量大小是不变的,实际上是通过控制positionlimit的值来控制读写的数据。

1.2 通道 Channel

首先我们看一下Channel有哪些子类:

在这里插入图片描述

常用的Channel有这四种:

  1. FileChannel:文件数据传输通道
  2. SocketChannel:TCP网络编程数据传输通道
  3. ServerSockectChannel:TCP服务端网络编程数据传输通道,监听新TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel
  4. DatagramChannel:UDP网络编程数据传输通道

Channel本身并不存储数据,只是负责数据的运输。必须要和Buffer一起使用。

Channel 通道类似于 Stream 流,它是读写数据的双向通道,可以从 channel 将数据读入 buffer,也可以将 buffer 的数据写入 channel,而 stream 要么是输入,要么是输出,channel 比 stream 更为底层。

1.2.1 FileChannel

FileChannel只能工作于阻塞模式下

获取
不能直接打开 FileChannel,必须通过 FileInputStream、FileOutputStream 或 RandomAccessFile来获取,它们都有 getChannel() 方法

  • 通过 FileInputStream 获取的 channel 只能读
  • 通过 FileOutputStream 获取的 channel 只能写
  • 通过 RandomAccessFile 获取的 channel 能否读写,取决于构造 RandomAccessFile 时指定的读写模式

FileChannel 的获取方式,下面举个文件复制拷贝的例子进行说明:
首先准备一个"1.txt"放在项目的根目录下,然后编写一个 main 方法:

public static void main(String[] args) throws Exception {
    //获取文件输入流
    File file = new File("1.txt");
    FileInputStream inputStream = new FileInputStream(file);
    //从文件输入流获取通道
    FileChannel inputStreamChannel = inputStream.getChannel();
    //获取文件输出流
    FileOutputStream outputStream = new FileOutputStream(new File("2.txt"));
    //从文件输出流获取通道
    FileChannel outputStreamChannel = outputStream.getChannel();
    //创建一个byteBuffer,小文件所以就直接一次读取,不分多次循环了
    ByteBuffer byteBuffer = ByteBuffer.allocate((int)file.length());
    //把输入流通道的数据读取到缓冲区
    inputStreamChannel.read(byteBuffer);
    //切换成读模式
    byteBuffer.flip();
    //把数据从缓冲区写入到输出流通道
    outputStreamChannel.write(byteBuffer);
    //关闭通道
    outputStream.close();
    inputStream.close();
    outputStreamChannel.close();
    inputStreamChannel.close();
}

执行后,我们就获得一个"2.txt"。执行成功。以上的例子,可以用一张示意图表示,是这样的:

在这里插入图片描述

读取
从 channel 读取数据填充 ByteBuffer,返回值表示读了多少字节,-1 表示达到文件末尾

int readBytes = channel.read(byteBuffer);

写入
正确写入姿势如下:

ByteBuffer bytebuffer = ...;
bytebuffer.put(...); // 存入数据
bytebuffer.flip();

while (bytebuffer.hasRemaining()) {
	channel.write(bytebuffer);
}

在 while 中调用 channel.write 是因为 write 方法并不能保证一次性将 byteBuffer 中的内容全部写入 channel

关闭
channel 必须关闭,不过调用了FileInputStream、FileOutputStream 或 RandomAccessFile 的 close() 方法会间接调用 channel 的 close 方法

大小
使用 size() 方法获取文件大小

强制写入
操作系统出于对性能的考虑,会将数据缓存,而不是立即写入磁盘。调用 force(true) 方法将文件内容和原数据(文件的权限等信息)立即写入磁盘

通道间的数据传输
这里主要介绍两个通道与通道之间数据传输的方式,且该方式效率高,底层会利用操作系统的零拷贝进行优化:

  • transferTo():把源通道的数据传输到目的通道中,返回实际传输字节大小。inputChannel.transferTo(0, inputChannel.size(), outputChannel);
  • transferFrom():把来自源通道的数据传输到目的通道,返回实际传输字节大小。 outputChannel.transferFrom(inputChannel, 0, inputChannel.size);

这两个方法一次最多仅能传输2GB数据,超过2GB可以使用这种方式:
for (long less = input.size(); less > 0;) less -= input.transferTo(input.size() - less, less, output);

1.2.2 SocketChannel

接下来我们学习获取SocketChannel的方式。

还是一样,我们通过一个例子来快速上手:

public static void main(String[] args) throws Exception {
    //获取ServerSocketChannel
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    InetSocketAddress address = new InetSocketAddress("127.0.0.1", 6666);
    //绑定地址,端口号
    serverSocketChannel.bind(address);
    //创建一个缓冲区
    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
    while (true) {
        //获取SocketChannel
        SocketChannel socketChannel = serverSocketChannel.accept();
        while (socketChannel.read(byteBuffer) != -1){
            //打印结果
            System.out.println(new String(byteBuffer.array()));
            //清空缓冲区
            byteBuffer.clear();
        }
    }
}

然后运行 main() 方法,我们可以通过 telnet 命令进行连接测试:

在这里插入图片描述

通过上面的例子可以知道,通过ServerSocketChannel.open()方法可以获取服务器的通道,然后绑定一个地址端口号,接着accept()方法可获得一个SocketChannel通道,也就是客户端的连接通道。最后配合使用Buffer进行读写即可。

这就是一个简单的例子,实际上上面的例子是阻塞式的。要做到非阻塞还需要使用选择器Selector

1.3 选择器 Selector

Selector翻译成选择器,有些人也会翻译成多路复用器,实际上指的是同一样东西。

只有网络IO才会使用选择器,文件IO是不需要使用的。

选择器可以说是NIO的核心组件,它可以监听通道的状态,来实现异步非阻塞的IO。换句话说,也就是事件驱动。以此实现单线程管理多个Channel的目的。

在这里插入图片描述

1.3.1 服务器设计演化

Selector单从字面意思不好理解,需要结合服务器的设计演化来理解它的用途

多线程版本设计:每个socket都会分配一个thread处理。缺点:1.内存占用高;2.线程频繁上下文切换;3.仅适合连接数少的场景
在这里插入图片描述

线程池版本设计:将socket交给线程池处理。缺点:1.阻塞模式下,每个线程仅能处理一个socket连接;2.仅适合短链接场景(socket完成相应业务后立即断开,比如http请求)
在这里插入图片描述

Selector 版设计:Selector的作用就是配合一个线程来管理多个channel,获取这些channel上发生的事件,这些channel工作在非阻塞模式下,不会让线程吊死在一个channel上(调selector的select()会阻塞直到channel发生了读写就绪事件,事件发生,select()方法就会返回这些事件交给thread来处理)适合连接数特别多,但流量低的场景(low traffic)。
在这里插入图片描述

1.3.2 核心API
API方法名作用
Selector.open()打开一个选择器
select()选择一组键,其相应的通道已为 I/O 操作准备就绪
selectedKeys()返回此选择器的已选择键集

以上的API会在后面的例子用到,先有个印象。


二、NIO快速入门

1. 阻塞与非阻塞

阻塞

阻塞模式下,相关方法会导致线程暂停。此时线程不会占用CPU,但处于闲置

  • ServerSocketChannel.accept() 会在没有连接建立时阻塞
  • SocketChannel.read() 会在没有数据可读时阻塞

缺点

单线程下,阻塞方法之间相互影响,几乎不能正常工作,所以需要多线程支持。但在多线程下又有新的问题,体现在以下几方面:

  1. 32 位 JVM 一个线程 320KB,64 位 JVM 一个线程 1024KB,如果连接数过多会因频繁上下文切换导致性能降低,甚至线程数过多导致占用内存过多引发OOM
  2. 使用线程池技术来减少线程数,但无法治理根本问题。如果有很多连接建立,但长时间阻塞,还是会导致线程池中线程数过多,因此不适合长连接,只适合短连接

非阻塞

非阻塞模式下,相关方法不会让线程暂停

  • ServerSocketChannel.accept() 在没有连接建立时,返回null,继续执行
  • SocketChannel.read() 在没有数据可读时,返回0,线程不必阻塞,可以去处理来自其他socketChannel的事件
  • 写数据时,线程只等待数据写入 channel 即可,无需等待 channel 通过网络把数据发出

缺点

在非阻塞模式下,即使没有连接建立和数据可读,线程仍然在不断运行,浪费CPU
数据复制过沉重,线程实际还是处于阻塞状态(AIO改进)

多路复用

单线程可以配合 selector 完成对多个 channel 可读写事件的监控,这称之为多路复用

  • 多路复用仅针对网络IO,普通文件IO无法多路复用
  • 如果不用 selector 的非阻塞模式,线程大部分时间都在做无用功,而 selector 保证:
    • 有可连接事件才去连接
    • 有可读事件才去读取
    • 有可写事件才去写入(限于网络传输能力,channel未必时时可写,一旦channel可写,会触发selector的可写事件)

2. Selector

在这里插入图片描述
优点

  • 一个线程配合 selector 就可以监控多个 channel 的事件,事件发生线程才去处理。避免非阻塞模式下所做无用功
  • 让线程能够被充分利用
  • 节约了线程的数量
  • 减少了线程上下文切换

创建

Selector selector = Selector.open();

绑定 Channel 事件

也称之为注册事件,绑定的事件 selector 才会关心

channel.configureBlocking(false);
SelectionKey key = channel.register(selector, 绑定事件);
  • channel 必须工作在非阻塞模式
  • FileChannel 没有非阻塞模式,因此不能配合 selector 一起使用
  • 绑定的事件类型可以有:
    • connect - 客户端连接成功时触发
    • accept - 服务器端成功接受连接时触发
    • read - 数据可读入时触发,有因为接收能力弱,数据暂不能读入的情况
    • write - 数据可写出时触发,有因为发送能力弱,数据暂不能写出的情况

监听 Channel 事件

可以通过下面三种方法来监听是否有事件发生,方法的返回值代表有多少 channel 发生了事件

方法1,阻塞直到绑定事件发生

int count = selector.select();

方法2,阻塞直到绑定事件发生,或是超时(时间单位为 ms)

int count = selector.select(long timeout);

方法3,不会阻塞,也就是不管有没有事件,立刻返回,自己根据返回值检查是否有事件

int count = selector.selectNow();

💡 select 何时不阻塞:

  • 事件发生时
    • 客户端发起连接请求,会触发 accept 事件
    • 客户端发送数据过来,客户端正常、异常关闭时,都会触发 read 事件,另外如果发送的数据大于 buffer 缓冲区,会触发多次读取事件
    • channel 可写,会触发 write 事件
    • 在 linux 下 nio bug 发生时
  • 调用 selector.wakeup()
  • 调用 selector.close()
  • selector 所在线程 interrupt

处理 accept 事件

客户端代码为

public class Client {
    public static void main(String[] args) {
        try (Socket socket = new Socket("localhost", 8080)) {
            System.out.println(socket);
            socket.getOutputStream().write("world".getBytes());
            System.in.read();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

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

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

相关文章

牛客网剑指Offer-树篇-JZ27 二叉树的镜像

题目 来源&#xff1a;JZ27 二叉树的镜像 描述 操作给定的二叉树&#xff0c;将其变换为源二叉树的镜像。 数据范围&#xff1a;二叉树的节点数 0≤n≤1000 &#xff0c; 二叉树每个节点的值 0≤val≤1000 要求&#xff1a; 空间复杂度 O(n) 。本题也有原地操作&#xff0c;即…

Axure设置文本——元件动作三

亲爱的小伙伴&#xff0c;在您浏览之前&#xff0c;烦请关注一下&#xff0c;在此深表感谢&#xff01; 课程主题&#xff1a;设置文本 主要内容&#xff1a;掌握文本框的类型、属性、设置文本赋值的过程 应用场景&#xff1a;各种输入框、数据的重复赋值&#xff1b;多种小…

关于前端程序员使用Idea快捷键配置的说明

相信很多前端程序员 转到后端第一件事就是安装Idea然后学习java&#xff0c;在这里面最难的不是java的语法&#xff0c;而是关于快捷键的修改&#xff0c;前端程序员用的最多的估计就是VsCode或者Webstorm&#xff0c;就拿我自己举例我经常使用Vscode&#xff0c;当我写完代码下…

11-Dockerfile

11-Dockerfile Dockerfile Dockerfile是用来构建Docker镜像的文本文件&#xff0c;是由一条条构建镜像所需的指令和参数构成的脚本。 构建步骤&#xff1a; 编写Dockerfile文件docker build命令构建镜像docker run依据镜像运行容器实例 构建过程 Dockerfile编写&#xff1a…

【零售和消费品&存货】食品分类检测系统源码&数据集全套:改进yolo11-RepNCSPELAN_CAA

改进yolo11-goldyolo等200全套创新点大全&#xff1a;食品分类检测系统源码&#xff06;数据集全套 1.图片效果展示 项目来源 人工智能促进会 2024.10.30 注意&#xff1a;由于项目一直在更新迭代&#xff0c;上面“1.图片效果展示”和“2.视频效果展示”展示的系统图片或者视…

入侵检测算法平台部署LiteAIServer视频智能分析平台行人入侵检测算法

在当今科技日新月异的时代&#xff0c;行人入侵检测技术作为安全防护的重要组成部分&#xff0c;正经历着前所未有的发展。入侵检测算法平台部署LiteAIServer作为这一领域的佼佼者&#xff0c;凭借其卓越的技术实力与广泛的应用价值&#xff0c;正逐步成为守护公共安全的新利器…

命令如诗,步入Linux的晨曦:指令初学者的旅程(下)

文章目录 前言&#x1f99a;补充内容——管道管道的意义示例 &#x1f99a;11. cat - 显示文件内容11.1 显示文件内容11.2 连接多个文件并显示内容11.3 显示行号11.4 合并文件11.5 显示非打印字符11.6 将标准输入输出到文件 &#x1f99a;12. less - 分页查看文件内容12.1 基本…

【安全性分析】正式安全分析与非正式安全分析

安全性分析-系列文章目录 第一章 【安全性分析】正式安全分析与非正式安全分析 第二章 【安全性分析】BAN逻辑 (BAN Logic) 文章目录 安全性分析-系列文章目录前言一、正式安全分析1. 理想化模型(如随机预言机模型)2. 标准模型(Standard Model)3. 形式化验证4. 数学证明二…

kettle工具小经验

1、kettle本地连接数据库报错Error connecting to database: (using class oracle.jdbc.driver.OracleDriver) 原因&#xff1a;缺少jdbc jar包 处理&#xff1a;在data-integration\libswt\win64目录放一个jdbc jar包&#xff0c;我放的是ojdbc6.jar。 不知道为什么&#xff…

Android平台RTSP转RTMP推送之采集麦克风音频转发

技术背景 RTSP转RTMP推送&#xff0c;好多开发者第一想到的是采用ffmpeg命令行的形式&#xff0c;如果对ffmpeg比较熟&#xff0c;而且产品不要额外的定制和更高阶的要求&#xff0c;未尝不可&#xff0c;如果对产品稳定性、时延、断网重连等有更高的技术诉求&#xff0c;比较…

SSM旅游信息系统-计算机毕业设计源码00526

目 录 摘要 1 绪论 1.1 研究背景 1.2研究意义 1.3论文结构与章节安排 2 旅游信息系统分析 2.1 可行性分析 2.1.1 技术可行性分析 2.1.2 经济可行性分析 2.1.3 法律可行性分析 2.2 系统功能分析 2.2.1 功能性分析 2.2.2 非功能性分析 2.3 系统用例分析 2.4 系统流…

「Mac畅玩鸿蒙与硬件7」鸿蒙开发环境配置篇7 - 使用命令行工具和本地模拟器管理项目

本篇将讲解在 macOS 上配置 HarmonyOS 开发环境的流程&#xff0c;聚焦 hvigorw 命令行工具的使用。我们将以创建 HelloWorld 项目为例&#xff0c;演示使用 hvigorw 进行项目构建、清理操作&#xff0c;并通过 DevEco Studio 的本地模拟器进行预览&#xff0c;帮助提升项目开发…

央国企信创替代,2027年目标百分达成!信创人才评价成标配?

在2027年之前&#xff0c;央国企实现100%的信创替代&#xff0c;标志着中国信息技术应用创新产业发展步入关键阶段。 这一目标不仅体现了国家对于科技自主可控的高度重视&#xff0c;也预示着国内信创产业将迎来前所未有的发展机遇。 一、政策与市场背景 自2020年以来&#xff…

BOE(京东方)全新一代发光器件赋能iQOO 13 全面引领柔性显示行业性能新高度

10月30日,备受瞩目的iQOO最新旗舰机——被誉为“性能之光”的iQOO 13在深圳震撼发布。该款机型由BOE(京东方)独供6.82英寸超旗舰2K LTPO直屏,行业首发搭载全新一代Q10发光器件,在画面表现、护眼舒适度及性能功耗方面均达到行业领先水准,并以“直屏超窄边”的设计为用户呈现了前…

Python Requests 的高级使用技巧:应对复杂 HTTP 请求场景

介绍 网络爬虫&#xff08;Web Crawler&#xff09;是自动化的数据采集工具&#xff0c;用于从网络上提取所需的数据。然而&#xff0c;随着反爬虫技术的不断进步&#xff0c;很多网站增加了复杂的防护机制&#xff0c;使得数据采集变得更加困难。在这种情况下&#xff0c;Pyt…

【SAP Hana】X-DOC:数据仓库ETL如何抽取SAP中的CDS视图数据

【SAP Hana】X-DOC&#xff1a;数据仓库ETL如何抽取SAP中的CDS视图数据 1、无参CDS对应数据库视图2、有参CDS对应数据库表函数3、封装有参CDS为无参CDS&#xff0c;从而对应数据库视图 1、无参CDS对应数据库视图 select * from ZFCML_REP_V where mandt 300;2、有参CDS对应数…

提升网站速度与性能优化的有效策略与实践

内容概要 在数字化快速发展的今天&#xff0c;网站速度与性能优化显得尤为重要&#xff0c;它直接影响用户的浏览体验。用户在访问网站时&#xff0c;往往希望能够迅速获取信息&#xff0c;若加载时间过长&#xff0c;轻易可能导致他们转向其他更为流畅的网站。因此&#xff0…

C#界面设计--9--fatal error C1083: 无法打开包括文件:“jruparse.h”: No such file or directory

1、VS2008-编译时报错“fatal error C1083: 无法打开包括文件:“jruparse.h”: No such file or directory” 2、问题出现的原因及解决方法 1、如果要引入的这些,h文件跟.cpp在同一个目录下&#xff0c;就不会出现这种问题&#xff0c;检査在工程的include目录下是不是真的存…

‍️CentOS7.9 mall 部署【高可用版本】【本机部署】

文章目录 [TOC]技术选型后端技术前端技术移动端技术开发环境架构图业务架构图 项目部署实操主机规划中间件版本服务规划系统准备开始部署[[#MYSQL]]建立主从关系再次配置成为双主双从为 mysql 集群配置 vip [[#mongodb]]在主节点上无认证登录 [[#redis]]在主节点上查看集群状态…

【数据结构】-数组

数组 特点&#xff1a; 数组的地址连续&#xff0c;可以通过下标获取数据。 1. 数组扩容 步骤&#xff1a; $1. 创建一个比原来数组更长的新数组 $2. 让原来数组当中的数据依次复制到新数组当中 $3. 让arr指向新数组&#xff0c;原数组空间释放 2. 数组插入 2.1 最后位置…