JAVA的BIO、NIO、AIO模式精解(二)

news2024/11/23 17:12:11

4.JAVA NIO深入剖析

4.1 java NIO基本介绍

  • Java NIO(New IO)即java non-block IO。NIO支持面向缓冲区的,基于通道的IO操作。NIO可理解为非阻塞IO,传统IO只能阻塞读写,而NIO可配置socket为非阻塞式。
  • NIO类在java.nio包下,对原io包进行改写
  • NIO三大组件 《Channel通道》,《Buffer缓冲区》,《Selector选择器
  • NIO非阻塞模式使一个线程从某管道发送请求或读取数据,没有数据不会获取,不会阻塞直至数据可读。该线程可做其他事,非阻塞写也是。
  • NIO可一个线程处理多个操作。1K请求可分配20~80个线程处理,而不是阻塞IO分配1000。

4.2 NIO和NIO比较

  • BIO以流方式处理数据,效率低。NIO以块处理数据,效率高。
  • BIO阻塞,NIO非阻塞
  • BIO基于字节流和字符流进行操作,而NIO基于Channel和Buffer进行操作,数据总是从通道到缓冲区,或相反,Seletor用于监听多个通道的事件(链接请求,数据就绪),因此单线程就可监听多个客户端。
NIOBIO
面向缓冲区Buffer面向流Stream
非阻塞Non Blocking IO阻塞Blocking IO
选择器Selector

4.3 NIO三大核心原理示意图

Buffer:一块可读写数的内存,被包装成NIO Buffer对象,并提供一组方法来访问。相对数组更易操作管理。
Channel:可读可写的管道,流(inPut,Output)是单向。通道可支持读取写入缓冲区,也支持异步读写。
Selector:可检查多个NIO管道是否就绪。这样一个单独线程可管理多个Channel。
NIO三组件示意图

  • 每个Channel都对应一个Buffer
  • 一个线程对应一个Selector,一个Selector对应多个Channel。
  • 程序切换到那个channel由事件决定。selector根据不同事件在各个管道上切换
  • Buffer是内存块,底层数组实现
  • 数据读写由Buffer完成双向,BIO输入输出单向。
  • Java NIO核心:通道表示打开到IO设备负责传输,缓冲区表示存储数据负责存取。

4.4 NIO核心1:缓冲区

缓冲区

一个用于基本数据类型的容器。nio包定义。所有缓冲区都是Buffer抽象类的子类。主要用于与NIO通道进行交互。数据都是从通道读入缓冲区,在写入管道。
Buffer

Buffer类及其子类

Buffer类似数组,保存多个相同类型的数据。根据类型不同,有以下子类:Byte,Char,Short,Int,Long,Float,Double。上述Buffer采取相同的方法管理数,只是数据类型不同。

static XxxBufer allocate(int capacity) : 创建一个容量为capacity的 XxxBuffer对象

缓冲区的基本属性

  • 容量capacity:内存块的固定大小,不能为负,不能修改。
  • 限制limit:缓冲区可操作数据大小,不能为负,不能超容量。写入模式,限制等于Buffer容量,读取模式,等于写入数据量。
  • 位置Position:下个要读取或写入的数据索引。
  • 标记mark与重置reset:标记是索引,通过Buffer中的mark方法指定Buffer中特定的position,之后可调用reset方法恢复position。标记,职位,限制,容量遵循以下不变式:0<=mark<=position<=limit<=limit<=capacity
  • 图示
    在这里插入图片描述

缓冲区常见方法

Buffer clean():清空缓冲区并返回缓冲区的引用
Buffer flip():为将缓冲区的界限设置为当前位置,并将当前位置重置为0
int capacity():返回Buffer的容量
boolean hasRemaining():判断缓冲区还有元素
int limit():返回Buffer界限位置
Buffer limit():设置缓冲区界限为n,并返回一个新的limit的缓冲区对象
Buffer mark():对缓冲区设置标记
int position():返回缓冲区当前位置
Buffer position(int n):设置缓冲区当前位置为n,并返回修改后的Buffer对象
int remaining():返回position和limit之间的元素个数
Buffer reset():将位置position转到以前的设置的mark所在的位置
Buffer rewind():位置设置为0,取消设置的mark

        ByteBuffer buffer = ByteBuffer.allocate(10);
        System.out.println(buffer.position());  //0
        System.out.println(buffer.limit());     //10
        System.out.println(buffer.capacity());  //10
        String name = "xuy";
        buffer.put(name.getBytes());
        System.out.println(buffer.position());  //3
        System.out.println(buffer.limit());     //10
        System.out.println(buffer.capacity());  //10
        buffer.flip();
        System.out.println(buffer.position());  //0
        System.out.println(buffer.limit());     //3
        System.out.println(buffer.capacity());  //10
        char c = (char) buffer.get();
        System.out.println(c);                  //x
        System.out.println(buffer.position());  //1
        System.out.println(buffer.limit());     //3
        System.out.println(buffer.capacity());  //10

        buffer.clear();
        System.out.println(buffer.position());  //0
        System.out.println(buffer.limit());     //10
        System.out.println(buffer.capacity());  //10
        System.out.println(c);                  //x  只有在重复值时生效

        ByteBuffer buf = ByteBuffer.allocate(10);
        String n = "abcdefg";
        buf.put(n.getBytes());
        buf.flip();
        //读取数据
        byte[] b = new byte[2];
        buf.get(b);
        String rs = new String(b);
        System.out.println(rs);              //ab
        System.out.println(buf.position());  //2
        System.out.println(buf.limit());     //7
        System.out.println(buf.capacity());  //10
        buf.mark();                          //标记此刻位置:2
        byte[] b2 = new byte[3];
        buf.get(b2);
        System.out.println(new String(b2));  //cde
        System.out.println(buf.position());  //5
        System.out.println(buf.limit());     //7
        System.out.println(buf.capacity());  //10
        buf.reset();                         //回到标记位置
        if(buf.hasRemaining()) {
            System.out.println(buf.remaining());  //5
        }

缓冲区数据操作

Buffer提供两种用于数据操作方法:get,put获取Buffer中数据。
get():获取单个字节
get(byte[] dst):批量读取多个字节到dst中
get(int index):读取指定索引位置的字节(不移动position)
put(byte b):将给定单个字节写入缓冲区的当前位置
put(byte[] src):将src中的字节写入缓冲区的当前位置
put(int index, byte b):将指定字节写入缓冲区的索引位置(不移动position)

使用Buffer读写数据一般遵循以下四个步骤

  1. 写入数据到Buffer
  2. 调用flip方法,转换为读取模式
  3. 从Buffer中读取数据
  4. 调用buffer.clear()方法或buffer.compact()方法清楚缓冲区

直接与非直接缓冲区

buteBuffer分为基于直接内存(非堆内存),直接作用于本地IO操作,高效;另一种是非直接内存(堆内存),IO操作时要先从本进程内存复制到直接内存,再利用本地IO处理。
可使用isDriect()方法判断
非直接内存作用链:本地IO - 直接内存 - 非直接内存 - 直接内存 - 本地IO
直接内存调用链:本地IO - 直接内存 - 本地IO
结论:发送大量数据,生命周期长,直接内存效率高。直接使用allocateDirect创建,耗费性能。不过数据在JVM外存储不占用应用内存。

4.5 NIO核心2:通道Channel

通道概述

java.nio.channels包定义,表示IO源与目标打开的连接。Channel类似流,但不能直接访问数据,Channel只能与Buffer进行交互。

  1. NIO通道类似流,可异步读写数据,流只能单向。
  2. Channel是在NIO中的一个接口:public interface Channel extends Closeable{}

常用的Channel实现类

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

FileChannel

获取通道一种方式是对支持通道对象调用getChannel()方法,支持通道类型:FileInputStream,FileOutputStream,RendomAccessFile,DatagramSocket,Socket,ServerSocket。
获取管道的其他方式是使用File类的静态方法newByteChannel()获取字节通道。open()打开并返回指定通道。

        //写测试
        try {
            //1.从字节输出流写目标文件
            FileOutputStream fos = new FileOutputStream("data.txt");
            //2.得到字节输出流对应的Channel
            FileChannel channel = fos.getChannel();
            //3.分配缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            buffer.put("xuyu".getBytes());
            //4.缓冲区改为写模式
            buffer.flip();
            channel.write(buffer);
            channel.close();
            System.out.println("write ok!");
        }catch (Exception e){
            e.printStackTrace();
        }

        //读测试
        try {
            //1.定义一个文件字节输入流说源文件联通
            FileInputStream is = new FileInputStream("data.txt");
            //2.需要得到文件字节输入流管道
            FileChannel channel = is.getChannel();
            //3.定义一个缓冲区
            ByteBuffer buffer1 = ByteBuffer.allocate(1024);
            //4.读取数据到缓冲区
            channel.read(buffer1);
            buffer1.flip();
            //5.读取缓冲区中的数据并输出
            String rs = new String(buffer1.array(), 0,buffer1.remaining());  //回到首位读取
            System.out.println(rs);
        } catch (Exception e){
            e.printStackTrace();
        }
        
        //文件复制
        try{
            //源文件
            File srcFile = new File("C:\\Desktop\\Test.jpg");
            File desFile = new File("C:\\Desktop\\Test.jpg");
            //得到一个字节输出流和字节输入流
            FileInputStream fis = new FileInputStream(srcFile);
            //得到一个字节输出流
            FileOutputStream fos = new FileOutputStream(desFile);
            //得到文件通道
            FileChannel isChannel = fis.getChannel();
            FileChannel osChannel = fos.getChannel();
            
        //分散与聚集
        try{
            //1.字节输入管道
            FileInputStream is = new FileInputStream("data.txt");
            FileChannel isChannel = is.getChannel();
            //2.字节输出流管道
            FileOutputStream fos = new FileOutputStream("data2.txt");
            FileChannel fosChannel = fos.getChannel();
            //3.定义多个缓冲区做数据分散
            ByteBuffer buffer1 = ByteBuffer.allocate(4);
            ByteBuffer buffer2 = ByteBuffer.allocate(1024);
            ByteBuffer[] buffers  = {buffer1, buffer2};
            //4.从通道中读取数据分散到各个缓冲区
            isChannel.read(buffers);
            //5.从每个缓冲区中查询是否有数据读取到了
            for (ByteBuffer buffer : buffers) {
                buffer.flip();  //切换到读模式
                System.out.println(new String(buffer.array(), 0, buffer.remaining()));
            }
            //6.聚集写入
            fosChannel.write(buffers);
            isChannel.close();
            fosChannel.close();
            System.out.println("文件复制完成");
        }catch (Exception e){
            e.printStackTrace();
        }

        //从目标通道复制原通道数据
        try{
            //1.字节输入管道
            FileInputStream fis = new FileInputStream("data.txt");
            FileChannel fisChannel = fis.getChannel();
            //2.字节输出流管道
            FileOutputStream fos = new FileOutputStream("data1.txt");
            FileChannel fosChannel = fos.getChannel();
            //3.复制
            fosChannel.transferFrom(fisChannel,fosChannel.position(),fisChannel.size());
            fisChannel.close();
            fosChannel.close();
        }catch (Exception e){
            e.printStackTrace();
        }

        //从原通道数据复制到目标通道
        try{
            //1.字节输入管道
            FileInputStream is = new FileInputStream("data.txt");
            FileChannel isChannel = is.getChannel();
            //2.字节输出管道流
            FileOutputStream fos = new FileOutputStream("data1.txt");
            FileChannel osChannel = fos.getChannel();
            //3.复制
            isChannel.transferTo(isChannel.position(), isChannel.size(),osChannel);
            isChannel.close();
            osChannel.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

NIO核心3:选择器

Selector是SelectableChannel对象的多路复用器,Selector可同时监听多个SelectableChannel的IO状况。利用Selector可使一个单独的线程管理多个Channel。Selector是非阻塞IO的核心。
在这里插入图片描述

  • JavaNIO用非阻塞IO方式。用一个线程处理多个客户端连接,就会用到Selector选择器
  • Selector能检测多个注册的通道上是否有事件发生(注:多个Channel以事件的方式可注册到同一个Selector),如果有事件发生,便获取事件然后对每个事件进行相应的处理。这样就可以只用一个线程去管理多个通道。即多个连接和请求。
  • 只有在连接/通道真正有读写事件发生时,才会进行读写,就大大较少了系统开销,不必为每个连接创建线程。
  • 避免多线程之间的上下文切换。

Selector选择器应用

创建Selector:调用Selector.open()
向选择器注册通道:SelectableChannel.register(Selector sel, int ops);

   //获取通道
   ServerSocketChannel ssChannel = ServerSocketChannel.open();
   //切换非阻塞模式
   ssChannel.configureBlocking(false);
   //绑定连接
   ssChannel.bind(new InetSocketAddress(9898));
   //获取选择器
   Selector selector = Selector.open();
   //将通道注册到选择器上,并且指定“监听接收事件”
   ssChannel.register(selector, SelectionKey.OP_ACCEPT);

当调用register()将通道注册选择器时,选择器对通道的监听事件,需要通过第二个参数ops指定。可监听二点事件类型:

  • 读:SelectionKey.OP_READ (1)
  • 写:SelectionKey.OP_WRITE (4)
  • 连接:SelectionKey.OP_CONNECT (8)
  • 接收:SelectionKey.OP_ACCEPT (16)
  • 若注册时不止监听一个事件,则可使用”为或“操作符连接。

int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE

4.7 NIO非阻塞式网络通信原理分析

Selector示意图和特点说明

selector可以实现:一个I/O线程可以并发处理N个客户端连接和读写操作,这从根本上解决了传统同步阻塞I/O一连接一线程模型,架构的性能,弹性伸缩能力和可靠性都得到了极大的提升。
selector示意图

服务端流程

  • 1.当客户端连接服务端时,服务端会通过ServerSocketChannel得到SocketChannel获取通道
ServerSocketChannel = ssChannel = ServerSocketChannel.open();
  • 2.切换非阻塞模式
ssChannel.configureBlocking(false);
  • 3.绑定连接
ssChannel.bind(new InetSocketAddress(9999));
  • 4.获取选择器
ssChannel.bind(new InetSocketAddress(9999));
  • 5.将通道注册到选择器上,并且指定“监听接收事件”
ssChannel.register(selector, selectionKey.OP_ACCEPT);
  • 6.轮询式的获取选择器上一已经“准备就绪”的事件
while(selector.select() > 0) {
	sout("第一轮");
	//获取当前选择器中所有注册的“选择键(已就绪的监听事件)”
	Iterator<SelectionKey> it = selector.selectedKeys().iterator();
	while(it.hasNext()) {
		//获取准备“就绪”的事件
		SelectionKey sk = it.next();
		//判断具体是什么事件准备就绪
		if(sk.isAcceptable()) {
			//若就绪,获取客户端连接
			SocketChannel sChannel = ssChannel.accpet();
			//切换非阻塞模式
			sChannel.configureBlocking(false);
			//将该通道注册到选择器上
			sChannel.register(selector, SelectionKey.OP_READ);
		} else if (sk.isReadable()) {
			//获取当前选择器上“读就绪”状态的通道
			SocketCahnnel sChannel = (SocketChannel) sk.channel();
			//读取数据
			ByteBuffer buf = ByteBuffer.alocat(1024);
			int len = 0;
			while((len = sChannel.read(buf)) > 0) {
				buf.flip();
				sout(new String(buf.array(), 0 ,len));
				buf.clear();
			}
		}
		//取消选择键 SelectionKey
		it.remove();
	}
}

客户端流程

  • 1.获取通道
SocketChannel sChannel = socketChannel.opan(new InetSocketAddress("127.0.0.1", 9999));
  • 2.切换非阻塞模式
sChannel.configureBlocking(false);
  • 3.分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
  • 4.发送数据给服务端
Scanner scan = new Scanner(System.in);
while(scan.hasNext()) {
	String str = scan.nextLine();
	buf.put((new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(System.currentTimeMillis()) + "\n" + str).getBytes());
	buf.filp();
	sChannel().wriet(buf);
	buf.clear();
}

4.8 NIO非阻塞式网络通信案例

需求:服务端接收客户端的连接请求,并接收多个客户端发送过来的事件。
地址:https://gitee.com/xuyu294636185/JAVA_NIO_DEMO.git

4.9 NIO网络编程-群聊

需求:NIO非阻塞网路编程实现多人群聊

  • 编写一个NIO群聊,实现客户端与客户端通信(非阻塞)。
  • 服务端:可检测用户上线,离线,并实现消息转发。
  • 客户端:通过channel可无阻塞发送消息给所有客户端用户,同时接收其他客户端通过服务端转发来的消息。
  • 代码地址:https://gitee.com/xuyu294636185/JAVA_NIO_DEMO.git

5.JAVA AIO深度剖析

5.1 AIO编程

  • java AIO(NIO2):异步非阻塞,服务器实现为一个有效请求一个线程,客户端的I/O请求都是由OS先完成通知服务器应用去启动线程进行处理。
BIONIONIO
SocketSocketChannelAsynChronousScoketChannel
ServerSocketServerSocketChannelAsynchronousServerSocketChannel
与NIO不同,当进行读写操作时,只须直接调用API的read或write异步方法,当有流可读时,操作系统会将可读流传入read缓冲区,当write方法传递的流写完,操作系统主动通知应用程序。

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

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

相关文章

[游戏开发][Unity] Xlua与C#互相调用规则

第一部分&#xff1a;Xlua调用C# --Lua获取C#类 local GameObjectClass CS.UnityEngine.GameObject--使用C#类New新对象 local newGameObj GameObjectClass(helloworld) print(GameObjectClass, newGameObj)--调用C#类的静态方法 local FindObj GameObject.Find(helloworld…

Flask+表格静态展示

Python网页开发&#xff08;持续更新ing…&#xff09; 诸神缄默不语-个人CSDN博文目录 本文的需求场景是&#xff1a;我现在有一个JSON格式的表格&#xff0c;这个具体格式不重要相信你们能看懂其他格式的表格怎么改。总之我想用PythonFlask提取这个表格&#xff0c;并展示在…

PMP考试的难点在哪里以及应对策略

PMP考试内容非常综合&#xff0c;新考纲增加了一半的敏捷混合型题目&#xff0c;综合了《PMBOK指南》第6版和《敏捷实践指南》两本书的内容&#xff0c;考生需要掌握的内容就更多了&#xff0c;下面剖析PMP考试的难易点和PMP新考纲备考技巧&#xff01; 一、容易忽视的知识点 …

不负童年时光,涂鸦智能用IoT塑造新式童年

一直以来&#xff0c;儿童消费市场都是商家必争之地&#xff0c;市场前景十分广阔。尤其是随着IoT技术的发展&#xff0c;让哆啦A梦的“魔法道具”照进现实&#xff0c;越来越多的智能儿童产品开始进入家庭&#xff0c;并成为陪伴儿童成长的重要“伙伴”。 那么&#xff0c;究竟…

2023年上半年系统规划与管理师上午真题及答案解析

1.香农用概率来定量描述信息的公式如下&#xff0c;其中H(x)表示X的( )&#xff0c;Pi是( )出现第i种状态的( )。 A.信息熵 事件 概率 B.总熵 单位 概率 C.信息熵 单位 概率 D.总熵 单位 度量 2.信息传输模型中&#xff0c;( )负责信息的向外传播&#xff0c;( )负责…

一般人自学软件测试,我劝你回头是岸~

自学时间长短需要根据你个人的实际情况来看&#xff0c;有人三个月就能学成&#xff0c;有人学一年也没学出来个好歹来。每天学习多久&#xff0c;学习的是哪些视频课程&#xff0c;自己掌握能力都决定了你到底要学习多长时间。系统的培训基本是在3个月&#xff0c;那么自学就要…

加速开发RISC-V开源软件,Linux基金会启动RISE项目

使用RISC-V架构为移动、消费电子、数据中心和汽车等领域提供商用软件。 Linux软件基金会在官博宣布了RISC-V软件生态系统 RISE&#xff0c;该项目由Linux Foundation Europe托管&#xff0c;并支持RISC-V International的全球开放标准活动和成就。 官网&#xff1a;https://r…

大数据:分布式资源调度框架YARN,核心架构,主从结构,辅助结构,yarn和MapReduce部署与配置,蒙特卡罗法求圆周率PI

大数据&#xff1a;分布式资源调度框架YARN&#xff0c;核心架构&#xff0c;主从结构&#xff0c;辅助结构&#xff0c;yarn和MapReduce部署与配置&#xff0c;蒙特卡罗法求圆周率PI 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&am…

【Cloudreve】正确地用Webdav服务把网盘挂在Windows上

Cloudreve是一款基于Web平台的在线云存储管理系统。它支持各种常见云存储服务&#xff08;如Google Drive、OneDrive、Dropbox等&#xff09;的管理和集成&#xff0c;用户可以通过Cloudreve将这些云存储服务连接起来&#xff0c;方便地管理自己的云存储文件。同时&#xff0c;…

Redis7实战加面试题-高阶篇(Redlock算法和底层源码分析)

当前代码为8.0版接上一步 当前文档源码&#xff0c;接上一篇博客 Redis7实战加面试题-高阶篇&#xff08;手写Redis分布式锁&#xff09; 逐步深入&#xff0c;引入Redlock 自研一把分布式锁,面试中回答的主要考点 1.按照UC里面java.util.concurrent.locks.Lock接口规范编写…

【Linux】程序内获取文件系统挂载信息

Linux shell可通过查看/etc/mtab或者/proc/mounts文件来获取当前文件系统挂载信息&#xff0c;示例&#xff1a; 程序内读取/etc/mtab或者/proc/mounts&#xff0c;解析字符串较为繁琐&#xff0c;可以使用mntent提供的方便函数&#xff1a; FILE *setmntent(const char *file…

Linux下socketpair系统API调用使用说明

目录 1.socketpair函数说明 2.socketpair使用举例 在阅读nginx源码时&#xff0c;发现其调用socketpair来实现master和worker进程之间进行数据交互。其代码如下&#xff1a; 思考&#xff1a;master和worker进程是父子关系&#xff0c;有亲属关系的进程通过pipe/pipe2&#x…

Genio 500核心板,MT8385安卓核心板定制方案

Genio 500&#xff08;MT8385&#xff09;核心板搭载Arm Neon引擎的四核Arm Cortex-A73和Cortex-A53&#xff0c;提供必要的处理能力&#xff0c;可以通过2D/3D图形加速器进行增强&#xff0c;然后在高分辨率触摸屏显示器上进行可视化。为了提供先进的多媒体应用和服务&#xf…

电商后台管理项目vue3+express

目录 源码 1.系统功能设计 技术栈&#xff1a;采用前后端分离的开发模式前端&#xff1a;Vue3、Vue-router、Element-Plus、Axios、Echarts后端&#xff1a;Node.js、Express、Jwt、Mysql、Sequelize 2.项目初始化 打开cmd&#xff0c;输入vue ui&#xff08;vue-cli版本要…

Java程序设计入门教程--日期类Date

java.util.Date类是一个简单的日期处理类&#xff0c;它包含了一些关于时间和日期的操作方法&#xff0c;精确到毫秒。它的常用方法如表所示&#xff1a; 方法 说明 public Date() 构造方法&#xff0c;分配 Date 对象并用当前时间初始化此对象&#xff0c;以表示分配它的时…

2023年6月DAMA-CDGA/CDGP数据治理认证你考了吗?

DAMA认证为数据管理专业人士提供职业目标晋升规划&#xff0c;彰显了职业发展里程碑及发展阶梯定义&#xff0c;帮助数据管理从业人士获得企业数字化转型战略下的必备职业能力&#xff0c;促进开展工作实践应用及实际问题解决&#xff0c;形成企业所需的新数字经济下的核心职业…

NUC980编译错误,arm-linux-gcc: Command not found

报错问题&#xff1a; make: arm-linux-gcc: Command not found /bin/sh: 1: arm-linux-gcc: not found dirname: missing operand 昨天编译的时候&#xff0c;还小甜甜&#xff0c;今天就牛夫人了。啥也没干啊&#xff01; -----------------------------------------------…

亚马逊云科技与涂鸦智能持续赋能开发者,推动全行业的数智化创新

近几年&#xff0c;智能产品已渗透至人们生活的方方面面&#xff0c;IoT技术市场规模也随之获得较快增长&#xff0c;据IoT Analytics的数据&#xff0c;2023年IoT市场规模将增长19%&#xff0c;或成为经济波动周期的一大黑马赛道&#xff0c;但下游应用场景与需求的高度碎片化…

从零开始Vue3+Element Plus后台管理系统(17)——一键换肤的N种方案

暗黑模式 基于Element Plus和Tailwind CSS灵活的设计&#xff0c;我们很容易在项目中实现暗黑模式&#xff0c;具体可以参考之前的文章《从零开始写一个Vue3Element Plus的后台管理系统(二)——Layout页面布局的实现》 换肤方案 如果需要给用户提供更多主题&#xff0c;更丰…

【Android项目开发】聊天功能-主界面设计(对标企业需求)

文章目录 一、引言二、详细设计1、解决需求&#xff08;1&#xff09;图形问题&#xff08;2&#xff09;文本长度问题&#xff08;3&#xff09;时间转换问题 2、UI设计&#xff08;1&#xff09;主界面&#xff08;2&#xff09;适配器 3、Adapter适配器4、测试参数 三、附录…